我在斯卡拉嵌入式DSL的工作和宏正在成为实现我的目的的主要工具。 而试图重用传入的宏表达式的子树到生成一个我得到一个错误。 这种情况是相当复杂的,但(我希望)我已经简化它的理解。
假设我们有这样的代码:
val y = transform {
val x = 3
x
}
println(y) // prints 3
其中,“改造”是涉及宏。 虽然它可能似乎它也绝对没有什么,真的很改造所示的块到这个表达式:
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生树,我得到订货号(newTermName(“X”)),那就是究竟是什么的情况下RHS设置。 由于表达可被修改(例如X + 2,而不是X),这不是对我来说是有效的解决方案。 我想要做的就是重用XEXP树(见XEXP评论),而改变的“X”的意思(它是在输入表达式定义,但将在输出一个的情况下,LHS变量),但它会启动长期错误归纳为:
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
我目前的解决方案包括在XEXP的解析来sustitute所有新的Idents,但它是完全依赖于编译器的内部,因此,时间的解决方法。 很明显的是,XEXP与该由showRaw提供了更多的信息走来。 如何清洁是XEXP允许“X”到角色的情况下变量? 谁能解释这个错误的全貌?
PS:我一直在尝试不成功使用替代*法家族从TreeApi但我缺少的基础,以理解其含义。
拆卸输入表达式,并以不同的方式重组它们是赘言一个重要的场景(这是我们内部做的reify
宏)。 但不幸的是,这是特别不容易的时刻。
问题是,宏观到达宏实现幸福的输入参数已经typechecked。 这既是一种祝福和诅咒。
我们特别感兴趣的是,在对应参数的树木变量绑定已经建立的事实。 这意味着所有Ident
和Select
的节点有自己的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)
总括来说,我们写一个小片断,然后用scalac编译它,要求编译器的类型确定阶段后倾倒树木,打印分配给树(如果有的话)符号的唯一的ID。
在打印结果我们可以看到,标识已与相应的定义。 例如,在一方面中, ValDef("x", ...)
其表示方法foo的参数,定义了使用id = 9529的方法的符号。 在另一方面中, Ident("x")
中的方法的主体得到了它的sym
字段设置为相同的符号,它建立的绑定。
好了,我们已经看到在绑定怎么scalac工作,现在是介绍一个基本的事实的最佳时机。
If a symbol has been assigned to an AST node,
then subsequent typechecks will never reassign it.
这就是为什么物化卫生。 你可以采取具体化的结果,并将其插入到任意的树(也可能定义了名称冲突的变量) - 原绑定将保持不变。 这工作,因为物化保留了原始的符号,因此后续typechecks不会重新绑定具体化的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
相应订货号的领域。 随后类型检测,分配一个新的符号到新创建的ValDef,但不接触被复制到结果逐字原来的订货号。
该类型检测后,原来的订货号指向不再存在的符号(这正是错误消息是说:)),这会导致系统崩溃字节码生成过程中。
那么,我们如何修正这个错误? 不幸的是,没有简单的答案。
一种选择是利用c.resetLocalAttrs
,它循环擦除指定AST节点的所有符号。 然后随后的类型检测,将重新建立理所当然地认为你生成的代码不与他们乱绑定(如果,例如,你在那本身定义了一个名为值x块包裹XEXP,那么你就麻烦了)。
另一种选择是用符号来摆弄。 例如,你可以写你自己的resetLocalAttrs
,只有清除损坏的绑定和不接触有效的。 你也可以尝试自己分配的符号,但是这是一个短的路到了疯狂,尽管有时人们不得不走了。
不冷静,在所有的,我同意。 我们知道这一点,并打算尝试,有时解决这一根本性问题。 但是现在我们的手都满与最终2.10.0发布之前bugfixing,所以我们将不能够在不久的将来解决的问题。 UPD。 见https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU一些额外的信息。
底线。 不好的事情发生,因为绑定搞的一团糟。 尝试resetLocalAttrs第一,如果它不工作,对于一个苦差事做好准备。