28个版本 (12个破坏性更新)
新版本 0.25.0 | 2024年8月23日 |
---|---|
0.23.1 | 2024年8月6日 |
0.22.1 | 2024年7月28日 |
1195 在 编程语言
4,751 每月下载量
用于 4 个crate(2个直接使用)
2.5MB
62K SLoC
具有从访问者读取树的能力的AST遍历。
请参阅traverse_mut
了解API的解释。
实现细节
这个crate中的大部分代码都是通过代码生成器生成的。代码生成器目前是用JavaScript编写的(scripts/build.mjs
)。
不要编辑这些文件,因为它们将在下一次运行时被构建脚本覆盖。
允许读取树的方案是基于使违反Rust的别名规则在静态上成为不可能的。
Rust的别名规则大致如下
- 对于任何对象,你可以同时拥有任意数量的不可变
&
引用。 - 对于任何对象,如果你有对该对象的任何其他引用(不可变或可变),则无法获得对该对象的可变
&mut
引用。 &/
覆盖了对象本身及其下面的整个树。即你不能同时持有对子节点的可变引用和对父节点的任何引用(除了通过“重新借用”)。&mut
ref
这在修改访问者中读取树时提出了问题。在访问者中,你持有对节点的可变引用,因此无法获得对其父节点的&
引用,因为父节点拥有该节点。如果你持有对父节点的&
引用,这也充当了当前节点的&
引用 = 同时持有对同一个节点的&
和&mut
引用。灾难!
这个crate使用的解决方案是
- 在遍历AST的
walk_*
函数中不要创建引用。请使用原始指针。只在enter_*
/exit_*
方法中创建&mut
引用。 - 不允许
enter_*
/exit_*
访问其整个父节点或祖先,仅允许访问从父节点或祖先出发的树的其他分支。可以通过ctx.parent()
等方式访问的当前节点以上的树的部分 不与 通过&mut
引用在enter_*
/exit_*
中可访问的树的部分重叠。这使得同时获得相同AST节点的两个引用变得不可能。 - 再次提醒,在获取祖先的字段时不要创建瞬态引用。在创建
&
引用之前,使用原始指针直接访问字段。
此机制的实现是 Ancestor
枚举。每个AST节点类型都有几个 Ancestor
变体,例如 ProgramWithoutDirectives
、ProgramWithoutHashbang
、ProgramWithoutBody
。正如名称所暗示的,这些类型中的每一个都允许访问 Program
的所有字段,只有一个例外。
当 walk_*
函数遍历树时,它们会将适当的类型添加到 Ancestors
栈中,以防止访问正在遍历的路径。
walk_*
使用 TraverseCtx::retag_stack
来尽可能便宜地更新祖先栈,但这纯粹是性能优化,不是方案安全性的关键。
安全性
该crate包含大量不安全代码。整个 walk.rs
文件都是使用原始指针的不安全函数。
为了避免在编译时消耗时间解析1000多个注释,代码生成的文件不包含解释每个不安全操作的注释。但一切均按照上述原则进行。
几乎所有代码都是通过代码生成。我 (@overlookmotel) 建议继续仅使用代码生成,而不是手动编辑这些文件以处理“特殊情况”。安全方案很容易因为一个错误而完全偏离,所以在我看来,手动编辑是不明智的。
依赖项
~6–12MB
~121K SLoC