资讯动态

深入理解 Clippy 的 EarlyLintPass 与 LateLintPass:如何为你的新 Lint 选择正确的 Pass

发布时间:2026/9/15 14:13:06 来源:尧图企业网站定制
深入理解 Clippy 的 EarlyLintPass 与 LateLintPass如何为你的新 Lint 选择正确的 Pass【免费下载链接】rust-clippyA bunch of lints to catch common mistakes and improve your Rust code. Book: https://doc.rust-lang.org/clippy/项目地址: https://gitcode.com/GitHub_Trending/ru/rust-clippy导读在 rust-clippy 中开发一个新 lint 时开发者做出的第一个关键架构决策是实现EarlyLintPass还是LateLintPass。二者的本质区别在于是否拥有类型与符号信息LateLintPass可以访问类型检查和符号解析结果而EarlyLintPass只能看到语法层面的抽象语法树AST。本文将以 lint_passes.md 为骨架结合 clippy 源码中的真实实现如double_parenslint、静态组合 pass 机制完整讲解两种 pass 的运行时机、能力边界、选择准则与脚手架命令帮助你为下一个 lint 做出正确决策并快速落地。先决决策新 Lint 的 Pass 类型在编写任何 lint 逻辑之前每个 Clippy 开发者都必须回答一个问题用EarlyLintPass还是LateLintPass一句话概括这也是官方开发文档给出的核心准则LateLintPass拥有类型type和符号symbol信息而EarlyLintPass没有。如果你的 lint 不需要访问类型信息就使用EarlyLintPass。这个决策直接决定了你手中的上下文对象是什么、你能检查哪些代码结构、以及最终 lint 的性能与表达能力。下文分别展开两个 trait 的细节。EarlyLintPass纯 AST 层面的语法检查运行时机与上下文查阅EarlyLintPass的文档可以发现该 trait 定义的每一个方法都接收一个EarlyContext。而EarlyContext的文档对其适用阶段有明确描述Context for lint checking of the AST, after expansion, before lowering to HIR. 在宏展开之后、降级到 HIR 之前对 AST 进行 lint 检查的上下文。也就是说EarlyLintPass只工作在抽象语法树AST层面。AST 是编译过程中词法分析与解析lexing and parsing阶段生成的产物。因此它不知道某个符号代表什么含义也不了解任何类型信息——如果你的 lint 只关心语法相关问题它应该是首选。从源码可以看到early pass 中导入的是rustc_ast::ast::*而非rustc_hir::*这一点在脚手架生成逻辑 clippy_dev/src/new_lint.rs 中有明确区分let (pass_type, pass_lifetimes, pass_import, context_import) match lint.pass { Pass::Early (EarlyLintPass, , use rustc_ast::ast::*;, EarlyContext), Pass::Late (LateLintPass, _, use rustc_hir::*;, LateContext), };EarlyLintPass检查的是rustc_ast中的节点Expr、Item等完全没有接触rustc_hir与类型检查产物。语法正确 ≠ 类型正确一个直观例子考虑下面这段代码let x OurUndefinedType; x.non_existing_method();从 AST 视角看这两行都是语法正确的赋值语句以let开头并以分号结尾方法调用的写法也毫无问题。作为程序员我们可能已经嗅到问题但解析器parser完全接受这段代码。这就是EarlyLintPass只处理 AST 层面的语法的含义。再换一个官方文档反复引用的例子foo_functionslint参见 Define New Lints。它的目标只是检测名为foo的函数——只检查函数名不涉及任何类型系统能力因此只需要操作 AST 即可完全不需要触碰LateLintPass才能获得的类型信息。性能优势虽然性能历来不是 Clippy 的主要顾虑但EarlyLintPass确实更快。如果你能确定一个 lint 不需要类型信息就应选择它。生成骨架命令使用以下命令为EarlyLintPass类型的 lint 生成模板$ cargo dev new_lint --nameyour_new_lint --passearly --categoryyour_category_choice在clippy_dev/src/new_lint.rs中Pass枚举只有Early与Late两个成员见 clippy_dev/src/new_lint.rs--pass参数即对应此二者。值得注意的是new_lint工具在用户选择 early pass 时还会打印一句提醒NOTE: Use a late pass unless you need something specific from an early pass, as they lack many features and utilities见 clippy_dev/src/new_lint.rs即除非你需要 early pass 的某些特定能力否则请用 late pass因为 early pass 缺少许多特性与工具函数——这与官方文档的建议完全一致。源码实例double_parensClippy 仓库中真实的 early lint 数量远少于 late lint。一个典型的EarlyLintPass实现是double_parens检测多余的双层括号完整代码见 clippy_lints/src/double_parens.rs。use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext as _, declare_lint_pass}; declare_lint_pass!(DoubleParens [DOUBLE_PARENS]); impl EarlyLintPass for DoubleParens { fn check_expr(mut self, cx: EarlyContext_, expr: Expr) { match expr.kind { ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) expr.span.eq_ctxt(inner.span) !expr.span.in_external_macro(cx.sess().source_map()) check_source(cx, inner) { // suggest removing the outer parens // ... span_lint_and_sugg( cx, DOUBLE_PARENS, expr.span, unnecessary parentheses, remove them, sugg.to_string(), applicability, ); }, // func((n)) / x.method((n)) 的情形 ExprKind::Call(_, args) | ExprKind::MethodCall(MethodCall { args, .. }) if let [arg] **args /* ... */ { /* ... */ }, _ {}, } } }这段实现清楚展示了 early lint 的典型形态上下文是EarlyContext_不是LateContext检查对象直接是 AST 节点rustc_ast::ast::Expr与ExprKind::Paren通过expr.kind的模式匹配完成语法分析全程只关心语法结构、span源码区间与宏展开上下文完全没有类型信息参与是否触发 lint 只取决于源码文本本身check_source会进一步核对源码是否真的形如((..))并排除$($result),*这类宏重复模式。其 UI 测试位于 tests/ui/double_parens.rs例如((0))、dummy_fn((0))、x.dummy_method((0))都会命中double_parens的//~^标注验证该 lint 只依赖 AST 就能工作。另外early lint 也有自己的类型分组例如misc_early目录clippy_lints/src/misc_early/mod.rs内的MiscEarlyLints同样是declare_lint_pass!加impl EarlyLintPass的结构把若干不需要类型信息的早期 lint 聚合在一起。LateLintPass引入类型检查的完整视角LateContext 与类型检查 API与EarlyLintPass相对LateLintPass包含类型信息。它的每个方法接收的是LateContext其中提供了EarlyContext中不存在的、与类型检查相关的方法例如maybe_typeck_results—— 获取可选的类型检查结果typeck_results—— 获取类型检查结果TypeckResults可查询表达式类型、方法解析结果、自动解引用链等。通过LateContext还可以进一步访问tcxTyCtxt类型上下文从而查询符号、trait 约束、类型关系等大量编译期信息。这正是类型与符号信息的入口。同一个例子不同的视角还是那段代码let x OurUndefinedType; x.non_existing_method();这两行在 AST 视角下是语法正确的有赋值、有对变量的方法调用语法上毫无问题。但下探到类型信息层面编译器会发现OurUndefinedType和non_existing_method()都没有被定义从而报错。作为 Clippy 开发者要想获得这类类型信息就必须在自己的 lint 上实现LateLintPass。浏览 Clippy 的 lint 集合你会发现几乎每个 lint 都是LateLintPass实现——因为大多数 lint 不仅需要检查语法问题还需要类型信息比如判断一个表达式是不是i32、某个方法调用解析到了哪个 trait 等。EarlyLintPass 的另一个局限节点定位方式除了缺少类型信息EarlyLintPass还有一个结构性限制AST 中的节点只能通过其在 AST 中的位置来识别。这意味着你无法拿到一个节点id后去按图索骥地请求某个特定节点。对大多数 lint 来说这不是问题但确实有一些 lint 需要检查其他节点例如跨函数、跨语句的关联分析这类需求在 HIR 层面做起来容易得多——此时LateLintPass是更好的选择。生成骨架命令为LateLintPass类型的 lint 生成模板$ cargo dev new_lint --nameyour_new_lint --passlate --categoryyour_category_choice对比两种命令可见唯一的差异是--pass取值early或late。--category若不提供默认是nursery详见 Define New Lints。决策速查表判断维度EarlyLintPassLateLintPass工作阶段AST宏展开后、降级为 HIR 前HIR / 类型检查结果TypeckResults上下文对象EarlyContextLateContext含maybe_typeck_results、typeck_results、tcx类型信息无有符号信息无有导入的语法树 craterustc_ast::ast::*rustc_hir::*性能更快相对较重但对 Clippy 非关键考量节点访问只能按 AST 位置识别无法按id取节点可借助 HIR id 检查关联节点典型用途纯语法检查多余括号、格式化、字面量写法、命名风格类型相关转换建议、需要类型或 trait 信息的检查生成命令cargo dev new_lint --passearly ...cargo dev new_lint --passlate ...在 Clippy 中的占比少数几十个绝大多数数百个从模板到注册两种 Pass 在仓库中的落地机制脚手架生成的模板差异运行cargo dev new_lint后clippy_dev/src/new_lint.rs会按 pass 类型生成不同的模板代码。除了上文提到的导入差异还有两点值得注意lint pass 声明宏不同early 模板使用declare_lint_pass!late 模板使用impl_lint_pass!当启用 MSRV 配置时late 模板还会生成带msrv: Msrv字段的pub struct与new(conf)构造函数见 clippy_dev/src/new_lint.rs。MSRV 支持方式不同early pass 使用MsrvStack基于栈的 MSRV 上下文late pass 使用Msrv对应的extract_msrv_attr!()宏只在 early 模板中出现——这是因为 early 阶段尚无法像 late 阶段那样直接读取配置对象。注册early_lint_methods! 与 late_lint_methods!新 lint 若属于 standalone不挂靠某个类型分组cargo dev new_lint会自动把该 pass 注册进 clippy_lints/src/lib.rs 中的// add early passes here或// add late passes here标记处对应cargo dev update_lints的手动路径。在lib.rs中可以看到两个宏展开rustc_lint::early_lint_methods!(crate::combined_early_lint_pass, [CombinedEarlyLintPass, ...])clippy_lints/src/lib.rs把约 45 个 early pass 折叠进CombinedEarlyLintPass结构体rustc_lint::late_lint_methods!(crate::combined_late_lint_pass, [CombinedLateLintPass, ...])clippy_lints/src/lib.rs把数百个 late pass 折叠进CombinedLateLintPass。官方文档强调若 lint 没有进入early_lint_methods!或late_lint_methods!中的任意一个该 lint pass 根本不会运行。如果想把新 lint 归入某个类型分组如functions、methods、casts等则需使用--type参数在clippy_lints/src/{type}/mod.rs的 lint pass 内调用详见 Define New Lints。静态组合 pass两种 pass 的实现差异Clippy 不再为每个 lint 注册一个独立 pass而是把同类 pass静态组合成单个结构体从而消除 vtable 动态分发。但 early 与 late 的组合实现并不完全相同combined_early_passclippy_lints/src/combined_early_pass.rs所有字段都是必选的非Option。因为 rustc 的 early pass 运行器没有skippable_lints式的过滤机制所以直接转发即可不会浪费什么。生成的EarlyLintPass实现里每个check_*方法都被标记为#[inline(always)]把调用转发给所有字段未覆写的方法只有空的默认实现会被 DCE死代码消除完全移除。combined_late_passclippy_lints/src/combined_late_pass.rs每个字段被包装在Option中构造时通过 lint 是否仍需要运行 的谓词判断若可跳过则置为None运行时用if let Some(pass) ...分支跳过从而保住 Clippy 默认允许allow-by-default 的快速路径。这意味着你新写的 late lint 即便不常用只要被静态组合也不会给每个节点带来额外的间接调用开销而 early pass 则天然就是扁平转发 内联的形态。如何验证你的选择UI 测试cargo dev new_lint会自动生成 UI 测试文件tests/ui/lint_name.rs内容形如#![warn(clippy::lint_name)]加一个空main见 clippy_dev/src/new_lint.rs。运行$ cargo test --test compile-test即可验证 lint 的输出。这类测试与 pass 类型无关——无论 early 还是 late测试文件都放在tests/ui/下cargo 类 lint 则放在tests/ui-cargo/的fail/pass目录。例如double_parens的测试用//~^ double_parens标注期望的告警位置见 tests/ui/double_parens.rscompile-test.rstests/compile-test.rs驱动整个 UI 测试流程。总结如何做出正确选择在动手写 lint 之前先问自己三个问题我需要类型或符号信息吗需要 →LateLintPass纯语法判断 →EarlyLintPass。我需要跨节点、按 id 定位/关联分析吗需要 →LateLintPassHIR 更易处理。性能是否敏感极度敏感且确实不需要类型信息 →EarlyLintPass。而实际开发体验中官方工具与维护者给出的倾向性建议非常明确除非 early pass 能提供你真正需要的特定能力否则一律使用 late pass——因为 late pass 拥有完整的功能集类型、符号、工具函数、更便利的节点访问这也是 Clippy 中绝大多数 lint 都是LateLintPass实现的根本原因。选定 pass 之后cargo dev new_lint --namelint_name --passearly|late --categorycategory会为你生成完整的实现骨架、注册条目与 UI 测试让你把精力集中在 lint 逻辑本身。如果你想继续深入了解 lint 的命名规范、declare_clippy_lint!宏的字段含义与类型分组注册方式请阅读同目录下的 Define New Lints 章节。【免费下载链接】rust-clippyA bunch of lints to catch common mistakes and improve your Rust code. Book: https://doc.rust-lang.org/clippy/项目地址: https://gitcode.com/GitHub_Trending/ru/rust-clippy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价