Skip to content

Commit 973ad0d

Browse files
committed
Auto merge of #158239 - nnethercote:rework-lint-pass-running, r=Urgau
Rework lint pass running Some cleanups relating to the running of lint passes. r? @Urgau
2 parents 73100ee + 2724e23 commit 973ad0d

12 files changed

Lines changed: 93 additions & 77 deletions

File tree

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn resolve_block<'tcx>(
148148
if !terminating
149149
&& !visitor
150150
.tcx
151-
.lints_that_dont_need_to_run(())
151+
.skippable_lints(())
152152
.contains(&lint::LintId::of(lint::builtin::TAIL_EXPR_DROP_ORDER))
153153
{
154154
// If this temporary scope will be changing once the codebase adopts Rust 2024,

compiler/rustc_interface/src/passes.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ fn pre_expansion_lint<'a>(
101101
lint_store,
102102
registered_tools,
103103
None,
104-
rustc_lint::BuiltinCombinedPreExpansionLintPass::new(),
105104
check_node,
106105
);
107106
},
@@ -479,7 +478,6 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) {
479478
lint_store,
480479
tcx.registered_tools(()),
481480
Some(lint_buffer),
482-
rustc_lint::BuiltinCombinedEarlyLintPass::new(),
483481
EarlyCheckNode::CrateRoot(&*krate, &*krate.attrs),
484482
)
485483
}

compiler/rustc_lint/src/context.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ use self::TargetLint::*;
4040
use crate::levels::LintLevelsBuilder;
4141
use crate::passes::{EarlyLintPassObject, LateLintPassObject};
4242

43-
type EarlyLintPassFactory = Box<dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync>;
43+
pub(crate) type EarlyLintPassFactory =
44+
Box<dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync>;
4445
type LateLintPassFactory =
4546
Box<dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync>;
4647

compiler/rustc_lint/src/early.rs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_span::{Ident, Span};
1616
use tracing::debug;
1717

1818
use crate::DiagAndSess;
19-
use crate::context::{EarlyContext, LintContext, LintStore};
19+
use crate::context::{EarlyContext, EarlyLintPassFactory, LintContext, LintStore};
2020
use crate::passes::{EarlyLintPass, EarlyLintPassObject};
2121

2222
pub(super) mod diagnostics;
@@ -316,7 +316,6 @@ pub fn check_ast_node<'a>(
316316
lint_store: &LintStore,
317317
registered_tools: &RegisteredTools,
318318
lint_buffer: Option<LintBuffer>,
319-
builtin_lints: impl EarlyLintPass + 'static,
320319
check_node: EarlyCheckNode<'a>,
321320
) {
322321
let context = EarlyContext::new(
@@ -328,35 +327,20 @@ pub fn check_ast_node<'a>(
328327
lint_buffer.unwrap_or_default(),
329328
);
330329

331-
// Note: `passes` is often empty. In that case, it's faster to run
332-
// `builtin_lints` directly rather than bundling it up into the
333-
// `RuntimeCombinedEarlyLintPass`.
334-
let passes =
335-
if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes };
336-
if passes.is_empty() {
337-
check_ast_node_inner(sess, check_node, context, builtin_lints);
330+
let context = if pre_expansion {
331+
let builtin_lints = crate::BuiltinCombinedPreExpansionLintPass::new();
332+
let passes = &lint_store.pre_expansion_passes;
333+
run_passes(check_node, context, builtin_lints, passes)
338334
} else {
339-
let mut passes: Vec<_> = passes.iter().map(|mk_pass| (mk_pass)()).collect();
340-
passes.push(Box::new(builtin_lints));
341-
let pass = RuntimeCombinedEarlyLintPass { passes };
342-
check_ast_node_inner(sess, check_node, context, pass);
343-
}
344-
}
345-
346-
fn check_ast_node_inner<'a, T: EarlyLintPass>(
347-
sess: &Session,
348-
check_node: EarlyCheckNode<'a>,
349-
context: EarlyContext<'_>,
350-
pass: T,
351-
) {
352-
let mut cx = EarlyContextAndPass { context, pass };
353-
354-
cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx));
335+
let builtin_lints = crate::BuiltinCombinedEarlyLintPass::new();
336+
let passes = &lint_store.early_passes;
337+
run_passes(check_node, context, builtin_lints, passes)
338+
};
355339

