理论部分
这是 scalac 的一个架构特性,一旦我们在 2.10 中的编译时/运行时反射中公开内部编译器数据结构,它就开始泄漏到公共 API 中。
粗略地说,scalac 的前端由一个解析器和一个打字器组成,两者都与树一起工作并生成树作为结果。然而,这些树的属性有很大不同,这是因为解析器生成的树是无属性的(具有它们的属性)symbol
字段设置为NoSymbol
和他们的tpe
字段设置为null
),而 typer 生成的树则被归因。
现在你可能想知道这会带来什么不同,因为它只是symbol
and tpe
, 正确的?然而,在 scalac 中,它不仅仅如此。为了完成它的工作,typer 改变了它正在处理的 AST 的结构,破坏了一些原始树并生成了一些合成树。不幸的是,有时这些转换是不可逆的,这意味着如果对一棵树进行类型检查,然后删除所有指定的属性,则生成的树将不再有意义(https://issues.scala-lang.org/browse/SI-5464 https://issues.scala-lang.org/browse/SI-5464).
好吧,但是为什么要擦除(或者用 scalac 的说法,重置,如resetLocalAttrs
or resetAllAttrs
)类型检查树的属性?好吧,这种必要性源于另一个实现细节——符号及其所有者链。就在几天前,我在 scala-internals 上写了一些有关此内容的详细信息:https://groups.google.com/d/msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ https://groups.google.com/d/msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ,但简而言之,您不能在某些词汇上下文中对树进行类型检查,然后简单地在不同的词汇上下文中使用它(这是本质上需要的)c.eval
).
因此,总结一下 scalac 树管理的最新技术:
- 无类型树(也称为解析器树或无属性树)在观察上与类型树(也称为类型树、类型检查树或属性树)不同
- 这两种树风格之间有两个主要区别:a)类型化树具有由类型检查器设置的符号和类型,b)类型化树具有稍微不同的形状。
- 通常,如果某些编译器 API 采用树,那么无类型树和类型树都可以。然而,在某些情况下(我在上面概述的其中之一),只有无类型或只有类型的树才是合适的。
- 可以通过调用从无类型树到类型树
Context.typecheck
(编译时反射)或ToolBox.typecheck
(运行时反射),但是通过以下方式从类型化树返回到非类型化树resetLocalAttrs
or resetAllAttrs
目前不可靠,因为https://issues.scala-lang.org/browse/SI-5464 https://issues.scala-lang.org/browse/SI-5464.
因此,正如您所看到的,我们的树非常反复无常,这给 Scala 中的元编程带来了很大的复杂性。
然而,好消息是,这种复杂性并不是由源自编译器 101 的一些基本的好理由决定的。所有的复杂性都是偶然的,我们计划逐步驱逐它,直到它全部消失。https://groups.google.com/forum/#!topic/scala-internals/TtCTPlj_qcQ https://groups.google.com/forum/#!topic/scala-internals/TtCTPlj_qcQ(几天前也发布过)是朝这个方向迈出的第一步。请继续关注今年也可能推出的其他好东西!
实用部分
在通过详细阐述所有细节并暗示毫无作用的神秘案例彻底吓到您之后,我想指出的是,在使用宏时通常不需要了解此类内容。通常,无类型树(手动构造的 AST、准引号)和类型树(宏参数)都可以正常工作。
在某些情况下,当 scalac 想要特定的树味时,它会告诉你喜欢c.eval
或者有时会在你面前崩溃(RefChecks、LambdaLift 和 GenICode 崩溃是树在宏扩展期间混合在一起的巨大指标 - 在这些情况下使用resetLocalAttrs
如中所述https://groups.google.com/forum/#!msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ https://groups.google.com/forum/#!msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ)。解决这个问题是我的首要任务,我现在正在努力解决这个问题。修复可能会进入 2.11.0,这个答案很快就会过时:)