在scala宏中,如何在编译时提升一个对象并在quasiquote中使用它?

问题描述

以下代码片段是来自 Thoughtworks 项目的简短 scala 宏包定义:

  private[SelfType] final class Macros(val c: whiteBox.Context) {
    import c.universe._

    def apply[A: WeakTypeTag]: Tree = {
      val a = weakTypeOf[A]
      val selfTypes: List[Type] = {
        val selfTypeBuilder = List.newBuilder[Type]
        def buildSelfTypes(t: Type): Unit = {
          val dealiased = t.dealias
          dealiased match {
            case RefinedType(superTypes,refinedScope) =>
              superTypes.foreach(buildSelfTypes)
            case typeRef: TypeRef =>
              val symbol = dealiased.typeSymbol
              if (symbol.isClass) {
                selfTypeBuilder += symbol.asClass.selfType.asSeenFrom(dealiased,symbol)
              }
            case _ =>
          }
        }
        buildSelfTypes(a)
        selfTypeBuilder.result()
      }
      val out = selfTypes match {
        case Nil =>
          deFinitions.AnyTpe
        case _ =>
          internal.refinedType(selfTypes,c.internal.enclosingOwner)
      }
      q"_root_.com.thoughtworks.feature.SelfType.make[$a,$out]"

    }

  }

(由 https://github.com/ThoughtWorksInc/feature.scala/blob/4d19cc19016d85f26925895f43f618e1b7552d09/SelfType/src/main/scala/com/thoughtworks/feature/SelfType.scala 提供)

作为准引用的最后一行似乎包含很多样板文本:

q"_root_.com.thoughtworks.feature.SelfType.make[$a,$out]"

假设这个宏包是作为家族多态设计模式的一部分在特征内定义的:没有确定性的q"_root_.com.thoughtworks.feature.SelfType.make[$a,$out]",它必须从宏包的对象变量vvv派生编译的时候。如何使用此变量使准引用更短且更具适应性?

可能有多种方法来实现这一点(例如,对于每个实现,为 SelfType 对象定义一个 Liftable)。但这更像是样板。我正在寻找最短的解决方案。理想情况下,是这样的:

val sym = Term(vvv)
q"$sym.make[$a,$out]"

解决方法

如果您有静态引用(例如导入类型/伴随),您可以:

q"${symbolOf[SelfType.type]}.make[$a,$out]"

如果您有 symbolOf[A].companion 但没有关于它的同伴的信息,您也可以使用 A: WeakTypeTag。如果编译器不认为 object Aclass A

的伴侣,这可能不起作用 ,

找到我的第一个解决方案:

val name = SelfTypes.getClass.getCanonicalName.stripSuffix("$")
val tree = c.parse(name)
q"$tree.make[$a,$out]"

不确定这是否是最有效或最惯用的解决方案,我将问题搁置一段时间