scalametaprogrammingscala-macrostype-constructor

Applying type to a type constructor in macro throws exception


Consider the following simple macro fragment:

val lstConstructor = weakTypeTag[List[String]].tpe.typeConstructor
println(c.typecheck(tq"$lstConstructor[String]", mode = c.TYPEmode))

This seemed to be pretty straightforward piece of code fails with the following no that informative exception:

[error] scala.reflect.macros.TypecheckException: Any does not take type parameters
[error]         at scala.reflect.macros.contexts.Typers.$anonfun$typecheck$3(Typers.scala:44)
[error]         at scala.reflect.macros.contexts.Typers.$anonfun$typecheck$2(Typers.scala:38)
[error]         at scala.reflect.macros.contexts.Typers.doTypecheck$1(Typers.scala:37)
[error]         at scala.reflect.macros.contexts.Typers.typecheck(Typers.scala:51)
[error]         at scala.reflect.macros.contexts.Typers.typecheck$(Typers.scala:32)
[error]         at scala.reflect.macros.contexts.Context.typecheck(Context.scala:18)
[error]         at scala.reflect.macros.contexts.Context.typecheck(Context.scala:18)

What is the right way to aoply a type argument to a type constructor?

UPD:

Manually specifying hardcoded name of the type constructor as

println(c.typecheck(tq"List[String]", mode = c.TYPEmode))

works perfectly fine


Solution

  • Welcome to undocumented land of untyped compiler guts.

    The quasiquote tq"List[String]" makes the following tree:

    AppliedTypeTree(Ident(TypeName("List")), List(Ident(TypeName("String"))))
    

    Notice that the first parameter is not a Type, it's an Ident. So your quasiquote, tq"$lstConstructor[String]", silently replaces the unappropriately typed tree with an empty TypeTree():

    AppliedTypeTree(TypeTree(), List(Ident(TypeName("String"))))
    

    I'm pretty sure that is equivalent to Any, so you get the error.


    Now, to get an Ident out of a Type, you can use .typeSymbol method (though idents don't have type param info, it doesn't matter whether you did .typeConstructor before or not). This should work:

    c.typecheck(tq"${lstConstructor.typeSymbol}[String]", mode = c.TYPEmode)
    

    Code on Scastie (using runtime universe) here.