Scala 如何在宏中重用定义(AST)子树?

Scala 如何在宏中重用定义(AST)子树?,scala,macros,abstract-syntax-tree,scala-2.10,Scala,Macros,Abstract Syntax Tree,Scala 2.10,我在Scala嵌入式DSL中工作,宏正成为实现我目标的主要工具。尝试将传入宏表达式中的子树重用到生成的子树时出错。情况相当复杂,但(我希望)我已将其简化以便于理解 假设我们有以下代码: val y = transform { val x = 3 x } println(y) // prints 3 其中“transform”是所涉及的宏。虽然看起来它似乎什么都不做,但它实际上是在将所示的块转换为以下表达式: 3 match { case x => x } 这是通过以下宏实现完成

我在Scala嵌入式DSL中工作,宏正成为实现我目标的主要工具。尝试将传入宏表达式中的子树重用到生成的子树时出错。情况相当复杂,但(我希望)我已将其简化以便于理解

假设我们有以下代码:

val y = transform {
  val x = 3
  x
}
println(y) // prints 3
其中“transform”是所涉及的宏。虽然看起来它似乎什么都不做,但它实际上是在将所示的块转换为以下表达式:

3 match { case x => x }
这是通过以下宏实现完成的:

def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = {
  import c.universe._
  import definitions._

  block.tree match {
    /* {
     *   val xNam = xVal
     *   xExp
     * }
     */
    case Block(List(ValDef(_, xNam, _, xVal)), xExp) =>
      println("# " + showRaw(xExp)) // prints Ident(newTermName("x"))
      c.Expr(
        Match(
          xVal, 
          List(CaseDef(
            Bind(xNam, Ident(newTermName("_"))),
            EmptyTree,
            /* xExp */ Ident(newTermName("x")) ))))
    case _ => 
      c.error(c.enclosingPosition, "Can't transform block to function")
      block  // keep original expression
  }
}
请注意,xNam对应变量名,xVal对应其关联值,最后xExp对应包含变量的表达式。好的,如果我打印xExp原始树,我会得到Ident(newTermName(“x”)),这正是在RHS中设置的。由于表达式可以修改(例如x+2而不是x),因此这对我来说不是一个有效的解决方案。我想做的是重用xExp树(请参阅xExp注释),同时更改“x”的含义(它是输入表达式中的一个定义,但在输出表达式中是case LHS变量),但它会启动一个长错误,总结如下:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
我目前的解决方案是解析xExp,用新的标识替换所有标识,但它完全依赖于编译器内部,因此是一种暂时的解决方法。很明显,xExp附带了showRaw提供的更多信息。如何清除允许“x”充当case变量的xExp?有人能解释这个错误的全貌吗


PS:我一直试图使用来自的替代*方法系列,但没有成功,但我缺少理解其含义的基本知识。

分解输入表达式并以不同的方式重新组装它们是宏观学中的一个重要场景(这是我们在
具体化
宏内部所做的)。但不幸的是,目前这并不特别容易

问题是宏的输入参数到达宏实现时已经进行了类型检查。这既是福也是祸

我们特别感兴趣的是,与参数对应的树中的变量绑定已经建立。这意味着所有
Ident
Select
节点的
sym
字段都已填充,指向这些节点引用的定义

下面是一个符号如何工作的示例。我将复制/粘贴我的一次演讲的打印输出(我在这里不提供链接,因为我演讲中的大多数信息现在都已被弃用,但这个打印输出永远有用):

这就是为什么物化是卫生的。您可以获取reify的结果并将其插入任意树(该树可能定义名称冲突的变量)——原始绑定将保持不变。这是因为具体化保留了原始符号,因此后续的类型检查不会重新绑定具体化的AST节点

现在,我们都准备好解释您面临的错误:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
transform
宏的参数包含变量
x
的定义和引用。正如我们刚刚了解到的,这意味着相应的ValDef和Ident将同步其
sym
字段。到目前为止,一切顺利

然而不幸的是,宏破坏了已建立的绑定。它重新创建ValDef,但不清除相应标识的
sym
字段。随后的typecheck会为新创建的ValDef分配一个新符号,但不会触及复制到结果中的原始标识

在类型检查之后,原始标识指向一个不再存在的符号(这正是错误消息所说的:),这导致字节码生成过程中崩溃

那么我们如何修复错误呢?不幸的是,没有简单的答案

一个选项是利用
c.resetLocalAttrs
,它递归地擦除给定AST节点中的所有符号。随后的typecheck将重新建立绑定,并确保生成的代码不会弄乱它们(例如,如果将xExp包装在一个本身定义了一个名为x的值的块中,那么您就有麻烦了)

另一种选择是摆弄符号。例如,您可以编写自己的
resetLocalAttrs
,只擦除损坏的绑定,而不触及有效的绑定。你也可以试着自己分配符号,但这是一条通往疯狂的捷径,尽管有时你不得不走这条路

一点也不酷,我同意。我们意识到这一点,并打算有时尝试解决这一基本问题。然而,在最终的2.10.0版本发布之前,我们手头的工作都是bug修复,因此我们无法在最近的将来解决这个问题。upd。有关更多信息,请参阅



底线。糟糕的事情会发生,因为绑定会出错。首先尝试resetLocalAttrs,如果它不起作用,请做好做家务的准备。

毕竟,
resetAllAttrs
起作用了吗?是的,起作用了。它可以在显示的代码中工作,也可以在一个包含多个变量“更改”的非常复杂的树中工作。您在
c.Expr
结果、在
block
或某些选定的树上调用了
resetAllAttrs
?我现在没有测试,但我相信在XExp上应用reset就足够了(重置整个c.Expr肯定有效)。我没有尝试在输入块上应用该方法。我建议使用
resetLocalAttrs
,因为它的破坏性要小得多:
If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.