
rustc 编译器 LintStore 深度解析Lint 注册机制与 lint pass 运行原理【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文围绕 rustc 编译器诊断体系的核心基础设施LintStore展开基于 src/doc/rustc-dev-guide/src/diagnostics/lintstore.md 的系统阐述并结合 rustc 源码深入剖析 lint 声明与 lint pass 的二元结构、三类 lint 来源内部 lint、内置 lint、驱动 lint的注册路径以及编译器为性能而采用的合并 lint pass设计。读完本文你将理解 lint 从声明、注册、冻结到按类别执行的完整生命周期并能基于Config::register_lints为 clippy 等自定义驱动接入自己的 lint。Lint 与 lint pass编译器 lint 机制的两半rustc 的 lint 机制由两个相互独立的部分组成而大量文档笼统地将它们都称为lints这是初学者最容易混淆的地方。Lint 声明lint declarations定义了 lint 的名称、默认级别和其他元数据。它们通常通过declare_lint!宏定义最终展开为一个类型为rustc_lint_defs::Lint的静态变量。lint 声明本身不携带任何状态——它们只是全局标识符和对 lint 的描述。编译器在运行时断言同一个 lint 不会被重复注册按 lint 名称检查一旦重复注册将触发内部编译器错误ICE。值得注意的是rustc 禁止绕过宏直接手写声明这一约束由内部 lint 强制保证。Lint pass是任何 lint 的血肉——真正的检查逻辑所在。lint 与 lint pass 之间不存在一一对应关系一个 lint 可能没有任何对应的 lint pass永远不会被发出可能有多个也可能只有一个编译器根本不追踪某个 pass 与特定 lint 的关联许多 lint 是在类型检查等其他工作中顺带发出的。LintStore一切围绕它旋转LintStore是整个 lint 基础设施的核心Session持有它并在Session创建后不久填充 lint 列表。从源码看Session中以OptionArcdyn DynLintStore形式保存见 compiler/rustc_session/src/session.rs注册完成后 store 被放入Arc冻结此后只读共享。LintStore结构体的字段见 compiler/rustc_lint/src/context.rs清晰地揭示了其职责lints已注册的 lint 声明集合pre_expansion_lint_passes/early_lint_passes/late_lint_passes/late_lint_mod_passes四类 lint pass 工厂闭包的列表by_name按名称索引 lint 的映射表lint_groupslint 组名到组内 lint 列表的映射。源码注释特别强调了 late 类 pass 的差异late_lint_passes在 HIR 节点上运行、每个 pass 处理整个 crate因此无法受益于增量编译仅应在需要check_crate/check_crate_post并跨模块累积状态时使用late_lint_mod_passes则按模块多次构造可受益于增量编译应优先选用。注册流程总览在rustc_interface::run_compiler中LintStore被创建并注册全部 lint。Session构建完成后紧接着执行以下逻辑见 compiler/rustc_interface/src/interface.rslet mut lint_store rustc_lint::new_lint_store(sess.enable_internal_lints()); if let Some(register_lints) config.register_lints.as_deref() { register_lints(sess, mut lint_store); }new_lint_store的签名是pub fn new_lint_store(internal_lints: bool) - LintStore见 compiler/rustc_lint/src/lib.rs其实现如下pub fn new_lint_store(internal_lints: bool) - LintStore { let mut lint_store LintStore::new(); register_builtins(mut lint_store); if internal_lints { register_internals(mut lint_store); } lint_store }可以看到内部 lint 的注册是可选的只有当 session 启用了内部 lintenable_internal_lints()时才注册。lint 本身通过LintStore::register_lints注册每个 lint 只能注册一次重复注册会触发 ICE。三类 lint 来源lint 有三个来源内部 lintinternal lints仅 rustc 代码库自身或 clippy 等驱动使用内置 lintbuiltin lints编译器内置、非外部提供的 lint驱动 lintdriver lints通过rustc_interface::Config的register_lints字段在编译器构造期间注入。内部 lint约束编译器自身的检查内部 lint 定义在rustc_lint::internal模块见 compiler/rustc_lint/src/internal.rs。一个典型例子是LINT_PASS_IMPL_WITHOUT_MACRO它检查 lint pass 是否通过declare_lint_pass!宏实现而非手写其定义为pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO, impl LintPass without the declare_lint_pass! or impl_lint_pass! macros并通过declare_lint_pass!(LintPassImpl [LINT_PASS_IMPL_WITHOUT_MACRO])绑定到一个早期 lint pass。内部 lint 的注册发生在register_internals函数见 compiler/rustc_lint/src/lib.rs它在new_lint_store构造新 store 时被调用。该函数注册InternalCombinedEarlyLintPass与InternalCombinedLateLintModPass两个合并 pass并将一系列内部 lint 聚合到rustc::internallint 组中——组内注释逐一标注了每个 lint 所属的 pass如LINT_PASS_IMPL_WITHOUT_MACRO属于早期 passLintPassImpl、DEFAULT_HASH_TYPES属于晚期 passDefaultHashTypes等。其中一个有趣的细节是SYMBOL_INTERN_STRING_LITERAL被刻意排除在rustc::internal组之外因为 rustc_driver 之外的 crate 不应新增预内部化的符号rustc 本体由 bootstrap 手动启用该 lintrustdoc 则使用warn(symbol_intern_string_literal)。内置 lint编译器的默认检查集内置 lint 主要定义在两处rustc_lint_defs::builtin提供 lint 声明本身rustc_lint::builtin提供 lint pass 定义与实现但并非绝对两处职责常有交叉。内置 lint 的注册发生在register_builtins函数见 compiler/rustc_lint/src/lib.rs与内部 lint 一样在new_lint_store内完成。它首先注册四个合并 pass 的 lint 向量store.register_lints(BuiltinCombinedPreExpansionLintPass::lint_vec()); store.register_lints(BuiltinCombinedEarlyLintPass::lint_vec()); store.register_lints(BuiltinCombinedLateLintModPass::lint_vec()); store.register_lints(foreign_modules::lint_vec()); store.register_lints(hardwired::lint_vec());随后通过add_lint_group!宏将 lint 聚合为组例如nonstandard_style组包含NON_CAMEL_CASE_TYPES、NON_SNAKE_CASE、NON_UPPER_CASE_GLOBALSunused组包含UNUSED_IMPORTS、UNUSED_VARIABLES、DEAD_CODE、UNUSED_MUT、UNREACHABLE_CODE、UNUSED_MUST_USE等。这些组允许用户通过#![warn(unused)]之类的属性一键开启整组 lint。驱动 lint通过 Config 回调注入第三类 lint 由驱动通过rustc_interface::Config的register_lints字段提供其类型为见 compiler/rustc_interface/src/interface.rspub register_lints: OptionBoxdyn Fn(Session, mut LintStore) Send Sync,这是一个回调函数。驱动在添加自己的回调时如果发现该字段已被设置应当先调用当前已设置的函数再追加自己的逻辑。驱动获取该能力的最佳途径是覆盖Callbacks::config方法它能让驱动直接访问Config结构体。clippy 正是通过这一机制将自身数以百计的 lint 注入编译器的典型例子。lint pass 的注册与分类lint pass 分别注册到四个类别之一pre-expansion在宏展开之前运行于 AST 上。源码注释将其标记为softly deprecated——它可能遗漏展开后的代码并曾引发过一些错误目前仅 clippy 使用新实现应避免使用该接口见 compiler/rustc_lint/src/context.rsearly在 AST 节点上运行late在 HIR 节点上运行每个 pass 处理整个 cratelate module在 HIR 节点上运行按模块构造。pass 以闭包形式注册——即impl Fn() - Boxdyn X其中dyn X是早期或晚期 lint pass 的 trait 对象。运行 lint pass 时编译器先执行闭包工厂函数构造 pass 实例再调用其 lint 方法lint 方法接收mut self因此 pass 可以在内部维护状态。合并 pass静态分发换性能出于性能考虑编译器通常不会注册几十个独立的 lint pass而是每种类别只注册一个合并 pass如BuiltinCombinedLateLintModPass由它在内部依次调用所有单独 lint pass。这样做的收益是对于每个往往为空的trait 方法可以享受静态分发而非动态分发的优势。这些合并 pass 通过declare_combined_early_lint_pass与declare_combined_late_lint_pass宏在编译期生成见 compiler/rustc_lint/src/lib.rs例如BuiltinCombinedPreExpansionLintPass、BuiltinCombinedEarlyLintPass与BuiltinCombinedLateLintModPass均以此声明InternalCombinedLateLintModPass同理。与之相对的还有一种运行时合并方案RuntimeCombinedEarlyLintPass见 compiler/rustc_lint/src/early.rs在运行时持有VecEarlyLintPassObject其每个check_foo方法通过for pass in self.passes.iter_mut()依次调用各 pass 的同一方法。源码注释明确指出declare_combined_early_lint_pass是在编译期合并 lint pass而RuntimeCombinedEarlyLintPass与之类似但发生在运行时。文档也坦承这种设计并非理想——它增加了理解代码的复杂度但在当前类型擦除type-erased的 LintStore 架构下为了性能这样做是值得的。Lint 组的更多细节除了通过add_lint_group!注册内置 lint 组外LintStore还提供register_group注册 lint 组及其成员见 compiler/rustc_lint/src/context.rsregister_internals中即用其注册rustc::internal组register_group_alias为已有组注册别名见 compiler/rustc_lint/src/context.rsregister_removed登记已被移除的 lint 及其原因见 compiler/rustc_lint/src/context.rs。例如register_builtins的末尾见 compiler/rustc_lint/src/lib.rs登记了repr_transparent_external_private_fields已转为硬错误、no_mangle_generic_items泛型项必须被 mangling故转为硬错误、dependency_on_unit_never_type_fallback触发该 lint 的代码已无法编译等。注册为 removed 的 lint 在被用户指定时编译器会给出该 lint 已被移除及原因的诊断信息而不是报未知 lint。结语从LintStore的创建、三类 lint 的注册、四种类别 pass 的闭包式注册到编译期合并 pass 的性能取舍rustc 的 lint 体系呈现出清晰的架构分层声明与执行解耦、注册与运行解耦、来源可扩展驱动注入。理解这套机制无论对阅读 rustc 源码、为编译器贡献新 lint还是通过Config::register_lints打造 clippy 式的自定义 lint 工具都是最直接的切入点。建议继续阅读 diagnostics 目录下的其他文档如 diagnostic-structs.md、error-codes.md以完整掌握 rustc 的诊断体系。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考