2

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

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的 s 取决于用户输入,比如

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

知道如何获取密封特征的子类,因此我可以访问c.WeakTypeTag[SubTest1]c.WeakTypeTag[SubTest2]代码。但我不知道如何TypeTag通过方法将它们变成预期的Example.apply。我想过使用in()似乎允许TypeTag在宇宙之间传输 s 的方法,但它需要目标镜像,而且我不知道如何在编译时从宏内部获取运行时镜像。

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

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

2 回答 2

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,因为它们现在在 2.10 项目中很容易获得,但如果您不想要它们,可以直接修改此代码以使用手动树构造。

于 2013-08-28T14:55:03.120 回答
2

你正在寻找这个:

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

上面的代码将假设import c.universe._.

它将创建一个Tree最终将scala.reflect.runtime.universe.TypeTag[_]在运行时评估为您所需的值。

再三考虑,我认为可能根本不需要手动生成这棵树。从宏返回的树经过更多类型检查,这意味着编译器可能能够TypeTag为您填充隐式 s。不过,它需要进行测试。尝试使用这个:

TypeApply(Select(reify(Example).tree, newTermName("apply")), TypeTree().setType(childTpe))
于 2013-08-28T14:35:46.533 回答