Reputation: 127831
I want to use macros to generate code which instantiates objects which look like this:
import scala.reflect.runtime.universe._
case class Example[T: TypeTag] {
val tpe = implicitly[TypeTag[T]].tpe
}
This, obviously, translates to something like the following:
import scala.reflect.runtime.universe._
case class Example[T](implicit ev: TypeTag[T]) {
val tpe = ev.tpe
}
Then if this class is instantiated in regular code Scala compiler provides TypeTag
instance automatically.
However, I want to generate code which instantiates several instances of this class with different T
s, where concrete T
s depend on user input, something like
sealed trait Test
case class SubTest1 extends Test
case class SubTest2 extends Test
val examples = generate[Test]
// I want this ^^^^^^^^^^^^^^ to expand into this:
val examples = Seq(Example[SubTest1], Example[SubTest2])
I know how to get subclasses of a sealed trait, so I can access c.WeakTypeTag[SubTest1]
and c.WeakTypeTag[SubTest2]
in macro code. But I do not know how to turn them to TypeTag
s expected by Example.apply
method. I thought of using in()
method which seems to allow transferring TypeTag
s between universes, but it requires destination mirror, and I don't know how to get runtime mirror at compile time from inside the macro.
Here is the code I have so far (I have added several annotations and extra statements for it to be clearer):
object ExampleMacro {
def collect[T] = macro collect_impl
def collect_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[Example[_]]] = {
import c.universe._
val symbol = weakTypeOf[T].typeSymbol
if (symbol.isClass && symbol.asClass.isTrait && symbol.asClass.isSealed) {
val children = symbol.asClass.knownDirectSubclasses.toList
if (!children.forall(c => c.isClass && c.asClass.isCaseClass)) {
c.abort(c.enclosingPosition, "All children of sealed trait must be case classes")
}
val args: List[c.Tree] = children.map { ch: Symbol =>
val childTpe = c.WeakTypeTag(ch.typeSignature) // or c.TypeTag(ch.typeSignature)
val runtimeChildTpe: c.Expr[scala.reflect.runtime.universe.TypeTag[_]] = ??? // What should go here?
Apply(Select(reify(Example).tree, newTermName("apply")), runtimeChildTpe.tree)
}
Apply(Select(reify(Seq).tree, newTermName("apply")), args)
} else {
c.abort(c.enclosingPosition, "Can only construct sequence from sealed trait")
}
}
}
Upvotes: 2
Views: 764
Reputation: 139038
You don't need to worry about providing the runtime type tag here—the compiler will find it for you (as I see ghik notes in another answer). The trick is to use toType
on the child class's type symbol, instead of typeSignature
:
object ExampleMacro {
def collect[T] = macro collect_impl[T]
def collect_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[Example[_]]] = {
import c.universe._
val symbol = weakTypeOf[T].typeSymbol
if (symbol.isClass && symbol.asClass.isTrait && symbol.asClass.isSealed) {
val children = symbol.asClass.knownDirectSubclasses.toList
if (!children.forall(c => c.isClass && c.asClass.isCaseClass)) c.abort(
c.enclosingPosition,
"All children of sealed trait must be case classes"
)
val args: List[c.Tree] = children.collect {
case child: TypeSymbol => q"Example[${child.toType}]"
}
c.Expr[Seq[Example[_]]](
Apply(Select(reify(Seq).tree, newTermName("apply")), args)
) // or just c.Expr[Seq[Example[_]]](q"Seq(..$args)")
} else c.abort(
c.enclosingPosition,
"Can only construct sequence from sealed trait"
)
}
}
I've used quasiquotes here for clarity, and since they're now easily available in 2.10 projects, but if you don't want them it would be straightforward to adapt this code to use manual tree construction.
Upvotes: 2
Reputation: 10764
You are looking for this:
c.reifyType(treeBuild.mkRuntimeUniverseRef, EmptyTree, childTpe)
The above code will work assuming import c.universe._
.
It will create a Tree
that will eventually evaluate to your desired scala.reflect.runtime.universe.TypeTag[_]
in runtime.
On second thought, I think manual generation of this tree may not be needed at all. Tree returned from a macro undergoes more typechecking which means that the compiler may be able to fill implicit TypeTag
s for you. It needs to be tested, though. Try using this:
TypeApply(Select(reify(Example).tree, newTermName("apply")), TypeTree().setType(childTpe))
Upvotes: 2