Search code examples
scalareflectionmacrostype-constructormaterialize

Applying type constructors to generated type parameters with Scala macros


I am trying to materialize an instance of the (simplified) trait

trait TC[F[_]] {
  def apply[A](fa: F[A]): F[A]
}

using Scala macros. The signature of the macro therefore is

def materialize[F[_]](c: Context)(
  implicit fT: c.WeakTypeTag[F[_]]): c.Expr[TC[F]]

Type constructor F[_] now needs to be applied to the type parameter A for two reasons:

  1. To write the signature of apply above for a particular F (like Foo[A])
  2. To inspect the members of the type Foo[A] in order to specify an interesting body of apply

Is there any way to create the type corresponding to the method type parameter A that can than be used in appliedType? It appears difficult for me, since the method apply and its type parameter A are also just being generated as trees.


I tried to take WeakTypeTag[TC[F]] as additional argument to the macro call and received the paramter type by

val paramT = wfg.tpe.member("apply": TermName).tpe.typeParams.head.tpe

but then using paramT in q"... def apply[$paramT] ..." does result in

java.lang.IllegalArgumentException: can't splice "A" as type parameter

so this appears also to be no solution.


Solution

  • I solved the problem by changing the definition of the above trait to

    trait TC[F[_]] {
      type ApplyF[A] = F[A]
      def apply[A](fa: ApplyF[A]): ApplyF[A]
    }
    

    and typechecking the tree for a dummy value:

    typecheck(q"""new TC[Foo] {
      def apply[A](fa: ApplyF[A]): ApplyF[A] = ???
    }""").tpe
    

    The typechecked result then could be destructed and transformed (by means of tree transformers) to fill in the ???. This did not solve the problem completely, yielding the type error:

    found   : A(in method apply)(in method apply)(in method apply)...
    required: A(in method apply)(in method apply)(in method apply)...
    

    While calling untypecheck before returning the tree did not help - inspection of the resulting tree however showed that the expected result is valid (type correct) Scala code. Thus, the last step that made the macro finally fly was to call

    parse(showCode(result))
    

    It feels completely unnecessary, but it seems that this was the only way to get rid of conflicting type information.