我想使用宏来生成实例化对象的代码,如下所示:
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")
}
}
}
答案 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,但是如果你不想要它们,那么使用手动树构造来调整这些代码是很简单的。