From 5ecda6e9b43b70dfe68f35b3a675b4b94a7d822b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 16 Jun 2026 09:37:53 +1000 Subject: [PATCH 1/6] Remove unnecessary parens --- compiler/rustc_lint/src/early.rs | 2 +- compiler/rustc_lint/src/late.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index e9afbcbbbaf70..084ca4545e1c3 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -336,7 +336,7 @@ pub fn check_ast_node<'a>( if passes.is_empty() { check_ast_node_inner(sess, check_node, context, builtin_lints); } else { - let mut passes: Vec<_> = passes.iter().map(|mk_pass| (mk_pass)()).collect(); + let mut passes: Vec<_> = passes.iter().map(|mk_pass| mk_pass()).collect(); passes.push(Box::new(builtin_lints)); let pass = RuntimeCombinedEarlyLintPass { passes }; check_ast_node_inner(sess, check_node, context, pass); diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 1f37ac2d3e0bc..d0894ff430320 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -370,7 +370,7 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( let passes = store .late_module_passes .iter() - .map(|mk_pass| (mk_pass)(tcx)) + .map(|mk_pass| mk_pass(tcx)) .chain(std::iter::once(builtin_lints)) .collect::>(); @@ -408,7 +408,7 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { // Note: `passes` is often empty after filtering. let mut passes: Vec<_> = - unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| mk_pass(tcx)).collect(); passes.retain(|pass| { let lints = pass.get_lints(); // Lintless passes are always in From f5cba18f5ce99f4fd12261aaaecef1cc73263d35 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 16 Jun 2026 10:18:48 +1000 Subject: [PATCH 2/6] Factor out repeated "run pass?" code --- compiler/rustc_lint/src/late.rs | 19 +++---------------- compiler/rustc_lint/src/lib.rs | 17 +++++++++++++++++ src/tools/clippy/clippy_lints/src/lib.rs | 9 ++------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index d0894ff430320..1c2cc5b7df885 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -18,7 +18,7 @@ use rustc_span::Span; use tracing::debug; use crate::passes::LateLintPassObject; -use crate::{LateContext, LateLintPass, LintId, LintStore}; +use crate::{LateContext, LateLintPass, LintStore, pass_must_run}; /// Extract the [`LintStore`] from [`Session`]. /// @@ -355,14 +355,7 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( let store = unerased_lint_store(tcx.sess); if store.late_module_passes.is_empty() { - // If all builtin lints can be skipped, there is no point in running `late_lint_mod_inner` - // at all. This happens often for dependencies built with `--cap-lints=allow`. - let dont_need_to_run = tcx.lints_that_dont_need_to_run(()); - let can_skip_lints = builtin_lints - .get_lints() - .iter() - .all(|lint| dont_need_to_run.contains(&LintId::of(lint))); - if !can_skip_lints { + if pass_must_run(tcx.lints_that_dont_need_to_run(()), &builtin_lints.get_lints()) { late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); } } else { @@ -409,13 +402,7 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { // Note: `passes` is often empty after filtering. let mut passes: Vec<_> = unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| mk_pass(tcx)).collect(); - passes.retain(|pass| { - let lints = pass.get_lints(); - // Lintless passes are always in - lints.is_empty() || - // If the pass doesn't have a single needed lint, omit it - !lints.iter().all(|lint| lints_that_dont_need_to_run.contains(&LintId::of(lint))) - }); + passes.retain(|pass| pass_must_run(lints_that_dont_need_to_run, &pass.get_lints())); if passes.is_empty() { return; } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 91267c2445ac4..12e13ce93cd27 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -117,6 +117,7 @@ use precedence::*; use ptr_nulls::*; use redundant_semicolon::*; use reference_casting::*; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::LocalModDefId; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; @@ -742,5 +743,21 @@ fn register_internals(store: &mut LintStore) { ); } +/// Must a pass (which contains `lints`) run? Maybe not, e.g. for dependencies built with +/// `--cap-lints=allow`. +/// +/// Note: this is a conservative estimate intended for optimization purposes. It might return +/// `true` for a pass that need not run, but it will never return `false` for a pass that must run. +pub fn pass_must_run(skippable: &UnordSet, lints: &LintVec) -> bool { + // A pass without any lints? Clippy sometimes does this, to collect things while traversing. + // Such a pass must always run. + if lints.is_empty() { + return true; + } + + // Otherwise, the pass must run unless all lints within are skippable. + !lints.iter().all(|lint| skippable.contains(&LintId::of(lint))) +} + #[cfg(test)] mod tests; diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 12d53e742f19a..48010455ff437 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -414,7 +414,7 @@ mod zombie_processes; use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; -use rustc_lint::Lint; +use rustc_lint::{Lint, pass_must_run}; use rustc_middle::ty::TyCtxt; use utils::attr_collector::AttrStorage; @@ -471,12 +471,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.late_passes.push(Box::new(move |tcx: TyCtxt<'_>| { let dont_need = tcx.lints_that_dont_need_to_run(()); - let is_active = |lints: &rustc_lint::LintVec| { - lints.is_empty() - || !lints - .iter() - .all(|lint| dont_need.contains(&rustc_lint::LintId::of(lint))) - }; + let is_active = |lints: &rustc_lint::LintVec| pass_must_run(dont_need, lints); Box::new(CombinedLateLintPass::new( tcx, conf, From b06c3112692722022a17fdac5ee236292c971231 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 16 Jun 2026 10:31:06 +1000 Subject: [PATCH 3/6] Rename `lints_that_dont_need_to_run` It's a bad name: long, and contains a negative. `skippable_lints` is better. --- compiler/rustc_hir_analysis/src/check/region.rs | 2 +- compiler/rustc_lint/src/if_let_rescope.rs | 2 +- compiler/rustc_lint/src/late.rs | 6 +++--- compiler/rustc_lint/src/levels.rs | 10 +++++----- compiler/rustc_middle/src/queries.rs | 2 +- .../src/lint_tail_expr_drop_order.rs | 2 +- .../clippy/clippy_lints/src/combined_early_pass.rs | 2 +- src/tools/clippy/clippy_lints/src/lib.rs | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index e31beca390837..e60318756d5c7 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -148,7 +148,7 @@ fn resolve_block<'tcx>( if !terminating && !visitor .tcx - .lints_that_dont_need_to_run(()) + .skippable_lints(()) .contains(&lint::LintId::of(lint::builtin::TAIL_EXPR_DROP_ORDER)) { // If this temporary scope will be changing once the codebase adopts Rust 2024, diff --git a/compiler/rustc_lint/src/if_let_rescope.rs b/compiler/rustc_lint/src/if_let_rescope.rs index cc8229b3f387b..97b7cdc135142 100644 --- a/compiler/rustc_lint/src/if_let_rescope.rs +++ b/compiler/rustc_lint/src/if_let_rescope.rs @@ -269,7 +269,7 @@ impl_lint_pass!( impl<'tcx> LateLintPass<'tcx> for IfLetRescope { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { if expr.span.edition().at_least_rust_2024() - || cx.tcx.lints_that_dont_need_to_run(()).contains(&LintId::of(IF_LET_RESCOPE)) + || cx.tcx.skippable_lints(()).contains(&LintId::of(IF_LET_RESCOPE)) { return; } diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 1c2cc5b7df885..b2f1d5c58830d 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -355,7 +355,7 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( let store = unerased_lint_store(tcx.sess); if store.late_module_passes.is_empty() { - if pass_must_run(tcx.lints_that_dont_need_to_run(()), &builtin_lints.get_lints()) { + if pass_must_run(tcx.skippable_lints(()), &builtin_lints.get_lints()) { late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); } } else { @@ -397,12 +397,12 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( } fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { - let lints_that_dont_need_to_run = tcx.lints_that_dont_need_to_run(()); + let skippable_lints = tcx.skippable_lints(()); // Note: `passes` is often empty after filtering. let mut passes: Vec<_> = unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| mk_pass(tcx)).collect(); - passes.retain(|pass| pass_must_run(lints_that_dont_need_to_run, &pass.get_lints())); + passes.retain(|pass| pass_must_run(skippable_lints, &pass.get_lints())); if passes.is_empty() { return; } diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index b7c24b9f2d5b5..492131ca8b32e 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -114,11 +114,11 @@ impl LintLevelSets { } } -fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet { +fn skippable_lints(tcx: TyCtxt<'_>, (): ()) -> UnordSet { let store = unerased_lint_store(&tcx.sess); let root_map = tcx.shallow_lint_levels_on(hir::CRATE_OWNER_ID); - let mut dont_need_to_run: FxHashSet = store + let mut skippable: FxHashSet = store .get_lints() .into_iter() .filter(|lint| { @@ -145,13 +145,13 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet { for (_, specs) in map.specs.iter() { for (lint, level_spec) in specs.iter() { if !level_spec.is_allow() { - dont_need_to_run.remove(lint); + skippable.remove(lint); } } } } - dont_need_to_run.into() + skippable.into() } #[instrument(level = "trace", skip(tcx), ret)] @@ -1035,7 +1035,7 @@ where } pub(crate) fn provide(providers: &mut Providers) { - *providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers }; + *providers = Providers { shallow_lint_levels_on, skippable_lints, ..*providers }; } pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option, &str) { diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index d4817888468fa..87dafe7bb41d6 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -541,7 +541,7 @@ rustc_queries! { desc { "computing `#[expect]`ed lints in this crate" } } - query lints_that_dont_need_to_run(_: ()) -> &'tcx UnordSet { + query skippable_lints(_: ()) -> &'tcx UnordSet { arena_cache // This depends on the lint store, which includes internal lints when the // untracked `-Zunstable-options` flag is set. diff --git a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs index d665a3603b5b5..fe09594d7c5f3 100644 --- a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs +++ b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs @@ -187,7 +187,7 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body< return; } if body.span.edition().at_least_rust_2024() - || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER)) + || tcx.skippable_lints(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER)) { return; } diff --git a/src/tools/clippy/clippy_lints/src/combined_early_pass.rs b/src/tools/clippy/clippy_lints/src/combined_early_pass.rs index 85de0b00a1b04..7494711c1f240 100644 --- a/src/tools/clippy/clippy_lints/src/combined_early_pass.rs +++ b/src/tools/clippy/clippy_lints/src/combined_early_pass.rs @@ -11,7 +11,7 @@ //! inlined calls. No vtable, no per-node dynamic dispatch. //! //! Unlike the late combine there is no `active` gate. rustc drops fully-disabled -//! late passes via `lints_that_dont_need_to_run`, but the early pass runner has +//! late passes via `skippable_lints`, but the early pass runner has //! no such filtering, so a plain forward is equivalent and loses nothing. //! //! [`combined_late_pass`]: crate::combined_late_pass diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 48010455ff437..e164cf27b8cd8 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -470,8 +470,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co } store.late_passes.push(Box::new(move |tcx: TyCtxt<'_>| { - let dont_need = tcx.lints_that_dont_need_to_run(()); - let is_active = |lints: &rustc_lint::LintVec| pass_must_run(dont_need, lints); + let skippable_lints = tcx.skippable_lints(()); + let is_active = |lints: &rustc_lint::LintVec| pass_must_run(skippable_lints, lints); Box::new(CombinedLateLintPass::new( tcx, conf, From 8f412c4aa5cc8ce794878c9c0f2f4ee74c8ba637 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 16 Jun 2026 10:41:12 +1000 Subject: [PATCH 4/6] Make `late_lint_mod`/`late_lint_crate` more similar In particular, `late_lint_mod` doesn't filter consistently: it only checks if `builtin_lints` needs running in the `passes.is_empty()` case, and it doesn't check if the other passes need running at all. --- compiler/rustc_lint/src/late.rs | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index b2f1d5c58830d..4e659e5a95fb7 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -349,24 +349,26 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( only_module: true, }; + let skippable_lints = tcx.skippable_lints(()); + // Note: `passes` is often empty. In that case, it's faster to run // `builtin_lints` directly rather than bundling it up into the // `RuntimeCombinedLateLintPass`. - let store = unerased_lint_store(tcx.sess); - - if store.late_module_passes.is_empty() { - if pass_must_run(tcx.skippable_lints(()), &builtin_lints.get_lints()) { + let mut passes: Vec<_> = unerased_lint_store(tcx.sess) + .late_module_passes + .iter() + .map(|mk_pass| mk_pass(tcx)) + .filter(|pass| pass_must_run(skippable_lints, &pass.get_lints())) + .collect(); + let builtin_lints_must_run = pass_must_run(skippable_lints, &builtin_lints.get_lints()); + if passes.is_empty() { + if builtin_lints_must_run { late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); } } else { - let builtin_lints = Box::new(builtin_lints) as Box>; - let passes = store - .late_module_passes - .iter() - .map(|mk_pass| mk_pass(tcx)) - .chain(std::iter::once(builtin_lints)) - .collect::>(); - + if builtin_lints_must_run { + passes.push(Box::new(builtin_lints) as Box>); + } let pass = RuntimeCombinedLateLintPass { passes }; late_lint_mod_inner(tcx, module_def_id, context, pass); } @@ -400,9 +402,12 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { let skippable_lints = tcx.skippable_lints(()); // Note: `passes` is often empty after filtering. - let mut passes: Vec<_> = - unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| mk_pass(tcx)).collect(); - passes.retain(|pass| pass_must_run(skippable_lints, &pass.get_lints())); + let passes: Vec<_> = unerased_lint_store(tcx.sess) + .late_passes + .iter() + .map(|mk_pass| mk_pass(tcx)) + .filter(|pass| pass_must_run(skippable_lints, &pass.get_lints())) + .collect(); if passes.is_empty() { return; } From 9b420de3f7fb48a536ef9551ea98d13b40676673 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 18 Jun 2026 14:00:48 +1000 Subject: [PATCH 5/6] Rework `check_ast_node_inner` It's called in two cases: pre-expansion lists, and early lints. The appropriate builtin pass is specified at the call site but the appropriate other passes are chosen within, which is inconsistent. This commit changes it so everything is chosen within, based on the boolean `pre_expansion_lint` parameter. `check_ast_node_inner` is split into `run_passes` and `run_pass`; the extra function is needed because the genericness of `builtin_lints` is pushed down one level below `check_ast_node`. --- compiler/rustc_interface/src/passes.rs | 2 - compiler/rustc_lint/src/context.rs | 3 +- compiler/rustc_lint/src/early.rs | 63 ++++++++++++++++---------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 060083e3bb59b..6a257255d2781 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -101,7 +101,6 @@ fn pre_expansion_lint<'a>( lint_store, registered_tools, None, - rustc_lint::BuiltinCombinedPreExpansionLintPass::new(), check_node, ); }, @@ -479,7 +478,6 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { lint_store, tcx.registered_tools(()), Some(lint_buffer), - rustc_lint::BuiltinCombinedEarlyLintPass::new(), EarlyCheckNode::CrateRoot(&*krate, &*krate.attrs), ) } diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 5599e3cdafd63..3348b61762c75 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -40,7 +40,8 @@ use self::TargetLint::*; use crate::levels::LintLevelsBuilder; use crate::passes::{EarlyLintPassObject, LateLintPassObject}; -type EarlyLintPassFactory = Box EarlyLintPassObject + sync::DynSend + sync::DynSync>; +pub(crate) type EarlyLintPassFactory = + Box EarlyLintPassObject + sync::DynSend + sync::DynSync>; type LateLintPassFactory = Box Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync>; diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 084ca4545e1c3..93d150c79d0df 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -16,7 +16,7 @@ use rustc_span::{Ident, Span}; use tracing::debug; use crate::DiagAndSess; -use crate::context::{EarlyContext, LintContext, LintStore}; +use crate::context::{EarlyContext, EarlyLintPassFactory, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; pub(super) mod diagnostics; @@ -316,7 +316,6 @@ pub fn check_ast_node<'a>( lint_store: &LintStore, registered_tools: &RegisteredTools, lint_buffer: Option, - builtin_lints: impl EarlyLintPass + 'static, check_node: EarlyCheckNode<'a>, ) { let context = EarlyContext::new( @@ -328,42 +327,56 @@ pub fn check_ast_node<'a>( lint_buffer.unwrap_or_default(), ); + let context = if pre_expansion { + let builtin_lints = crate::BuiltinCombinedPreExpansionLintPass::new(); + let passes = &lint_store.pre_expansion_passes; + run_passes(check_node, context, builtin_lints, passes) + } else { + let builtin_lints = crate::BuiltinCombinedEarlyLintPass::new(); + let passes = &lint_store.early_passes; + run_passes(check_node, context, builtin_lints, passes) + }; + + // All of the buffered lints should have been emitted at this point. + // If not, that means that we somehow buffered a lint for a node id + // that was not lint-checked (perhaps it doesn't exist?). This is a bug. + for (id, lints) in context.buffered.map { + if !lints.is_empty() { + assert!( + sess.dcx().has_errors().is_some(), + "failed to process buffered lint here (dummy = {})", + id == ast::DUMMY_NODE_ID + ); + break; + } + } +} + +fn run_passes<'a, 'ecx, T: EarlyLintPass + 'static>( + check_node: EarlyCheckNode<'a>, + context: EarlyContext<'ecx>, + builtin_lints: T, + passes: &[EarlyLintPassFactory], +) -> EarlyContext<'ecx> { // Note: `passes` is often empty. In that case, it's faster to run // `builtin_lints` directly rather than bundling it up into the // `RuntimeCombinedEarlyLintPass`. - let passes = - if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes }; if passes.is_empty() { - check_ast_node_inner(sess, check_node, context, builtin_lints); + run_pass(check_node, context, builtin_lints) } else { let mut passes: Vec<_> = passes.iter().map(|mk_pass| mk_pass()).collect(); passes.push(Box::new(builtin_lints)); let pass = RuntimeCombinedEarlyLintPass { passes }; - check_ast_node_inner(sess, check_node, context, pass); + run_pass(check_node, context, pass) } } -fn check_ast_node_inner<'a, T: EarlyLintPass>( - sess: &Session, +fn run_pass<'a, 'ecx, T: EarlyLintPass>( check_node: EarlyCheckNode<'a>, - context: EarlyContext<'_>, + context: EarlyContext<'ecx>, pass: T, -) { +) -> EarlyContext<'ecx> { let mut cx = EarlyContextAndPass { context, pass }; - cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx)); - - // All of the buffered lints should have been emitted at this point. - // If not, that means that we somehow buffered a lint for a node id - // that was not lint-checked (perhaps it doesn't exist?). This is a bug. - for (id, lints) in cx.context.buffered.map { - if !lints.is_empty() { - assert!( - sess.dcx().has_errors().is_some(), - "failed to process buffered lint here (dummy = {})", - id == ast::DUMMY_NODE_ID - ); - break; - } - } + cx.context } From 2724e23a54c66ce397c6028c0645ce51ad146e36 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 23 Jun 2026 08:10:25 +1000 Subject: [PATCH 6/6] Rename `pass_must_run` as `is_lint_pass_required` --- compiler/rustc_lint/src/late.rs | 8 ++++---- compiler/rustc_lint/src/lib.rs | 4 ++-- src/tools/clippy/clippy_lints/src/lib.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 4e659e5a95fb7..fea6b2ab2f9bd 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -18,7 +18,7 @@ use rustc_span::Span; use tracing::debug; use crate::passes::LateLintPassObject; -use crate::{LateContext, LateLintPass, LintStore, pass_must_run}; +use crate::{LateContext, LateLintPass, LintStore, is_lint_pass_required}; /// Extract the [`LintStore`] from [`Session`]. /// @@ -358,9 +358,9 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( .late_module_passes .iter() .map(|mk_pass| mk_pass(tcx)) - .filter(|pass| pass_must_run(skippable_lints, &pass.get_lints())) + .filter(|pass| is_lint_pass_required(skippable_lints, &pass.get_lints())) .collect(); - let builtin_lints_must_run = pass_must_run(skippable_lints, &builtin_lints.get_lints()); + let builtin_lints_must_run = is_lint_pass_required(skippable_lints, &builtin_lints.get_lints()); if passes.is_empty() { if builtin_lints_must_run { late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); @@ -406,7 +406,7 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { .late_passes .iter() .map(|mk_pass| mk_pass(tcx)) - .filter(|pass| pass_must_run(skippable_lints, &pass.get_lints())) + .filter(|pass| is_lint_pass_required(skippable_lints, &pass.get_lints())) .collect(); if passes.is_empty() { return; diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 12e13ce93cd27..14c70100c5f97 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -743,12 +743,12 @@ fn register_internals(store: &mut LintStore) { ); } -/// Must a pass (which contains `lints`) run? Maybe not, e.g. for dependencies built with +/// Is a pass (which contains `lints`) required to run? Maybe not, e.g. for dependencies built with /// `--cap-lints=allow`. /// /// Note: this is a conservative estimate intended for optimization purposes. It might return /// `true` for a pass that need not run, but it will never return `false` for a pass that must run. -pub fn pass_must_run(skippable: &UnordSet, lints: &LintVec) -> bool { +pub fn is_lint_pass_required(skippable: &UnordSet, lints: &LintVec) -> bool { // A pass without any lints? Clippy sometimes does this, to collect things while traversing. // Such a pass must always run. if lints.is_empty() { diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index e164cf27b8cd8..a0a5558b42eac 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -414,7 +414,7 @@ mod zombie_processes; use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; -use rustc_lint::{Lint, pass_must_run}; +use rustc_lint::{Lint, is_lint_pass_required}; use rustc_middle::ty::TyCtxt; use utils::attr_collector::AttrStorage; @@ -471,7 +471,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.late_passes.push(Box::new(move |tcx: TyCtxt<'_>| { let skippable_lints = tcx.skippable_lints(()); - let is_active = |lints: &rustc_lint::LintVec| pass_must_run(skippable_lints, lints); + let is_active = |lints: &rustc_lint::LintVec| is_lint_pass_required(skippable_lints, lints); Box::new(CombinedLateLintPass::new( tcx, conf,