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)。