zio / zio-mock

https://zio.dev/zio-mock
Apache License 2.0
27 stars 33 forks source link

Bounds on Poly input types are not represented in @mockable macro expansion #17

Open TrustNoOne opened 2 years ago

TrustNoOne commented 2 years ago

The following code does not compile on the latest 1.0.0-RC5 (and I think on any other version):

trait BoundedType[A <: AnyRef]

trait TestService {
  def polyBoundedIn[A <: AnyRef: EnvironmentTag](a: BoundedType[A]): UIO[Unit]
}

and the mock :

@mockable[TestService]
object TestServiceMock

The compiler error:

<macro>:5:37: type arguments [A] do not conform to trait BoundedType's type parameter bounds [A <: AnyRef]
[error]       final def polyBoundedIn[A](a: BoundedType[A])(implicit evidence$1: zio.EnvironmentTag[A]): _root_.zio.ZIO[Any, Nothing, Unit] = proxy(TestServiceMock.PolyBoundedIn.of[BoundedType[A]], a)

The problem seems to be in this part of the macro https://github.com/zio/zio-mock/blob/e437c0af392fd3b93c9e320c9d50795749bb0071/mock/shared/src/main/scala-2/zio/mock/MockableMacro.scala#L69

and in particular the bound function:

    def bound(tpe: Type): Tree =
      tq"$tpe" match {
        case TypeTree() => EmptyTree
        case nonEmpty   => nonEmpty
      }

The first branch TypeTree() matches and as a result the A <: Something becomes just A in the macro definition. That becomes noticeable when the parameter of the mocked method has itself a type paramater that must respect the bound, like in my example above.

I don't know macros enough to know what happens removing that "bound" function and simply always doing case TypeBounds(lo, hi) => tq"$lo" -> tq"$hi". I tried to modify the code and all the tests seem to pass (including a new one for the failing case), but I really don't know why that replacement with EmptyTree was there in the first place so I haven't opened a pull request for it.

If you need any more information, let me know!