Vladimir Matveev
Vladimir Matveev

Reputation: 127831

Scala macros: convert Context#TypeTag to JavaUniverse#TypeTag

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 Ts, where concrete Ts 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 TypeTags expected by Example.apply method. I thought of using in() method which seems to allow transferring TypeTags 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

Answers (2)

Travis Brown
Travis Brown

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

ghik
ghik

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 TypeTags for you. It needs to be tested, though. Try using this:

TypeApply(Select(reify(Example).tree, newTermName("apply")), TypeTree().setType(childTpe))

Upvotes: 2

Related Questions