【问题标题】:How can I reuse definition (AST) subtrees in a macro?如何在宏中重用定义 (AST) 子树?
【发布时间】:2012-06-26 13:48:48
【问题描述】:

我正在使用 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 以用新的 Idents 替换所有 Idents,但它完全依赖于编译器内部结构,因此是一种临时解决方法。很明显,xExp 附带了 showRaw 提供的更多信息。如何清理该 xExp 以允许“x”扮演案例变量的角色?谁能解释这个错误的全貌?

PS:我一直在尝试使用 TreeApi 中的替代*方法系列,但没有成功,但我缺少了解其含义的基础知识。

【问题讨论】:

  • resetAllAttrs 工作了吗?
  • 是的,确实如此。它在显示的代码中以及在具有多个变量“更改”的非常复杂的树中工作。
  • 您在c.Expr 结果、block 或某些选定的树上调用了resetAllAttrs
  • 我现在没有测试,但我相信在 XExp 上应用重置就足够了(重置整个 c.Expr 肯定有效)。我没有尝试在输入块上应用该方法。
  • 我建议使用resetLocalAttrs,因为它的破坏性要小得多:github.com/scala/scala/pull/3305

标签: scala macros abstract-syntax-tree scala-2.10


【解决方案1】:

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

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

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

以下是符号如何工作的示例。我将从我的一个演讲中复制/粘贴一份打印输出(我在这里不提供链接,因为我的演讲中的大部分信息现在已被弃用,但这个特殊的打印输出具有永恒的用处):

>cat Foo.scala
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T]
foo[Long](42)

>scalac -Xprint:typer -uniqid Foo.scala
[[syntax trees at end of typer]]// Scala source: Foo.scala
def foo#8339
  [T#8340 >: Nothing#4658 <: Any#4657]
  (x#9529: Any#4657)
  (implicit evidence$1#9530: TypeTag#7861[T#8341])
  : T#8340 =
x#9529.asInstanceOf#6023[T#8341];
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361)

回顾一下,我们编写了一个小的 sn-p,然后用 scalac 编译它,要求编译器在 typer 阶段之后转储树,打印分配给树的符号的唯一 ID(如果有的话)。

在生成的打印输出中,我们可以看到标识符已链接到相应的定义。例如,一方面,代表方法foo的参数的ValDef("x", ...)定义了一个id=9529的方法符号。另一方面,方法体中的Ident("x") 将其sym 字段设置为相同的符号,从而建立了绑定。

好的,我们已经了解了绑定在 scalac 中的工作原理,现在是介绍一个基本事实的最佳时机。

If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 

这就是 reify 卫生的原因。您可以获取 reify 的结果并将其插入任意树(可能定义名称冲突的变量) - 原始绑定将保持不变。这是可行的,因为 reify 保留了原始符号,因此后续类型检查不会重新绑定 reified 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,但不清理相应 Ident 的 sym 字段。随后的类型检查为新创建的 ValDef 分配一个新符号,但不会触及逐字复制到结果的原始 Ident。

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

那么我们该如何解决这个错误呢?不幸的是,没有简单的答案。

一种选择是使用c.resetLocalAttrs,它递归地擦除给定AST节点中的所有符号。随后的类型检查将重新建立绑定,前提是您生成的代码不会与它们混淆(例如,如果您将 xExp 包装在一个块中,该块本身定义了一个名为 x 的值,那么您就有麻烦了)。

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

一点都不酷,我同意。我们意识到这一点,并打算有时尝试解决这个基本问题。然而,现在我们的手在 2.10.0 最终版本之前进行了错误修复,所以我们无法在最近的将来解决这个问题。更新。有关更多信息,请参阅https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU


底线。坏事发生了,因为绑定搞砸了。先尝试 resetLocalAttrs,如果不起作用,请准备好做点家务。

【讨论】:

    猜你喜欢
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-26
    • 2018-11-12
    • 2021-10-12
    • 1970-01-01
    • 2023-03-14
    相关资源
    最近更新 更多