Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZEnvironment#prune doesn't work with newtypes on scala 2 #1341

Open
paulpdaniels opened this issue Jun 26, 2024 · 2 comments
Open

ZEnvironment#prune doesn't work with newtypes on scala 2 #1341

paulpdaniels opened this issue Jun 26, 2024 · 2 comments

Comments

@paulpdaniels
Copy link

paulpdaniels commented Jun 26, 2024

This is more of an aspirational issue as I'm not sure if this is actually fixable. At issue is the fact that Newtype[A] will fail at runtime if it interacts with ZEnvironment#prune.

Consider:

object TestApp extends ZIOAppDefault {

  case class Wrapped[A](value: A)

  object MyType1 extends Newtype[Wrapped[Int]] {
    implicit val tag: Tag[Type] = derive[Tag]
    val layer: ULayer[Type]     = ZLayer.succeed[Type](wrap(Wrapped(42)))
  }
  type MyType1 = MyType1.Type

  object MyType2 extends Newtype[Wrapped[String]] {
    implicit val tag: Tag[Type] = derive[Tag]
    val layer: ULayer[Type]     = ZLayer.succeed[Type](wrap(Wrapped("42")))
  }
  type MyType2 = MyType2.Type

  val run = (for {
    env <- ZIO.environment[MyType1 with MyType2]
    _ = env.prune
  } yield ()).provide(
    MyType1.layer,
    MyType2.layer
  )

}

This will fail with Defect in zio.ZEnvironment: HashSet(TestApp::MyType1::Type, TestApp::MyType2::Type) statically known to be contained within the environment are missing

You can artificially make this work by explicitly providing a cast EnvironmentTag

  val envTag: EnvironmentTag[MyType1 with MyType2] =
    EnvironmentTag[MyType1.Wrapped with MyType2.Wrapped]
      .asInstanceOf[EnvironmentTag[MyType1 with MyType2]]
      
   env.prune(envTag)

Moreover as long as you don't invoke a prune it will function correctly.

for instance:

  val run = (for {
    myType1 <- ZIO.service[MyType1]
    myType2 <- ZIO.service[MyType2]
  } yield ()

I suspect the issue arises because of how type tagging occurs. During creation of a layer we use the derive mechanism which is essentially just implicitly[Tag[Wrapped]].asInstanceOf[Tag[Type]], this means that the LightTagType that is inserted into the type map is that of the underlying type. In most cases the get operation then uses the same mechanism, that is fetching by the underlying type id. However, in the prune case it is generating an intersection type via macro which seems to hold onto ephemeral type of the newtype instead. Hence, when it iterates through the set of environmental types it can't find them (because it isn't referencing the aliased type anymore). This is just a suspicion though, I don't have enough experience with the internals of this to say for certain.

If this is actually an impossible ask, I would suggest adding a disclaimer to the docs indicating that newtypes aren't usable in this fashion, since this particular issue occurs at runtime.

@finalchild
Copy link

Also occurs in Scala 3

@kyri-petrou
Copy link
Contributor

Moreover as long as you don't invoke a prune it will function correctly

This is the part that I'm finding most bizarre. I'm wondering whether this might be a legit bug either in how we do subtype checking in ZIO during pruning, or in izumi-tag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants