Scala宏:将Context#TypeTag转换为JavaUniverse#TypeTag

时间:2013-08-28 14:08:33

标签: scala reflection macros scala-macros

我想使用宏来生成实例化对象的代码,如下所示:

import scala.reflect.runtime.universe._
case class Example[T: TypeTag] {
  val tpe = implicitly[TypeTag[T]].tpe
}

这显然可以转化为以下内容:

import scala.reflect.runtime.universe._
case class Example[T](implicit ev: TypeTag[T]) {
  val tpe = ev.tpe
}

然后,如果在常规代码中实例化此类,则Scala编译器会自动提供TypeTag实例。

但是,我想生成代码,用不同的T s实例化这个类的几个实例,其中具体的T取决于用户输入,如

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])

know如何获取密封特征的子类,因此我可以在宏代码中访问c.WeakTypeTag[SubTest1]c.WeakTypeTag[SubTest2]。但我不知道如何将它们转换为TypeTag方法预期的Example.apply。我想过使用in()方法似乎允许在Universe之间传递TypeTag,但它需要目标镜像,我不知道如何在编译时从宏内部获取运行时镜像。< / p>

这是我到目前为止的代码(我添加了几个注释和额外的语句,以便更清楚):

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")
    }
  }
}

2 个答案:

答案 0 :(得分:2)

您正在寻找:

c.reifyType(treeBuild.mkRuntimeUniverseRef, EmptyTree, childTpe)

上述代码将以import c.universe._为准。

它将创建一个Tree,最终将在运行时评估为您想要的scala.reflect.runtime.universe.TypeTag[_]

第二个想法,我认为可能根本不需要手动生成这棵树。从宏返回的树经历了更多的类型检查,这意味着编译器可能能够为您填充隐式TypeTag。但是,它需要进行测试。试试这个:

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

答案 1 :(得分:2)

您无需担心在此提供运行时类型标记 - 编译器会为您找到它(我在另一个答案中看到ghik注释)。诀窍是在子类的类型符号上使用toType,而不是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"
    )
  }
}

为了清楚起见,我在这里使用了quasiquotes,因为它们是now easily available in 2.10 projects,但是如果你不想要它们,那么使用手动树构造来调整这些代码是很简单的。