356340
// All of the buffered lints should have been emitted at this point.
357341
// If not, that means that we somehow buffered a lint for a node id
358342
// that was not lint-checked (perhaps it doesn't exist?). This is a bug.
359-
for (id, lints) in cx.context.buffered.map {
343+
for (id, lints) in context.buffered.map {
360344
if !lints.is_empty() {
361345
assert!(
362346
sess.dcx().has_errors().is_some(),
@@ -367,3 +351,32 @@ fn check_ast_node_inner<'a, T: EarlyLintPass>(
367351
}
368352
}
369353
}
354+
355+
fn run_passes<'a, 'ecx, T: EarlyLintPass + 'static>(
356+
check_node: EarlyCheckNode<'a>,
357+
context: EarlyContext<'ecx>,
358+
builtin_lints: T,
359+
passes: &[EarlyLintPassFactory],
360+
) -> EarlyContext<'ecx> {
361+
// Note: `passes` is often empty. In that case, it's faster to run
362+
// `builtin_lints` directly rather than bundling it up into the
363+
// `RuntimeCombinedEarlyLintPass`.
364+
if passes.is_empty() {
365+
run_pass(check_node, context, builtin_lints)
366+
} else {
367+
let mut passes: Vec<_> = passes.iter().map(|mk_pass| mk_pass()).collect();
368+
passes.push(Box::new(builtin_lints));
369+
let pass = RuntimeCombinedEarlyLintPass { passes };
370+
run_pass(check_node, context, pass)
371+
}
372+
}
373+
374+
fn run_pass<'a, 'ecx, T: EarlyLintPass>(
375+
check_node: EarlyCheckNode<'a>,
376+
context: EarlyContext<'ecx>,
377+
pass: T,
378+
) -> EarlyContext<'ecx> {
379+
let mut cx = EarlyContextAndPass { context, pass };
380+
cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx));
381+
cx.context
382+
}

compiler/rustc_lint/src/if_let_rescope.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl_lint_pass!(
269269
impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
270270
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
271271
if expr.span.edition().at_least_rust_2024()
272-
|| cx.tcx.lints_that_dont_need_to_run(()).contains(&LintId::of(IF_LET_RESCOPE))
272+
|| cx.tcx.skippable_lints(()).contains(&LintId::of(IF_LET_RESCOPE))
273273
{
274274
return;
275275
}

compiler/rustc_lint/src/late.rs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_span::Span;
1818
use tracing::debug;
1919

2020
use crate::passes::LateLintPassObject;
21-
use crate::{LateContext, LateLintPass, LintId, LintStore};
21+
use crate::{LateContext, LateLintPass, LintStore, is_lint_pass_required};
2222

2323
/// Extract the [`LintStore`] from [`Session`].
2424
///
@@ -349,31 +349,26 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
349349
only_module: true,
350350
};
351351

