ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

rustc 与 rust-analyzer 共享 trait solver:rustc_type_ir 抽象层与 rustc_next_trait_solver 源码级剖析

rustc 与 rust-analyzer 共享 trait solver:rustc_type_ir 抽象层与 rustc_next_trait_solver 源码级剖析 rustc 与 rust-analyzer 共享 trait solverrustc_type_ir 抽象层与 rustc_next_trait_solver 源码级剖析【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读本文基于本仓库Rust 编译器源码树中的 dev-guide 文档 sharing-crates-with-rust-analyzer.md系统讲解 rustc 与 rust-analyzer 如何通过一组共享的rustc_*crate 复用新一代 trait solvernext trait solver实现避免两套前端的类型推导与 trait 求解逻辑重复实现。读完本文你将掌握rustc_type_ir中Interner、inherent、InferCtxtLike、SolverDelegate、search_graph等核心抽象的设计动机与具体形态理解 rust-analyzer 如何以 stable 工具链接入 nightly 专属代码并能定位到本仓库中对应的源码与文档文件进一步钻研。rust-analyzer 为什么需要共享 rustc 的 craterust-analyzer 本质上是一个编译器前端它执行与 rustc 代码生成之前阶段类似的工作包括解析parsing、词法分析lexing、AST 构建与降级lowering、HIR 降级甚至还有受限的 MIR 构建与常量求值。然而由于 rust-analyzer 首先是语言服务器language server其架构与 rustc 存在若干重要差异——它必须处理频繁变化的源码、部分无效或不完整的代码因此介于源码与 HIR 之间的基础设施例如Ty及其背后的 interner与 rustc 并不相同。即便如此两者职责中仍有相当大一部分是重叠的最典型的就是类型推断与 trait 求解。为了避免重复实现、并保持两个实现之间行为一致rust-analyzer 直接复用了 rustc 的多个 crate尽可能依赖共享抽象。共享 crate 清单与发布管道目前 rust-analyzer 依赖以下来自编译器的 craterustc_abirustc_ast_irrustc_indexrustc_lexerrustc_next_trait_solverrustc_parse_formatrustc_pattern_analysisrustc_type_ir这些 crate 并不随编译器常规分发流程发布到crates.io因此 rust-analyzer 维护了独立的发布管道通过 rustc-auto-publish 脚本外部项目见文档中[rustc-auto-publish]链接将这些 crate 以ra-ap-rustc_*前缀发布到crates.io例如ra-ap-rustc_next_trait_solverrust-analyzer 在自己的构建中依赖这些重新发布的 crate。对于 trait 求解而言两个最核心的共享 crate 是rustc_type_ir本仓库路径 compiler/rustc_type_ir定义共享 IR 与抽象接口rustc_next_trait_solver本仓库路径 compiler/rustc_next_trait_solver提供求解器核心逻辑。其中rustc_next_trait_solver的 crate 文档compiler/rustc_next_trait_solver/src/lib.rs也注明该 crate 包含新一代 trait solver 的实现其中也可能包含在将新 solver 泛型化过程中从旧 solver 提升uplift出来的内容。抽象层rustc_type_ir由于 rustc 与 rust-analyzer 在源码与 HIR 之间的基础设施差异巨大编译器提供了rustc_type_ir作为两者共享的抽象层。该 crate 定义了表示类型types、谓词predicates以及 trait solver 所需上下文的基本接口rustc 与 rust-analyzer 各自为自己的具体类型表示实现这些 trait而rustc_next_trait_solver则被编写为对这些抽象保持泛型。除了接口本身rustc_type_ir还包含若干建立在抽象层之上的非平凡组件例如elaboration 逻辑见 compiler/rustc_type_ir/src/elaborate.rs以及 solver 使用的search graph 机制见 compiler/rustc_type_ir/src/search_graph。设计目标是rustc_next_trait_solver只依赖rustc_type_ir中定义的抽象接口。为此rustc_type_ir中的类型系统 trait 必须暴露 solver 所需的每一个接口——例如创建新的推断类型变量new_infer在 compiler/rustc_type_ir/src/inherent.rs 的trait Ty中可见fn new_infer(interner: I, var: ty::InferTy) - Self。对于不需要编译器特定表示的条目rustc_type_ir直接将其定义为参数化于这些 trait 之上的结构体或枚举——例如TraitRef。从源码结构看rustc_type_ir的内容覆盖极广compiler/rustc_type_ir/src 下包含interner.rsInternertrait 本体、inherent.rs前向定义、infer_ctxt.rsInferCtxtLike、ir_print.rsIrPrint、fold.rs/lift.rs/visit.rs/generic_visit.rs派生宏与遍历基础设施、elaborate.rs、search_graph/Cx/Delegate/GlobalCache/Stack、solve/inspect、Goal、QueryResult、Certainty等求解器数据类型以及relate/、canonical.rs、binder.rs等。核心设计概念逐个拆解trait Interner一切实现差异的汇合点Interner是整个设计中的核心 trait本仓库源码 compiler/rustc_type_ir/src/interner.rs。它规定了 rustc 与 rust-analyzer 各自的所有实现特定细节。其职责包括通过关联类型指定实现所用的具体类型。这是每个编译器前端实例化共享 IR 的骨干。从源码看关联类型数量庞大且高度细分DefId/LocalDefId以及针对不同实体的TraitId、ForeignId、FunctionId、ClosureId、CoroutineId、AdtId、ImplId、OpaqueTyId等注释明确说明rustc 中这些全部定义为DefId而 rust-analyzer 使用不同类型还有GenericArgs、GenericArg、Term、BoundVarKinds、ParamEnv、Span、CanonicalInput等见 compiler/rustc_type_ir/src/interner.rs 一带。为 solver 提供所需上下文例如查询 lang itemsrequire_lang_item、枚举某个 trait 的所有 blanket implfor_each_blanket_impl等。必须实现IrPrint以支持格式化与 trace。在实践中这些IrPrint实现只是路由到 rustc 或 rust-analyzer 内部已有的格式化逻辑。IrPrinttrait 定义在 compiler/rustc_type_ir/src/ir_print.rsInterner要求对AliasTy、TraitRef、TraitClause、ProjectionClause、NormalizesTo、FnSig等一系列共享类型都实现IrPrint。在 rustc 中TyCtxt实现了Interner见 compiler/rustc_middle/src/ty/context/impl_interner.rs它暴露 rustc 的查询方法而所需的Internertrait 方法通过调用这些查询来实现。在 rust-analyzer 中实现类型名为DbInterner因为其大部分 interning 通过 salsa 数据库完成其大多数方法由 salsa 查询而非 rustc 查询支撑。另外值得注意的是Interner带有#[cfg_attr(feature nightly, rustc_diagnostic_item type_ir_interner)]属性且next_trait_solver_globally默认返回true——这体现了新 solver 全局启用的方向。mod inherent以 trait 模拟固有方法rustc_type_ir中另一个重要组件是inherent模块compiler/rustc_type_ir/src/inherent.rs。该模块以trait 形式提供固有方法的前向定义forward definitions对应存在于编译器特定类型如Ty、GenericArg上的固有方法。这些定义使得泛型 crate如rustc_next_trait_solver可以调用在 rustc 与 rust-analyzer 中实现方式不同的方法。例如trait TyI: InternerTy Self中定义了大量前向方法new_unit、new_bool、new_u8、new_usize、new_infer、new_var、new_param、new_placeholder、new_bound、new_alias、new_projection等并约束Ty需实现Copy Debug Hash Eq、IntoI::GenericArg、IntoI::Term、RelateI、Flags以及TypeSuperVisitable/TypeSuperFoldable等。泛型 crate 中的代码应这样导入这些定义use inherent::*;这些前向定义绝不能在具体实现内部使用。实现mod inherent中 trait 的 crate在具体类型可被命名之后应当调用具体类型上真正的固有方法。rustc 对这些 trait 的实现位于rustc_middle::ty::inherent模块rust-analyzer 的对应实现则分散在hir_ty::next_solver下的多个模块中例如hir_ty::next_solver::region。trait InferCtxtLike 与 trait SolverDelegate拆分 InferCtxt这两个 trait 共同对应 rustc 中InferCtxt的角色由于coherence孤儿规则约束而必须拆成两个InferCtxtLikecompiler/rustc_type_ir/src/infer_ctxt.rs必须定义在rustc_infer中。因此它无法提供位于rustc_trait_selection中的功能。其方法包括cx()返回Interner、next_trait_solver()默认true、universe()/create_next_universe()、placeholder 假设的插入与获取、get_solver_region_constraint以及TypingMode相关接口等。依赖 trait 求解逻辑的行为被抽象到另一个 traitSolverDelegatecompiler/rustc_next_trait_solver/src/delegate.rs中。它在 rustc 中的实现只是一个InferCtxt的 newtype 包装pub struct SolverDelegatetcx(InferCtxttcx);见 compiler/rustc_trait_selection/src/solve/delegate.rs。SolverDelegate的方法包括build_with_canonical、compute_goal_fast_path、fresh_var_for_kind、leak_check源码中留有 FIXME把 leak check 提升进本 crate、evaluate_const、well_formed_goalsFIXME 注明仅因wf::obligations位于rustc_trait_selection才放在这里、make_deduplicated_region_constraints、instantiate_canonical、add_item_bounds_for_hidden_type、fetch_eligible_assoc_item、is_transmutable、obtain_canonicalizer_state/release_canonicalizer_state默认实现支持 canonicalizer state 的复用与释放属于性能优化钩子以及emit_next_solver_overflow_fcw。在 rust-analyzer 中SolverDelegate同样为自己的InferCtxt实现于一个 newtype 包装上主要是为了镜像 rustc 的结构这并非严格必要因为所有与 solver 相关的逻辑已经位于hir-tycrate 中。长期理想设计是把目前通过SolverDelegate表达的逻辑全部移入rustc_next_trait_solver所需的核心操作直接加到InferCtxtLike上从而让更多 solver 行为完全落在共享 solver crate 内部。rustc_type_ir::search_graph::{Cx, Delegate}抽象 traitCx与Delegate已经在rustc_next_trait_solver内部实现因此共享 crate 的使用方rustc 与 rust-analyzer都无需提供自己的实现。从源码看Cxcompiler/rustc_type_ir/src/search_graph/mod.rs要求Copy关联类型有Input/Result/AmbiguityKind均要求Debug Eq Hash Copy、DepNodeIndex、TrackedT方法包括mk_tracked/get_tracked/with_cached_task依赖节点跟踪、with_global_cache以及assert_evaluation_is_concurrent等。Delegate在rustc_next_trait_solver中通过一个永不构造的哨兵类型SearchGraphDelegateD为所有实现SolverDelegate的类型统一实现compiler/rustc_next_trait_solver/src/solve/search_graph.rs并给出ENABLE_PROVISIONAL_CACHE true、FIXPOINT_STEP_LIMIT、DIVIDE_AVAILABLE_DEPTH_ON_OVERFLOW 4等常量以及不同路径类别Coinductive/Inductive/Unknown/ForcedAmbiguity下初始 provisional 结果、栈溢出结果、fixpoint 溢出结果的具体语义例如 coherence 模式下 inductive 环返回 overflow 而非NoSolution相关测试参见仓库tests/ui/traits/next-solver/cycles/unproductive-in-coherence.rs源码注释中有引用。这些 trait 存在的主要目的是独立于完整 trait solver 对 search graph 进行 fuzzing。这套基础设施被外部模糊测试项目lcnr/search_graph_fuzz使用。派生宏体系减少样板代码rustc_type_ir大量使用以下派生 trait 及其宏它们主要用于减少实现Lift、TypeFoldable、TypeVisitable、GenericTypeVisitable所需的样板代码trait TypeVisitable与TypeVisitable_Generictrait TypeFoldable与TypeFoldable_Generictrait Lift与Lift_Generictrait GenericTypeVisitableTypeVisitable 与 TypeVisitable_GenericTypeVisitable要求结构体或枚举实现visit_with(...)方法将控制权转交给TypeVisitor详细实现见 compiler/rustc_type_ir/src/visit.rs。尽管名字相似TypeVisitable_Generic与GenericTypeVisitable实现的是两套不同的遍历系统TypeVisitable_Generic在Interner上泛型地派生普通的TypeVisitabletraitGenericTypeVisitable派生另一套独立的GenericTypeVisitabletrait供非 nightly 消费方如 rust-analyzer使用。TypeVisitable_Generic 的遍历语义它按声明顺序遍历值的字段将每个字段委托给该字段自身的TypeVisitableI实现如果 visitor 返回 residual 结果遍历可以提前终止。使用#[type_visitable(ignore)]忽略某个字段——该字段不会参与遍历也不需要实现TypeVisitableI。这只能在字段确实无需被遍历时使用。仓库源码中可见其实际用法例如 compiler/rustc_type_ir/src/error.rs 中对PolarityMismatch、SafetyMismatch、AbiMismatch中的ExpectedFound、I::Safety、ExternAbi字段的忽略。TypeFoldable 与 TypeFoldable_GenericTypeFoldable由需要嵌入类型的对象实现该概念在 dev-guide 的 ty-fold.md 中有详细讨论源码见 compiler/rustc_type_ir/src/fold.rs。TypeFoldable_Generic为结构体或枚举派生rustc_type_ir::TypeFoldableI。它消费一个值在折叠其字段后重建同一结构体或同一枚举变体同时生成 fallible 与 infallible 两种折叠方法。对于枚举生成的 match 为每个变体生成一个重建分支。使用#[type_foldable(identity)]标记必须原样保留的字段宏将该字段直接移入重建后的值而不交给 folder因此该字段类型无需实现TypeFoldableI。仓库中可见示例如 compiler/rustc_type_ir/src/binder.rs 中的用法。Lift 与 Lift_GenericLifttrait 有一个方法lift_to_interner(...)顾名思义它将某个东西提升lift到 interner 中。关于 interner 的更多讨论见 dev-guide 的 memory.md源码见 compiler/rustc_type_ir/src/lift.rs。Lift_Generic宏为结构体或枚举派生Lift有三个不那么直观的注意事项泛型参数I与J是保留的I: InternerJ是要提升到的目标 interner。PhantomData被自动处理会创建新的PhantomData但它必须以完全非限定形式使用——不能在字段中直接写std::marker::PhantomData。约束刻意写成Interner上的关联类型约束而非LiftInto上的where子句给定I: LiftIntoJRust 便可将以下约束视为蕴含关系I::Ty: LiftJ, Lifted J::Ty I::Const: LiftJ, Lifted J::Const这样Lift_Generic在调用I::Ty、I::Const及其他已声明关联类型字段上的lift_to_interner时只需发出I: LiftIntoJ约束同时保证每次调用都产出 derive 将I::Assoc改写为J::Assoc后期望的目标字段类型。declare_lift_into!宏compiler/rustc_type_ir/src/interner.rs正是用于声明哪些Interner关联类型可以被 lift——它生成LiftIntoJtrait 及其 blanket 实现将个别关联类型的Lift约束聚合起来。从仓库看declare_lift_into!的调用覆盖了BoundVarKinds、Const、DefId、GenericArg、GenericArgs、ParamEnv、Term、TraitId、Ty等二十余项compiler/rustc_type_ir/src/interner.rs 一带。如果没有它derive 需要为每个字段用到的每个 interner 关联类型单独生成约束任何期望与Lift_Generic配合工作的新Interner关联类型都需要相应的Lift实现且通常需要加入declare_lift_into!调用。如果某个字段无法被 lift例如u32这样的基本类型可用#[lift(ignore)]跳过该字段。GenericTypeVisitable这是一套独立的、更通用的遍历 trait仅供 rust-analyzer 使用。与TypeVisitable不同visitor 类型是 trait 的参数而非方法的参数且遍历既不返回结果、也不支持短路。因此一个结构体或枚举可以同时 deriveTypeVisitable_Generic与GenericTypeVisitable。它刻意不提供 ignore 属性——遍历必须访问每个字段。这是 rust-analyzer 在 tracing 与垃圾回收 interned 类型时使用该遍历的健全性要求相关说明见 compiler/rustc_type_ir/src/generic_visit.rs 顶部注释。长期规划对 rust-analyzer 的持续支持整体目标是在共享 crate 中把 rust-analyzer 支持到与 rustc 同等的水平——前提是不实质性损害 rustc 的性能或可维护性文档中引用了相关 PR例如 #145377、#146111、#146182、#147723。一个关键技术约束是需要 nightly 特性的共享 crate 代码必须用nightlyfeature flag 保护因为 rust-analyzer 使用 stable 工具链构建。本仓库源码中也随处体现了这一约定例如 compiler/rustc_type_ir/src/interner.rs 中rustc_data_structures::stable_hash::StableHash的导入、#[cfg_attr(feature nightly, rustc_diagnostic_item ...)]属性以及 compiler/rustc_type_ir/src/infer_ctxt.rs 中Encodable_NoContext/Decodable_NoContext/StableHash_NoContext、rustc_must_match_exhaustively等派生均带feature nightly门控。展望未来计划将更多共享逻辑提升uplift进rustc_type_ir。目前 rustc 与 rust-analyzer 之间仍存在重复实现例如ObligationCtxtrustc 侧位于compiler/rustc_trait_selection/src/traits/engine.rsrust-analyzer 侧位于hir_ty::next_solver::obligation_ctxt以及类型强制转换coercion逻辑rustc 侧位于compiler/rustc_hir_typeck/src/coercion.rs这些都有望在将来逐步统一。如何继续深入阅读 dev-guide 中关于 trait solver 的更完整章节目录 src/doc/rustc-dev-guide/src/solve 下的相邻文档如ty-fold.md、memory.md分别讲解折叠与 interner 内存模型。深入共享抽象层源码compiler/rustc_type_ir/src 的interner.rs、inherent.rs、infer_ctxt.rs、ir_print.rs、fold.rs、lift.rs、visit.rs、generic_visit.rs与search_graph/、solve/子目录。深入求解器实现compiler/rustc_next_trait_solver/src 的delegate.rs、solve/含eval_ctxt/、assembly/、search_graph.rs、trait_goals.rs、normalizes_to.rs等以及canonical/、coherence.rs、normalize.rs。查看 rustc 侧的两个关键实现点TyCtxt 的 Interner 实现 与 SolverDelegate 的 newtype 包装。在tests/ui/traits/next-solver/目录下本仓库 tests/ui 中查找新 solver 的测试用例验证 search graph 环处理等行为。这套共享抽象层 各自实现的架构让两个独立演进的前端在类型系统这一最复杂的部分保持行为一致同时为后续把更多求解逻辑下沉到共享 crate 铺平了道路。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表