检测宏注释正文中的宏注释类型

时间:2014-03-08 22:11:18

标签: scala macros

我想使用宏注释(macro-paradise,Scala 2.11)在带注释的特征的伴随对象中生成合成特征。例如,给定一些STM抽象:

trait Var[Tx, A] {
  def apply()         (implicit tx: Tx): A
  def update(value: A)(implicit tx: Tx): Unit
}

我想定义一个宏注释txn,以便:

@txn trait Cell[A] {
  val value: A
  var next: Option[Cell[A]]
}

将重新合成为:

object Cell {
  trait Txn[-Tx, A] {
    def value: A
    def next: Var[Option[Cell.Txn[Tx, A]]] // !
  }
}
trait Cell[A] {
  val value: A
  var next: Option[Cell[A]]
}

我得到了伴侣对象,内部特征和value成员。但显然,为了使next成员具有扩充类型(而不是Option[Cell[A]],我需要Option[Cell.Txn[Tx, A]]),我需要模式匹配类型树并重写它。

例如,假设我在原始next特征中找到Cell值定义,如下所示:

case v @ ValDef(vMods, vName, tpt, rhs) =>

如何递归分析tpt以重写使用X[...]注释到@txn的任何类型X.Txn[Tx, ...]?这是否可能,如上例所示,X尚未处理?我应该修改Cell以混合要检测的标记特征吗?


因此模式匹配函数可以这样开始:

val tpt1 = tpt match {
  case tq"$ident" => $ident  // obviously don't change this?
  case ??? => ???
}

1 个答案:

答案 0 :(得分:9)

我想在免责声明中说明我的答案,这种东西在当前的Scala中并不容易。在理想的宏系统中,我们想在其词汇上下文中检查tpt,然后遍历结果类型的结构,将X[A]替换为X.Txn[Tx, A],用于那些XTxnMarker的子类型,然后在宏扩展中使用结果类型。

然而,无类型树(进入宏注释)和类型树(类型检查器发出)的这种快乐混合与编译器内部工作方式不兼容(有关详细信息可以在Scala macros: What is the difference between typed (aka typechecked) an untyped Trees中找到) ,所以我们必须近似。

这将是一个近似值,因为在2.10和2.11中我们的宏都是不卫生的,这意味着它们容易受到名称冲突的影响(例如,Var中的最终扩展树Var[...]可能会绑定某些东西不相关的,例如我们正在重写的特征包含一个名为Var的类型成员。不幸的是,目前只有一种方法可以很好地解决这个问题,而且如果没有深入理解编译器内部结构就非常难以实现,所以我不会在这里详细介绍这些细节。

import scala.reflect.macros.whitebox._
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation

trait Var[T]
trait TxnMarker

object txnMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._
    // NOTE: this pattern is only going to work with simple traits
    // for a full pattern that captures all traits, refer to Denys's quasiquote guide:
    // http://den.sh/quasiquotes.html#defns-summary
    val q"$mods trait $name[..$targs] extends ..$parents { ..$stats }" = annottees.head.tree
    def rewire(tpt: Tree): Tree = {
      object RewireTransformer extends Transformer {
        override def transform(tree: Tree): Tree = tree match {
          case AppliedTypeTree(x @ RefTree(xqual, xname), a :: Nil) =>
            val dummyType = q"type SomeUniqueName[T] = $x[T]"
            val dummyTrait = q"$mods trait $name[..$targs] extends ..$parents { ..${stats :+ dummyType} }"
            val dummyTrait1 = c.typecheck(dummyTrait)
            val q"$_ trait $_[..$_] extends ..$_ { ..${_ :+ dummyType1} }" = dummyTrait1
            def refersToSelf = dummyTrait1.symbol == dummyType1.symbol.info.typeSymbol
            def refersToSubtypeOfTxnMarker = dummyType1.symbol.info.baseClasses.contains(symbolOf[TxnMarker])
            if (refersToSelf || refersToSubtypeOfTxnMarker) transform(tq"${RefTree(xqual, xname.toTermName)}.Txn[Tx, $a]")
            else super.transform(tree)
          case _ =>
            super.transform(tree)
        }
      }
      RewireTransformer.transform(tpt)
    }
    val stats1 = stats map {
      // this is a simplification, probably you'll also want to do recursive rewiring and whatnot
      // but I'm omitting that here to focus on the question at hand
      case q"$mods val $name: $tpt = $_" => q"$mods def $name: $tpt"
      case q"$mods var $name: $tpt = $_" => q"$mods def $name: Var[${rewire(tpt)}]"
      case stat => stat
    }
    val annottee1 = q"$mods trait $name[..$targs] extends ..${parents :+ tq"TxnMarker"} { ..$stats }"
    val companion = q"""
      object ${name.toTermName} {
        trait Txn[Tx, A] { ..$stats1 }
      }
    """
    c.Expr[Any](Block(List(annottee1, companion), Literal(Constant(()))))
  }
}

class txn extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro txnMacro.impl
}

实际上,在我编写这个宏时,我意识到处理@txn - 注释定义的循环依赖性是不够的。 info.baseClasses.contains(symbolOf[TxnMarker])检查基本上强制扩展info中引用的类/特征,因此宏将循环。这不会导致SOE或冻结 - scalac只会产生循环参考错误并挽救。

目前我还不知道如何用宏来解决这个问题。也许您可以将代码生成部分保留在注释宏中,然后将类型转换部分移动到fundep实现器中。哦,对,似乎它可能会奏效!而不是生成

object Cell {
  trait Txn[-Tx, A] {
    def value: A
    def next: Var[Option[Cell.Txn[Tx, A]]]
  }
}
trait Cell[A] {
  val value: A
  var next: Option[Cell[A]]
}

你实际上可以生成这个:

object Cell {
  trait Txn[-Tx, A] {
    def value: A
    def next[U](implicit ev: TxnTypeMapper[Option[Cell[A]], U]): U
  }
}
trait Cell[A] {
  val value: A
  var next: Option[Cell[A]]
}

TxnTypeMapper[T, U]可以召唤fundep materializer macro使用Type => Type进行Type.map转换,而且{{1}}不会导致循环引用错误,因为调用实现器的时间(在typer期间),所有宏注释都已经扩展。不幸的是,我现在没有时间详细说明,但这看起来很可行!!