Rust 编译器 HIR 类型检查深度解析:类型收集(Type Collection)与函数体类型检查的实现
Rust 编译器 HIR 类型检查深度解析类型收集Type Collection与函数体类型检查的实现【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文基于 rustc 开发指南中 HIR Type checking 章节讲解 Rust 编译器前端最核心的两个阶段把 HIR 中用户书写的hir::Ty语法类型转换为编译器内部表示Tytcx的类型收集Collection以及在rustc_hir_typeck中完成的函数体类型检查Type Checking。读完后你将理解这两阶段为什么按 crate 与查询query拆分、rustc_hir_analysis中type_of/generics_of/clauses_of等查询各自负责什么以及类型检查阶段中类型推断、强制转换Coercion与方法查找Method Lookup在源码中的落点。两个 crate、两条职责线rustc 开发指南将 HIR 阶段的类型处理拆分为两个 crate见 章节原文rustc_hir_analysis包含 type collection类型收集及其相关功能源码位于 compiler/rustc_hir_analysis其中收集逻辑集中在 collect.rs 及其子模块rustc_hir_typeck实现函数**体body**的检查即真正的 type checking源码位于 compiler/rustc_hir_typeck。这两个 crate 都大量依赖 类型推断type inference 与 trait 求解trait solving 这两套基础设施这也是 rustc 前端能边检查边推断的根本原因。从rustc_hir_typeck的源码目录可以直观看到函数体检查覆盖面的宽度expr.rs 负责表达式检查cast.rs、callee.rs、closure.rs、loops.rs、pat.rs、_match.rs 分别处理对应的语法结构而 coercion.rs、expectation.rs、method 目录则对应下文的强制转换与方法查找check.rs 是整个 item 级检查的入口fn_ctxt 目录中的FnCtxt是检查单个函数体时的上下文对象。类型收集从hir::Ty到Tytcx类型收集的核心任务在 原文 中定义得很清楚Type collection is the process of converting the types found in the HIR (hir::Ty), which represent the syntactic things that the user wrote, into the internal representation used by the compiler (Tytcx).即把 HIR 中的hir::Ty——它只表示用户写了什么的语法层类型——转换为编译器内部表示Tytcx。同样的转换也作用于 where 子句及函数签名中的其他成分。原文用一个最小示例展示了二者的差别struct Foo { } fn foo(x: Foo, y: self::Foo) { ... } // ^^^ ^^^^^^^^^两个参数x和y的类型其实是同一个但它们在 HIR 中是两个不同的hir::Ty节点span 不同路径的编码方式也不同一个是裸路径Foo一个是限定路径self::Foo。而当这两个节点被收集为Tytcx之后它们会被表示为完全相同的内部类型——路径差异在 lowering 后消失只剩下类型本身。collect.rs 开头的模块文档注释与开发指南的表述完全一致可以把它当作权威定义Collection is the process of determining the type and other external details of each item in Rust. Collection is specifically concerned withinter-proceduralthings -- for example, for a function definition, collection will figure out the type and signature of the function, but it will not visit thebodyof the function in any way, nor examine type annotations on local variables (thats the job of type checking).这里有两个关键点值得展开收集是过程间inter-procedural的。对于函数定义收集阶段会确定该函数的类型和签名但完全不进入函数体也不检查局部变量的类型标注——那是类型检查type checking的工作。这条职责边界正是两个 crate 拆分的原因收集产出的信息类型、泛型参数、where 子句可以被其他 item 引用因此必须先行可用而函数体检查只针对单个函数。收集是一组查询queries的集合。原文指出Collection is defined as a bundle of queries for computing information about the various functions, traits, and other items in the crate being compiled并建议参见 查询系统章节。源码印证collect模块的查询族在 collect.rs 的provide函数中可以看到查询集合的具体形态——它向 rustc 的查询引擎注册了一批提供函数providertype_ofitem 的类型函数得到FnDef类型ADT 得到其 ADT 类型等generics_of、clauses_of及其explicit_*变体泛型参数集合与 where 子句item_bounds、item_self_bounds、item_non_self_boundsitem 的约束条件trait_def、adt_def、fn_sigtrait 定义、ADT 定义与函数签名const_of_item、const_param_default、coroutine_kind等。这些查询各自独立、按需求值是 rustc 查询驱动query-driven架构的典型体现收集不再是传统意义上扫一遍所有 item的单向 pass而是由使用方按需拉取各 item 的对外可见信息。原文也提到collect模块是这一过程的详细出处。type_of的实现位于 collect/type_of.rs。从源码结构看type_of(tcx, def_id)先处理 RPITITtrait 中返回位置impl Trait等特殊情形然后构造ItemCtxt并按 HIR 节点类型分发trait item / impl item 中的函数会构造Ty::new_fn_def即绑定到该DefId的函数定义类型配合 late-bound 变量绑定器常量、关联类型、static等则通过ItemCtxt内部的HirTyLowerer定义在 hir_ty_lowering.rs把hir::Ty逐节点 lower 为Tytcx遇到可推断类型suggable infer type例如static mut X: _时还会调用infer_placeholder_type从常量体反推占位类型。这正是同一个self::Foo与Foo最终收敛到同一个内部类型这一行为的落点lower 过程解析路径、消解泛型实参后输出只保留类型语义本身。collect目录下的其余子模块同样对应原文所说的related functionalitygenerics_of.rs泛型参数收集、clauses_of.rswhere 子句收集、item_bounds.rsitem 约束、resolve_bound_vars.rs绑定变量解析以及 type_of/opaque.rs不透明类型的 lowering。函数体类型检查rustc_hir_typeck的职责原文的 HIR Type checking 章节以一句 TODO 收尾actually talk about type checking对应 rustc-dev-guide 的 issue #1161即原文本身主要成文于类型收集部分。为完整覆盖该主题下面结合同目录的 Coercions 与 Method lookup 两篇姊妹文档以及rustc_hir_typeck源码补全类型检查阶段的核心内容。类型检查阶段处理原文中明确排除在收集之外的那些过程内事务函数体内的表达式、局部变量标注、模式、控制流等。检查在FnCtxt函数检查上下文fn_ctxt 目录中进行它持有推断上下文与正在累积的TypeckResults。其中两个最复杂的机制是强制转换与方法查找。强制转换Coercionsone-to-one 与 LUB 两类转换点强制转换是把值隐式转换为另一种类型的操作**转换点coercion site**是允许隐式转换发生的语法位置。rustc 中转换点分两类见 coercions.mdlet one_to_one_coercion: u32 mut 8; // one-to-onemut u32 - u32 let lub_coercion match my_bool { true mut 10, false 12, // LUBmut i32 与 i32 统一收敛为 i32 };one-to-one 转换从一个已知源类型转换到已知目标类型调用FnCtxt::coerce即可实现在 coercion.rs 的Coerce相关逻辑中如coerce_to_ref、coerce_unsized、coerce_from_fn_item等方法。LUBLeast-Upper-Bound最小上界转换把一组源类型转换为一个尚且未知的目标类型并且由转换过程本身产生这个目标类型。通用三步流程let mut coerce CoerceMany::new(initial_lub_ty); // 1. 创建 CoerceMany选定初始 LUB for expr in exprs { let expr_ty fcx.check_expr_with_expectation(expr, expectation); coerce.coerce(fcx, cause, expr, expr_ty); // 2. 逐个检查表达式并登记其类型 } let final_ty coerce.complete(fcx); // 3. 完成 LUB得到最终类型三步分别对应 coercion.rs 中CoerceMany结构的new/coerce/complete方法CoerceMany存储 LUB 转换所需的全部状态。细节要点初始 LUB 来自Expectation创建CoerceMany需要一个initial_lub它来自表达式的类型期望Expectation定义在 expectation.rs使得 LUB 计算中的推断约束能传播给后续被检查的表达式没有可用期望时就新建一个推断变量。若期望本身就是推断变量则不直接用它作为初始 LUB而是另建推断变量?y——否则中间表达式会过早地把?x约束为第一个分支的类型例如FnDef(a)最终统一?x eq fn() - ()时就会失败。没有 HIR 节点的情况例如不带操作数的break/return需要让()参与 LUB此时用CoerceMany::coerce_forced_unit。try_find_coercion_lub是给两个类型算出新 LUB 类型的核心逻辑FnCtxt的方法有三条路径把当前 LUB 与新类型都转为函数指针、把其一转换为另一个、或计算两者的共同超类型。例如match三分支foo / foo / bar的 LUB 过程第一步特殊处理直接把第一个分支FnDef(Foo)强转为初始推断变量?x推出?x FnDef(Foo)第二、三步再依次与FnDef(Foo)、FnDef(Bar)计算 LUB最终得到fn() - ()。use_lub字段one-to-one 转换的实现被 LUB 转换复用Coerce结构上的use_lub字段切换行为——one-to-one 场景在强转失败时回退到普通子类型判定而 LUB 场景必须计算共同超类型use_lub置位时。共同超类型的计算使用 rustc_infer 中的特殊类型关系LatticeOp它在遇到高阶类型HRTB时切换为不变性invariance强制两个高阶类型的绑定器等价从而避免选最泛绑定器这一难题并保证 LUB 计算不依赖求值顺序。转换结果落地为 adjustments每次成功强转都会向进行中的TypeckResults记录一系列adjustments如 unsize、autoderef。构建 THIR 时这些 adjustments 被展开为显式步骤此后再无强制转换概念MIR 中只有显式 cast 与子类型关系。回退到子类型FnCtxt::coerce与CoerceMany::coerce在强转失败后都会尝试子类型判定one-to-one 尝试源是目标的子类型LUB 尝试计算共同超类型因此强转失败后无需再单独尝试子类型。never 类型的特殊性从!强转到推断变量会得到一个NeverToAny转换目标即该推断变量这与把推断变量统一为!有微妙差别——统一要求?x真的等于!而NeverToAny允许?x被推断为任意类型。因此当 LUB 的初始类型是推断变量时必须走强转而不能用子类型。探针probe注意事项强转有副作用会写入 adjustments不能被探针回滚。在probe内调用FnCtxt::coerce是错误的commit_if_ok包裹coerce且成功后不回滚才是正确用法CoerceMany绝不应出现在probe/commit_if_ok内部。方法查找Method LookupProbe 与 Confirm 两阶段receiver.method(...)形式的调用本质上被转换为等价的完全限定调用trait 方法为Trait::method(ADJ(receiver), ...)固有方法为ReceiverType::method(ADJ(receiver), ...)其中ADJ通常是一系列 autoderef 之后可能再 autoref的调整如**receiver有时还包含 unsizing 等强转如[T; n]到[T]。整个查找分两个阶段见 method-lookup.md实现在 method 目录Probe探测决定调用哪个方法、接收者如何调整。探测阶段产出一个Pick结果刻意不含推断变量等状态以便在多个调用点之间缓存复用。Confirm确认把探测结果应用下去——更新 side table、统一类型变量即所有带副作用的动作都发生在此阶段。探测阶段内部又分三步Steps 生成对接收者类型逐步解引用直到不可再解引用并追加一个可选的 unsize 步。例如RcBox[T; 3]产生RcBox[T; 3] → Box[T; 3] → [T; 3] → [T]。候选收集沿各 step 收集Candidate分两类。**固有候选inherent**来自接收者类型本身的impl无需 import且固有 impl 只能定义在类型所在 crate 内**扩展候选extension**来自已导入 trait 的 impl即 extension method。候选搜索沿 steps 向下逐个匹配每个 step 同时尝试 autoref 与 auto-mut-ref 两种接收者形态对每种形态先固有候选后扩展候选。同组内多个候选匹配时报告错误同一 trait 的多个 impl 视为一次匹配否则取第一个命中者。命中后还要递归检查 impl 上的 where 子句若匹配或无法排除匹配则选定该方法否则继续向下探测。小结与延伸入口回到 原文 的核心结论rustc 把 HIR 阶段的类型工作切分为过程间的类型收集rustc_hir_analysis的collect查询族type_of、generics_of、clauses_of、item_bounds等把hir::Tylower 为Tytcx与过程内的函数体检查rustc_hir_typeck的FnCtxt驱动含强制转换与方法查找两大机制。原文对实际展开讲 type checking留了 TODO而仓库中同目录的 Coercions 与 Method lookup 已经承担了这部分内容并与 compiler/rustc_hir_typeck/src/coercion.rs、compiler/rustc_hir_typeck/src/method 的源码相互印证。如需继续深入建议按以下顺序阅读仓库文档query 系统理解收集查询如何被按需求值、type inference理解检查过程中的变量统一、trait resolution理解候选匹配中 where 子句检查背后的 trait 求解。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →