HIR
HIR ——“高级中间表示” ——是大多数 rustc 组件中使用的主要IR。
它是抽象语法树(AST)的对编译器更为友好的表示形式,该结构在语法分析,宏展开和名称解析之后生成(有关如何创建HIR,请参见Lowering)。
HIR 的许多部分都非常类似于普通 Rust 的语法,但是 Rust 中的的某些表达式已被“脱糖”。
例如,for
循环将转换为了 loop
,因此在HIR中不会出现 for
。 这使HIR比普通AST更易于分析。
本章介绍了HIR的主要概念。
您可以通过给 rustc 传递 -Zunpretty=hir-tree
标志来查看代码的 HIR 表示形式:
cargo rustc -- -Zunpretty=hir-tree
Out-of-band 存储和Crate
类型
HIR中的顶层数据结构是 Crate
,它存储当前正在编译的 crate 的内容(我们从来就只为当前 crate 构造 HIR)。
在 AST 中,crate 数据结构基本上只包含根模块,而 HIR Crate
结构则包含许多map 和其他用于组织 crate 内容以便于访问的数据。
例如,HIR 中单个项目(例如模块、函数、trait、impl等)的内容不能在其父级中直接访问。
因此,例如,如果有一个包含函数 bar()
的模块 foo
:
#![allow(unused)] fn main() { mod foo { fn bar() { } } }
那么在模块 foo
的HIR中表示(Mod
结构)中将只有bar()
的**ItemId
** I
。
要获取函数 bar()
的详细信息,我们将在 items
映射中查找 I
。
这种表示形式的一个好处是,可以通过遍历这些映射中的键值对来遍历 crate 中的所有项目(而无需遍历整个HIR)。 对于 trait 项和 impl 项以及“实体”(如下所述)也有类似的map。
使用这种表示形式的另一个原因是为了更好地与增量编译集成。
这样,如果您访问 &rustc_hir::Item
(例如mod foo
),不会同时立即去访问函数bar()
的内容。
相反,您只能访问 bar()
的id,必须将 id 传入某些函数来查找 bar
的内容。 这使编译器有机会观察到您访问了bar()
的数据,然后记录依赖。
HIR 中的标识符
有许多不同的标识符可以引用HIR中的其他节点或定义: 简单来说有:
DefId
表示对任何其他 crate 中的一个定义的引用。LocalDefId
表示当前正在编译的 crate 中的一个定义的引用。HirId
表示对 HIR 中任何节点的引用。
更多详细信息,请查看有关标识符的章节。
HIR Map
在大多数情况下,当您使用HIR时,您将通过 HIR Map 进行操作,该map可通过tcx.hir()
在tcx中访问(它在hir::map
模块中定义)。
HIR map 包含多个方法,用于在各种 ID 之间进行转换并查找与 HIR 节点关联的数据。
例如,如果您有一个 DefId
,并且想将其转换为 NodeId
,则可以使用 tcx.hir().as_local_node_id(def_id)
。
这将返回一个 Option<NodeId>
—— 如果 def-id 引用了当前 crate 之外的内容(因为这种内容没有HIR节点),则将为None
;
否则这个函数将返回 Some(n)
,其中 n
是定义对应的节点ID。
同样,您可以使用tcx.hir().find(n)
在节点上查找NodeId
。
这将返回一个Option<Node<'tcx>>
,其中Node
是在map中定义的枚举。
通过对此枚举进行 match ,您可以找出 node-id 所指的节点类型,并获得指向数据本身的指针。
一般来说,您已经事先知道了节点 n
是哪种类型——例如,如果您已经知道了 n
肯定是某个 HIR 表达式,
则可以执行tcx.hir().expect_expr(n)
,它将试图提取并返回&hir::Expr
,此时如果n
实际上不是一个表达式,那么会程序会 panic。
最后,您可以通过 tcx.hir().get_parent_node(n)
之类的调用,使用HIR map来查找节点的父节点。
HIR Bodies
rustc_hir::Body
代表某种可以执行的代码,例如函数/闭包的函数体或常量的定义。
body 与一个所有者相关联,“所有者”通常是某种Item(例如,fn()
或const
),但也可以是闭包表达式(例如, |x, y| x + y
)。
您可以使用 HIR 映射来查找与给定 def-id(maybe_body_owned_by
)关联的body,或找到 body 的所有者(body_owner_def_id
)。