【问题标题】:Scala macros: convert Context#TypeTag to JavaUniverse#TypeTagScala 宏:将 Context#TypeTag 转换为 JavaUniverse#TypeTag
【发布时间】:2013-08-28 22:57:47
【问题描述】:

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

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 实例。

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

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]。但我不知道如何将它们转换为Example.apply 方法所期望的TypeTags。我想过使用in() 方法,它似乎允许在宇宙之间传输TypeTags,但它需要目标镜像,我不知道如何在编译时从宏内部获取运行时镜像。

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

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

【问题讨论】:

    标签: scala reflection macros scala-macros


    【解决方案1】:

    你正在寻找这个:

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

    上面的代码将在假设import c.universe._时工作。

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

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

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

    【讨论】:

    • 哇,太好了。我想我应该先尝试一下,但我认为编译器并不那么聪明。显然,我错了)非常感谢。
    【解决方案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"
        )
      }
    }
    

    为了清楚起见,我在这里使用了准引号,因为它们是 now easily available in 2.10 projects,但如果您不想要它们,可以直接修改此代码以使用手动树构造。

    【讨论】:

    • 非常感谢。似乎我试图超越自己。可悲的是,IDEA 还不支持准引号(我想我必须坚持手动构建树,毕竟在这种情况下并不是很难。老实说,两个答案都是正确且有帮助的,所以我接受另一个,因为它是首先。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    • 2019-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多