352+
let skippable_lints = tcx.skippable_lints(());
353+
352354
// Note: `passes` is often empty. In that case, it's faster to run
353355
// `builtin_lints` directly rather than bundling it up into the
354356
// `RuntimeCombinedLateLintPass`.
355-
let store = unerased_lint_store(tcx.sess);
356-
357-
if store.late_module_passes.is_empty() {
358-
// If all builtin lints can be skipped, there is no point in running `late_lint_mod_inner`
359-
// at all. This happens often for dependencies built with `--cap-lints=allow`.
360-
let dont_need_to_run = tcx.lints_that_dont_need_to_run(());
361-
let can_skip_lints = builtin_lints
362-
.get_lints()
363-
.iter()
364-
.all(|lint| dont_need_to_run.contains(&LintId::of(lint)));
365-
if !can_skip_lints {
357+
let mut passes: Vec<_> = unerased_lint_store(tcx.sess)
358+
.late_module_passes
359+
.iter()
360+
.map(|mk_pass| mk_pass(tcx))
361+
.filter(|pass| is_lint_pass_required(skippable_lints, &pass.get_lints()))
362+
.collect();
363+
let builtin_lints_must_run = is_lint_pass_required(skippable_lints, &builtin_lints.get_lints());
364+
if passes.is_empty() {
365+
if builtin_lints_must_run {
366366
late_lint_mod_inner(tcx, module_def_id, context, builtin_lints);
367367
}
368368
} else {
369-
let builtin_lints = Box::new(builtin_lints) as Box<dyn LateLintPass<'tcx>>;
370-
let passes = store
371-
.late_module_passes
372-
.iter()
373-
.map(|mk_pass| (mk_pass)(tcx))
374-
.chain(std::iter::once(builtin_lints))
375-
.collect::<Vec<_>>();
376-
369+
if builtin_lints_must_run {
370+
passes.push(Box::new(builtin_lints) as Box<dyn LateLintPass<'tcx>>);
371+
}
377372
let pass = RuntimeCombinedLateLintPass { passes };
378373
late_lint_mod_inner(tcx, module_def_id, context, pass);
379374
}
@@ -404,18 +399,15 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>(
404399
}
405400

406401
fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
407-
let lints_that_dont_need_to_run = tcx.lints_that_dont_need_to_run(());
402+
let skippable_lints = tcx.skippable_lints(());
408403

409404
// Note: `passes` is often empty after filtering.
410-
let mut passes: Vec<_> =
411-
unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
412-
passes.retain(|pass| {
413-
let lints = pass.get_lints();
414-
// Lintless passes are always in
415-
lints.is_empty() ||
416-
// If the pass doesn't have a single needed lint, omit it
417-
!lints.iter().all(|lint| lints_that_dont_need_to_run.contains(&LintId::of(lint)))
418-
});
405+
let passes: Vec<_> = unerased_lint_store(tcx.sess)
406+
.late_passes
407+
.iter()
408+
.map(|mk_pass| mk_pass(tcx))
409+
.filter(|pass| is_lint_pass_required(skippable_lints, &pass.get_lints()))
410+
.collect();
419411
if passes.is_empty() {
420412
return;
421413
}

compiler/rustc_lint/src/levels.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ impl LintLevelSets {
114114
}
115115
}
116116

117-
fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
117+
fn skippable_lints(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
118118
let store = unerased_lint_store(&tcx.sess);
119119
let root_map = tcx.shallow_lint_levels_on(hir::CRATE_OWNER_ID);
120120

121-
let mut dont_need_to_run: FxHashSet<LintId> = store
121+
let mut skippable: FxHashSet<LintId> = store
122122
.get_lints()
123123
.into_iter()
124124
.filter(|lint| {
@@ -145,13 +145,13 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
145145
for (_, specs) in map.specs.iter() {
146146
for (lint, level_spec) in specs.iter() {
147147
if !level_spec.is_allow() {
148-
dont_need_to_run.remove(lint);
148+
skippable.remove(lint);
149149
}
150150
}
151151
}
152152
}
153153

154-
dont_need_to_run.into()
154+
skippable.into()
155155
}
156156

157157
#[instrument(level = "trace", skip(tcx), ret)]
@@ -1035,7 +1035,7 @@ where
10351035
}
10361036

10371037
pub(crate) fn provide(providers: &mut Providers) {
1038-
*providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers };
1038+
*providers = Providers { shallow_lint_levels_on, skippable_lints, ..*providers };
10391039
}
10401040

10411041
pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {

compiler/rustc_lint/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ use precedence::*;
117117
use ptr_nulls::*;
118118
use redundant_semicolon::*;
119119
use reference_casting::*;
120+
use rustc_data_structures::unord::UnordSet;
120121
use rustc_hir::def_id::LocalModDefId;
121122
use rustc_middle::query::Providers;
122123
use rustc_middle::ty::TyCtxt;
@@ -742,5 +743,21 @@ fn register_internals(store: &mut LintStore) {
742743
);
743744
}
744745

746+
/// Is a pass (which contains `lints`) required to run? Maybe not, e.g. for dependencies built with
747+
/// `--cap-lints=allow`.
748+
///
749+
/// Note: this is a conservative estimate intended for optimization purposes. It might return
750+
/// `true` for a pass that need not run, but it will never return `false` for a pass that must run.
751+
pub fn is_lint_pass_required(skippable: &UnordSet<LintId>, lints: &LintVec) -> bool {
752+
// A pass without any lints? Clippy sometimes does this, to collect things while traversing.
753+
// Such a pass must always run.
754+
if lints.is_empty() {
755+
return true;
756+
}
757+
758+
// Otherwise, the pass must run unless all lints within are skippable.
759+
!lints.iter().all(|lint| skippable.contains(&LintId::of(lint)))
760+
}
761+
745762
#[cfg(test)]
746763
mod tests;

compiler/rustc_middle/src/queries.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ rustc_queries! {
541541
desc { "computing `#[expect]`ed lints in this crate" }
542542
}
543543

544-
query lints_that_dont_need_to_run(_: ()) -> &'tcx UnordSet<LintId> {
544+
query skippable_lints(_: ()) -> &'tcx UnordSet<LintId> {
545545
arena_cache
546546
// This depends on the lint store, which includes internal lints when the
547547
// untracked `-Zunstable-options` flag is set.

compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
187187
return;
188188
}
189189
if body.span.edition().at_least_rust_2024()
190-
|| tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
190+
|| tcx.skippable_lints(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
191191
{
192192
return;
193193
}

0 commit comments

Comments
 (0)