Reputation: 85
I'm trying to make an existing macro that generates typeclass instances work for parameterized types, where the type argument already has an instance of the typeclass. I'm surprised that it fails to resolve the existing (type argument's) typeclass in this case, but that appears to be what's happening.
I've tried to pare it down to a small example that exhibits the behavior. Here's the macro definition:
package test
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
trait Read[A] {
def read(in: String): A
}
object Read {
def CaseClassReadImpl[A: c.WeakTypeTag](c: Context): c.Expr[Read[A]] = {
import c.universe._
val aType = weakTypeOf[A]
val params = aType.decls.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
val paramList = params.map(param => q"Read.read[${param.typeSignature}](in)")
val src = q"""
new Read[$aType] {
def read(in: String) = ${aType.typeSymbol.companion}.apply(..$paramList)
}
"""
c.Expr[Read[A]](src)
}
def readFor[A]: Read[A] = macro CaseClassReadImpl[A]
def read[A](in: String)(implicit A: Read[A]): A = A.read(in)
}
Here's the code that exercises it:
package test
object MacroTest {
case class Foo[A](bar: A)
implicit def fooRead[A](implicit A: Read[A]): Read[Foo[A]] =
Read.readFor[Foo[A]]
}
I was expecting this to succeed, thinking that the implicit parameter to the generated call to Read.read
would resolve to the implicit argument to the fooRead
function. Instead, it fails when defining fooRead
with:
Error:(7, 17) could not find implicit value for parameter A: test.Read[A]
Read.readFor[Foo[A]]
Why is it not using the implicit parameter A
to fooRead
? It's in lexical scope.
I realize that I should convert this codebase to use shapeless or some such library, but right now I'm just trying to get it working with as little effort as possible.
Update:
I've figured out that the problem derives from having two distinct A
s. The error above makes it look like the required implicit is the same type as the one I have in scope, but after fiddling with it a bit and passing the implicit in explicitly, I managed to get this (much more helpful) error message:
Error: type mismatch;
found : A(in class Foo)
required: A(in method fooRead)
implicit def fooRead[A](implicit read: Read[A]): Read[Foo[A]] = Read.readFor[Foo[A]]
I still haven't been able to find a way to get Scala to understand that I want to the A
s to be the same: the one passed into the fooRead
function.
Upvotes: 1
Views: 153
Reputation: 51703
Try to replace
param => q"Read.read[${param.typeSignature}](in)"
with
param => q"Read.read[${param.typeSignatureIn(aType)}](in)"
https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Symbols.scala#L335-L345
/** @see [[infoIn]] */
def typeSignatureIn(site: Type): Type
/** The type signature of this symbol seen as a member of given type `site`.
*
* @group Basics
*/
def infoIn(site: Type): Type
/** @see [[info]] */
def typeSignature: Type
/** The type signature of this symbol.
*
* This method always returns signatures in the most generic way possible, even if the underlying symbol is obtained from an
* instantiation of a generic type. For example, signature
* of the method `def map[B](f: (A) ⇒ B): List[B]`, which refers to the type parameter `A` of the declaring class `List[A]`,
* will always feature `A`, regardless of whether `map` is loaded from the `List[_]` or from `List[Int]`. To get a signature
* with type parameters appropriately instantiated, one should use `infoIn`.
*
* @group Basics
*/
def info: Type
Upvotes: 0