about summary refs log tree commit diff
path: root/compiler/rustc_lint
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-09-15 00:01:17 +0000
committerbors <bors@rust-lang.org>2022-09-15 00:01:17 +0000
commit2cb9a65684dba47c52de8fa938febf97a73e70a9 (patch)
tree1333ca0fbb414d2c41ded8fc12d8a1136179d2e1 /compiler/rustc_lint
parent750bd1a7ff3e010611b97ee75d30b7cbf5f3a03c (diff)
parent42a92eb54bd94985966eaaf0b7377679a78de835 (diff)
downloadrust-2cb9a65684dba47c52de8fa938febf97a73e70a9.tar.gz
rust-2cb9a65684dba47c52de8fa938febf97a73e70a9.zip
Auto merge of #101620 - cjgillot:compute_lint_levels_by_def, r=oli-obk
Compute lint levels by definition

Lint levels are currently computed once for the whole crate. Any code that wants to emit a lint depends on this single `lint_levels(())` query. This query contains the `Span` for each attribute that participates in the lint level tree, so any code that wants to emit a lint basically depends on the spans in all files in the crate.

Contrary to hard errors, we do not clear the incremental session on lints, so this implicit world dependency pessimizes incremental reuse. (And is furthermore invisible for allowed lints.)

This PR completes https://github.com/rust-lang/rust/pull/99634 (thanks for the initial work `@fee1-dead)` and includes it in the dependency graph.

The design is based on 2 queries:
1. `lint_levels_on(HirId) -> FxHashMap<LintId, LevelAndSource>` which accesses the attributes at the given `HirId` and processes them into lint levels.  The `TyCtxt` is responsible for probing the HIR tree to find the user-visible level.
2. `lint_expectations(())` which lists all the `#[expect]` attributes in the crate.

This PR also introduces the ability to reconstruct a `HirId` from a `DepNode` by encoding the local part of the `DefPathHash` and the `ItemLocalId` in the two `u64` of the fingerprint.  This allows for the dep-graph to directly recompute `lint_levels_on` directly, without having to force the calling query.

Closes https://github.com/rust-lang/rust/issues/95094.
Supersedes https://github.com/rust-lang/rust/pull/99634.
Diffstat (limited to 'compiler/rustc_lint')
-rw-r--r--compiler/rustc_lint/src/context.rs2
-rw-r--r--compiler/rustc_lint/src/early.rs1
-rw-r--r--compiler/rustc_lint/src/expect.rs4
-rw-r--r--compiler/rustc_lint/src/levels.rs680
-rw-r--r--compiler/rustc_lint/src/lib.rs1
5 files changed, 402 insertions, 286 deletions
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 7ca6ec5d962..34506c84cb8 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -560,7 +560,7 @@ pub struct LateContext<'tcx> {
 
 /// Context for lint checking of the AST, after expansion, before lowering to HIR.
 pub struct EarlyContext<'a> {
-    pub builder: LintLevelsBuilder<'a>,
+    pub builder: LintLevelsBuilder<'a, crate::levels::TopDown>,
     pub buffered: LintBuffer,
 }
 
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 96ecd79a69c..45f54cfed2d 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -59,6 +59,7 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
         F: FnOnce(&mut Self),
     {
         let is_crate_node = id == ast::CRATE_NODE_ID;
+        debug!(?id);
         let push = self.context.builder.push(attrs, is_crate_node, None);
 
         self.check_id(id);
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 699e8154318..b9a3d52ca9b 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -16,8 +16,10 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
         return;
     }
 
+    let lint_expectations = tcx.lint_expectations(());
     let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids();
-    let lint_expectations = &tcx.lint_levels(()).lint_expectations;
+
+    tracing::debug!(?lint_expectations, ?fulfilled_expectations);
 
     for (id, expectation) in lint_expectations {
         // This check will always be true, since `lint_expectations` only
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 1e16ac51e9e..a9ba2ad1d10 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -6,10 +6,11 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::{intravisit, HirId};
+use rustc_index::vec::IndexVec;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::lint::{
-    struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets,
-    LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
+    reveal_actual_level, struct_lint_level, LevelAndSource, LintExpectation, LintLevelSource,
+    ShallowLintLevelMap,
 };
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{RegisteredTools, TyCtxt};
@@ -27,47 +28,292 @@ use crate::errors::{
     UnknownToolInScopedLint,
 };
 
-fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
-    let store = unerased_lint_store(tcx);
-    let levels =
-        LintLevelsBuilder::new(tcx.sess, false, &store, &tcx.resolutions(()).registered_tools);
-    let mut builder = LintLevelMapBuilder { levels, tcx };
-    let krate = tcx.hir().krate();
+/// Collection of lint levels for the whole crate.
+/// This is used by AST-based lints, which do not
+/// wait until we have built HIR to be emitted.
+#[derive(Debug)]
+struct LintLevelSets {
+    /// Linked list of specifications.
+    list: IndexVec<LintStackIndex, LintSet>,
+}
 
-    builder.levels.id_to_set.reserve(krate.owners.len() + 1);
+rustc_index::newtype_index! {
+    struct LintStackIndex {
+        ENCODABLE = custom, // we don't need encoding
+        const COMMAND_LINE = 0,
+    }
+}
 
-    let push =
-        builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
+/// Specifications found at this position in the stack.  This map only represents the lints
+/// found for one set of attributes (like `shallow_lint_levels_on` does).
+///
+/// We store the level specifications as a linked list.
+/// Each `LintSet` represents a set of attributes on the same AST node.
+/// The `parent` forms a linked list that matches the AST tree.
+/// This way, walking the linked list is equivalent to walking the AST bottom-up
+/// to find the specifications for a given lint.
+#[derive(Debug)]
+struct LintSet {
+    // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
+    // flag.
+    specs: FxHashMap<LintId, LevelAndSource>,
+    parent: LintStackIndex,
+}
+
+impl LintLevelSets {
+    fn new() -> Self {
+        LintLevelSets { list: IndexVec::new() }
+    }
+
+    fn get_lint_level(
+        &self,
+        lint: &'static Lint,
+        idx: LintStackIndex,
+        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+        sess: &Session,
+    ) -> LevelAndSource {
+        let lint = LintId::of(lint);
+        let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
+        let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
+            self.raw_lint_id_level(id, idx, aux)
+        });
+        (level, src)
+    }
 
-    builder.levels.register_id(hir::CRATE_HIR_ID);
+    fn raw_lint_id_level(
+        &self,
+        id: LintId,
+        mut idx: LintStackIndex,
+        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+    ) -> (Option<Level>, LintLevelSource) {
+        if let Some(specs) = aux {
+            if let Some(&(level, src)) = specs.get(&id) {
+                return (Some(level), src);
+            }
+        }
+        loop {
+            let LintSet { ref specs, parent } = self.list[idx];
+            if let Some(&(level, src)) = specs.get(&id) {
+                return (Some(level), src);
+            }
+            if idx == COMMAND_LINE {
+                return (None, LintLevelSource::Default);
+            }
+            idx = parent;
+        }
+    }
+}
+
+fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> {
+    let store = unerased_lint_store(tcx);
+
+    let mut builder = LintLevelsBuilder {
+        sess: tcx.sess,
+        provider: QueryMapExpectationsWrapper {
+            tcx,
+            cur: hir::CRATE_HIR_ID,
+            specs: ShallowLintLevelMap::default(),
+            expectations: Vec::new(),
+            unstable_to_stable_ids: FxHashMap::default(),
+        },
+        warn_about_weird_lints: false,
+        store,
+        registered_tools: &tcx.resolutions(()).registered_tools,
+    };
+
+    builder.add_command_line();
+    builder.add_id(hir::CRATE_HIR_ID);
     tcx.hir().walk_toplevel_module(&mut builder);
-    builder.levels.pop(push);
 
-    builder.levels.update_unstable_expectation_ids();
-    builder.levels.build_map()
+    tcx.sess.diagnostic().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids);
+
+    builder.provider.expectations
 }
 
-pub struct LintLevelsBuilder<'s> {
-    sess: &'s Session,
-    lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
-    /// Each expectation has a stable and an unstable identifier. This map
-    /// is used to map from unstable to stable [`LintExpectationId`]s.
-    expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>,
+fn shallow_lint_levels_on(tcx: TyCtxt<'_>, hir_id: HirId) -> ShallowLintLevelMap {
+    let store = unerased_lint_store(tcx);
+
+    let mut levels = LintLevelsBuilder {
+        sess: tcx.sess,
+        provider: LintLevelQueryMap { tcx, cur: hir_id, specs: ShallowLintLevelMap::default() },
+        warn_about_weird_lints: false,
+        store,
+        registered_tools: &tcx.resolutions(()).registered_tools,
+    };
+
+    let is_crate = hir::CRATE_HIR_ID == hir_id;
+    if is_crate {
+        levels.add_command_line();
+    }
+    debug!(?hir_id);
+    levels.add(tcx.hir().attrs(hir_id), is_crate, Some(hir_id));
+
+    levels.provider.specs
+}
+
+pub struct TopDown {
     sets: LintLevelSets,
-    id_to_set: FxHashMap<HirId, LintStackIndex>,
     cur: LintStackIndex,
+}
+
+pub trait LintLevelsProvider {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource>;
+    fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource>;
+    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource;
+    fn push_expectation(&mut self, _id: LintExpectationId, _expectation: LintExpectation) {}
+}
+
+impl LintLevelsProvider for TopDown {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        &self.sets.list[self.cur].specs
+    }
+
+    fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
+        &mut self.sets.list[self.cur].specs
+    }
+
+    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource {
+        self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess)
+    }
+}
+
+struct LintLevelQueryMap<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    cur: HirId,
+    specs: ShallowLintLevelMap,
+}
+
+impl LintLevelsProvider for LintLevelQueryMap<'_> {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        &self.specs.specs
+    }
+    fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
+        &mut self.specs.specs
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+}
+
+struct QueryMapExpectationsWrapper<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    cur: HirId,
+    specs: ShallowLintLevelMap,
+    expectations: Vec<(LintExpectationId, LintExpectation)>,
+    unstable_to_stable_ids: FxHashMap<LintExpectationId, LintExpectationId>,
+}
+
+impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> {
+    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+        &self.specs.specs
+    }
+    fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
+        self.specs.specs.clear();
+        &mut self.specs.specs
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+    fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) {
+        let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id else { bug!("unstable expectation id should already be mapped") };
+        let key = LintExpectationId::Unstable { attr_id, lint_index: None };
+
+        if !self.unstable_to_stable_ids.contains_key(&key) {
+            self.unstable_to_stable_ids.insert(
+                key,
+                LintExpectationId::Stable { hir_id, attr_index, lint_index: None, attr_id: None },
+            );
+        }
+
+        self.expectations.push((id.normalize(), expectation));
+    }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    fn add_id(&mut self, hir_id: HirId) {
+        self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id));
+    }
+}
+
+impl<'tcx> intravisit::Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    type NestedFilter = nested_filter::All;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.provider.tcx.hir()
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.add_id(param.hir_id);
+        intravisit::walk_param(self, param);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_item(self, it);
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_foreign_item(self, it);
+    }
+
+    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+        // We will call `add_id` when we walk
+        // the `StmtKind`. The outer statement itself doesn't
+        // define the lint levels.
+        intravisit::walk_stmt(self, e);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.add_id(e.hir_id);
+        intravisit::walk_expr(self, e);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.add_id(s.hir_id);
+        intravisit::walk_field_def(self, s);
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.add_id(v.id);
+        intravisit::walk_variant(self, v);
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+        self.add_id(l.hir_id);
+        intravisit::walk_local(self, l);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.add_id(a.hir_id);
+        intravisit::walk_arm(self, a);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.add_id(trait_item.hir_id());
+        intravisit::walk_trait_item(self, trait_item);
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.add_id(impl_item.hir_id());
+        intravisit::walk_impl_item(self, impl_item);
+    }
+}
+
+pub struct LintLevelsBuilder<'s, P> {
+    sess: &'s Session,
+    provider: P,
     warn_about_weird_lints: bool,
     store: &'s LintStore,
     registered_tools: &'s RegisteredTools,
 }
 
-pub struct BuilderPush {
+pub(crate) struct BuilderPush {
     prev: LintStackIndex,
-    pub changed: bool,
 }
 
-impl<'s> LintLevelsBuilder<'s> {
-    pub fn new(
+impl<'s> LintLevelsBuilder<'s, TopDown> {
+    pub(crate) fn new(
         sess: &'s Session,
         warn_about_weird_lints: bool,
         store: &'s LintStore,
@@ -75,20 +321,66 @@ impl<'s> LintLevelsBuilder<'s> {
     ) -> Self {
         let mut builder = LintLevelsBuilder {
             sess,
-            lint_expectations: Default::default(),
-            expectation_id_map: Default::default(),
-            sets: LintLevelSets::new(),
-            cur: COMMAND_LINE,
-            id_to_set: Default::default(),
+            provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE },
             warn_about_weird_lints,
             store,
             registered_tools,
         };
-        builder.process_command_line(sess, store);
-        assert_eq!(builder.sets.list.len(), 1);
+        builder.process_command_line();
+        assert_eq!(builder.provider.sets.list.len(), 1);
         builder
     }
 
+    fn process_command_line(&mut self) {
+        self.provider.cur = self
+            .provider
+            .sets
+            .list
+            .push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
+        self.add_command_line();
+    }
+
+    /// Pushes a list of AST lint attributes onto this context.
+    ///
+    /// This function will return a `BuilderPush` object which should be passed
+    /// to `pop` when this scope for the attributes provided is exited.
+    ///
+    /// This function will perform a number of tasks:
+    ///
+    /// * It'll validate all lint-related attributes in `attrs`
+    /// * It'll mark all lint-related attributes as used
+    /// * Lint levels will be updated based on the attributes provided
+    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
+    ///   `#[allow]`
+    ///
+    /// Don't forget to call `pop`!
+    pub(crate) fn push(
+        &mut self,
+        attrs: &[ast::Attribute],
+        is_crate_node: bool,
+        source_hir_id: Option<HirId>,
+    ) -> BuilderPush {
+        let prev = self.provider.cur;
+        self.provider.cur =
+            self.provider.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
+
+        self.add(attrs, is_crate_node, source_hir_id);
+
+        if self.provider.current_specs().is_empty() {
+            self.provider.sets.list.pop();
+            self.provider.cur = prev;
+        }
+
+        BuilderPush { prev }
+    }
+
+    /// Called after `push` when the scope of a set of attributes are exited.
+    pub(crate) fn pop(&mut self, push: BuilderPush) {
+        self.provider.cur = push.prev;
+    }
+}
+
+impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     pub(crate) fn sess(&self) -> &Session {
         self.sess
     }
@@ -98,24 +390,20 @@ impl<'s> LintLevelsBuilder<'s> {
     }
 
     fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
-        &self.sets.list[self.cur].specs
+        self.provider.current_specs()
     }
 
     fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
-        &mut self.sets.list[self.cur].specs
+        self.provider.current_specs_mut()
     }
 
-    fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
-        self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
-
-        self.cur =
-            self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
-        for &(ref lint_name, level) in &sess.opts.lint_opts {
-            store.check_lint_name_cmdline(sess, &lint_name, level, self.registered_tools);
+    fn add_command_line(&mut self) {
+        for &(ref lint_name, level) in &self.sess.opts.lint_opts {
+            self.store.check_lint_name_cmdline(self.sess, &lint_name, level, self.registered_tools);
             let orig_level = level;
             let lint_flag_val = Symbol::intern(lint_name);
 
-            let Ok(ids) = store.find_lints(&lint_name) else {
+            let Ok(ids) = self.store.find_lints(&lint_name) else {
                 // errors handled in check_lint_name_cmdline above
                 continue
             };
@@ -138,9 +426,11 @@ impl<'s> LintLevelsBuilder<'s> {
     /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
     /// (e.g. if a forbid was already inserted on the same scope), then emits a
     /// diagnostic with no change to `specs`.
-    fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) {
-        let (old_level, old_src) =
-            self.sets.get_lint_level(id.lint, self.cur, Some(self.current_specs()), &self.sess);
+    fn insert_spec(&mut self, id: LintId, (mut level, src): LevelAndSource) {
+        let (old_level, old_src) = self.provider.get_lint_level(id.lint, &self.sess);
+        if let Level::Expect(id) = &mut level && let LintExpectationId::Stable { .. } = id {
+            *id = id.normalize();
+        }
         // Setting to a non-forbid level is an error if the lint previously had
         // a forbid level. Note that this is not necessarily true even with a
         // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`.
@@ -158,7 +448,7 @@ impl<'s> LintLevelsBuilder<'s> {
                 let id_name = id.lint.name_lower();
                 let fcw_warning = match old_src {
                     LintLevelSource::Default => false,
-                    LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol),
+                    LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
                     LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
                 };
                 debug!(
@@ -178,8 +468,8 @@ impl<'s> LintLevelsBuilder<'s> {
                                 id.to_string()
                             ));
                         }
-                        LintLevelSource::Node(_, forbid_source_span, reason) => {
-                            diag.span_label(forbid_source_span, "`forbid` level set here");
+                        LintLevelSource::Node { span, reason, .. } => {
+                            diag.span_label(span, "`forbid` level set here");
                             if let Some(rationale) = reason {
                                 diag.note(rationale.as_str());
                             }
@@ -199,11 +489,8 @@ impl<'s> LintLevelsBuilder<'s> {
                             LintLevelSource::Default => {
                                 OverruledAttributeSub::DefaultSource { id: id.to_string() }
                             }
-                            LintLevelSource::Node(_, forbid_source_span, reason) => {
-                                OverruledAttributeSub::NodeSource {
-                                    span: forbid_source_span,
-                                    reason,
-                                }
+                            LintLevelSource::Node { span, reason, .. } => {
+                                OverruledAttributeSub::NodeSource { span, reason }
                             }
                             LintLevelSource::CommandLine(_, _) => {
                                 OverruledAttributeSub::CommandLineSource
@@ -256,29 +543,7 @@ impl<'s> LintLevelsBuilder<'s> {
         };
     }
 
-    /// Pushes a list of AST lint attributes onto this context.
-    ///
-    /// This function will return a `BuilderPush` object which should be passed
-    /// to `pop` when this scope for the attributes provided is exited.
-    ///
-    /// This function will perform a number of tasks:
-    ///
-    /// * It'll validate all lint-related attributes in `attrs`
-    /// * It'll mark all lint-related attributes as used
-    /// * Lint levels will be updated based on the attributes provided
-    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
-    ///   `#[allow]`
-    ///
-    /// Don't forget to call `pop`!
-    pub(crate) fn push(
-        &mut self,
-        attrs: &[ast::Attribute],
-        is_crate_node: bool,
-        source_hir_id: Option<HirId>,
-    ) -> BuilderPush {
-        let prev = self.cur;
-        self.cur = self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
-
+    fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) {
         let sess = self.sess;
         for (attr_index, attr) in attrs.iter().enumerate() {
             if attr.has_name(sym::automatically_derived) {
@@ -293,7 +558,17 @@ impl<'s> LintLevelsBuilder<'s> {
                 None => continue,
                 // This is the only lint level with a `LintExpectationId` that can be created from an attribute
                 Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
-                    let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index);
+                    let LintExpectationId::Unstable { attr_id, lint_index } = unstable_id
+                        else { bug!("stable id Level::from_attr") };
+
+                    let stable_id = LintExpectationId::Stable {
+                        hir_id,
+                        attr_index: attr_index.try_into().unwrap(),
+                        lint_index,
+                        // we pass the previous unstable attr_id such that we can trace the ast id when building a map
+                        // to go from unstable to stable id.
+                        attr_id: Some(attr_id),
+                    };
 
                     Level::Expect(stable_id)
                 }
@@ -408,7 +683,7 @@ impl<'s> LintLevelsBuilder<'s> {
                                 [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
                                 _ => false,
                             };
-                            self.lint_expectations.push((
+                            self.provider.push_expectation(
                                 expect_id,
                                 LintExpectation::new(
                                     reason,
@@ -416,13 +691,19 @@ impl<'s> LintLevelsBuilder<'s> {
                                     is_unfulfilled_lint_expectations,
                                     tool_name,
                                 ),
-                            ));
+                            );
                         }
-                        let src = LintLevelSource::Node(
-                            meta_item.path.segments.last().expect("empty lint name").ident.name,
-                            sp,
+                        let src = LintLevelSource::Node {
+                            name: meta_item
+                                .path
+                                .segments
+                                .last()
+                                .expect("empty lint name")
+                                .ident
+                                .name,
+                            span: sp,
                             reason,
-                        );
+                        };
                         for &id in *ids {
                             if self.check_gated_lint(id, attr.span) {
                                 self.insert_spec(id, (level, src));
@@ -435,31 +716,26 @@ impl<'s> LintLevelsBuilder<'s> {
                             Ok(ids) => {
                                 let complete_name =
                                     &format!("{}::{}", tool_ident.unwrap().name, name);
-                                let src = LintLevelSource::Node(
-                                    Symbol::intern(complete_name),
-                                    sp,
+                                let src = LintLevelSource::Node {
+                                    name: Symbol::intern(complete_name),
+                                    span: sp,
                                     reason,
-                                );
+                                };
                                 for &id in ids {
                                     if self.check_gated_lint(id, attr.span) {
                                         self.insert_spec(id, (level, src));
                                     }
                                 }
                                 if let Level::Expect(expect_id) = level {
-                                    self.lint_expectations.push((
+                                    self.provider.push_expectation(
                                         expect_id,
                                         LintExpectation::new(reason, sp, false, tool_name),
-                                    ));
+                                    );
                                 }
                             }
                             Err((Some(ids), ref new_lint_name)) => {
                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
-                                let (lvl, src) = self.sets.get_lint_level(
-                                    lint,
-                                    self.cur,
-                                    Some(self.current_specs()),
-                                    &sess,
-                                );
+                                let (lvl, src) = self.provider.get_lint_level(lint, &sess);
                                 struct_lint_level(
                                     self.sess,
                                     lint,
@@ -483,19 +759,19 @@ impl<'s> LintLevelsBuilder<'s> {
                                     },
                                 );
 
-                                let src = LintLevelSource::Node(
-                                    Symbol::intern(&new_lint_name),
-                                    sp,
+                                let src = LintLevelSource::Node {
+                                    name: Symbol::intern(&new_lint_name),
+                                    span: sp,
                                     reason,
-                                );
+                                };
                                 for id in ids {
                                     self.insert_spec(*id, (level, src));
                                 }
                                 if let Level::Expect(expect_id) = level {
-                                    self.lint_expectations.push((
+                                    self.provider.push_expectation(
                                         expect_id,
                                         LintExpectation::new(reason, sp, false, tool_name),
-                                    ));
+                                    );
                                 }
                             }
                             Err((None, _)) => {
@@ -521,12 +797,7 @@ impl<'s> LintLevelsBuilder<'s> {
 
                     CheckLintNameResult::Warning(msg, renamed) => {
                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
-                        let (renamed_lint_level, src) = self.sets.get_lint_level(
-                            lint,
-                            self.cur,
-                            Some(self.current_specs()),
-                            &sess,
-                        );
+                        let (renamed_lint_level, src) = self.provider.get_lint_level(lint, &sess);
                         struct_lint_level(
                             self.sess,
                             lint,
@@ -549,12 +820,7 @@ impl<'s> LintLevelsBuilder<'s> {
                     }
                     CheckLintNameResult::NoLint(suggestion) => {
                         let lint = builtin::UNKNOWN_LINTS;
-                        let (level, src) = self.sets.get_lint_level(
-                            lint,
-                            self.cur,
-                            Some(self.current_specs()),
-                            self.sess,
-                        );
+                        let (level, src) = self.provider.get_lint_level(lint, self.sess);
                         struct_lint_level(self.sess, lint, level, src, Some(sp.into()), |lint| {
                             let name = if let Some(tool_ident) = tool_ident {
                                 format!("{}::{}", tool_ident.name, name)
@@ -583,17 +849,21 @@ impl<'s> LintLevelsBuilder<'s> {
                     if let CheckLintNameResult::Ok(ids) =
                         self.store.check_lint_name(&new_name, None, self.registered_tools)
                     {
-                        let src = LintLevelSource::Node(Symbol::intern(&new_name), sp, reason);
+                        let src = LintLevelSource::Node {
+                            name: Symbol::intern(&new_name),
+                            span: sp,
+                            reason,
+                        };
                         for &id in ids {
                             if self.check_gated_lint(id, attr.span) {
                                 self.insert_spec(id, (level, src));
                             }
                         }
                         if let Level::Expect(expect_id) = level {
-                            self.lint_expectations.push((
+                            self.provider.push_expectation(
                                 expect_id,
                                 LintExpectation::new(reason, sp, false, tool_name),
-                            ));
+                            );
                         }
                     } else {
                         panic!("renamed lint does not exist: {}", new_name);
@@ -608,13 +878,12 @@ impl<'s> LintLevelsBuilder<'s> {
                     continue;
                 }
 
-                let LintLevelSource::Node(lint_attr_name, lint_attr_span, _) = *src else {
+                let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src else {
                     continue
                 };
 
                 let lint = builtin::UNUSED_ATTRIBUTES;
-                let (lint_level, lint_src) =
-                    self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), self.sess);
+                let (lint_level, lint_src) = self.provider.get_lint_level(lint, &self.sess);
                 struct_lint_level(
                     self.sess,
                     lint,
@@ -634,32 +903,13 @@ impl<'s> LintLevelsBuilder<'s> {
                 break;
             }
         }
-
-        if self.current_specs().is_empty() {
-            self.sets.list.pop();
-            self.cur = prev;
-        }
-
-        BuilderPush { prev, changed: prev != self.cur }
-    }
-
-    fn create_stable_id(
-        &mut self,
-        unstable_id: LintExpectationId,
-        hir_id: HirId,
-        attr_index: usize,
-    ) -> LintExpectationId {
-        let stable_id =
-            LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None };
-
-        self.expectation_id_map.insert(unstable_id, stable_id);
-
-        stable_id
     }
 
     /// Checks if the lint is gated on a feature that is not enabled.
     ///
     /// Returns `true` if the lint's feature is enabled.
+    // FIXME only emit this once for each attribute, instead of repeating it 4 times for
+    // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`.
     fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool {
         if let Some(feature) = lint_id.lint.feature_gate {
             if !self.sess.features_untracked().enabled(feature) {
@@ -678,19 +928,14 @@ impl<'s> LintLevelsBuilder<'s> {
         true
     }
 
-    /// Called after `push` when the scope of a set of attributes are exited.
-    pub fn pop(&mut self, push: BuilderPush) {
-        self.cur = push.prev;
-    }
-
     /// Find the lint level for a lint.
-    pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) {
-        self.sets.get_lint_level(lint, self.cur, None, self.sess)
+    pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource {
+        self.provider.get_lint_level(lint, self.sess)
     }
 
     /// Used to emit a lint-related diagnostic based on the current state of
     /// this lint context.
-    pub fn struct_lint(
+    pub(crate) fn struct_lint(
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
@@ -699,141 +944,8 @@ impl<'s> LintLevelsBuilder<'s> {
         let (level, src) = self.lint_level(lint);
         struct_lint_level(self.sess, lint, level, src, span, decorate)
     }
-
-    /// Registers the ID provided with the current set of lints stored in
-    /// this context.
-    pub fn register_id(&mut self, id: HirId) {
-        self.id_to_set.insert(id, self.cur);
-    }
-
-    fn update_unstable_expectation_ids(&self) {
-        self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
-    }
-
-    pub fn build_map(self) -> LintLevelMap {
-        LintLevelMap {
-            sets: self.sets,
-            id_to_set: self.id_to_set,
-            lint_expectations: self.lint_expectations,
-        }
-    }
-}
-
-struct LintLevelMapBuilder<'tcx> {
-    levels: LintLevelsBuilder<'tcx>,
-    tcx: TyCtxt<'tcx>,
-}
-
-impl LintLevelMapBuilder<'_> {
-    fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F)
-    where
-        F: FnOnce(&mut Self),
-    {
-        let is_crate_hir = id == hir::CRATE_HIR_ID;
-        let attrs = self.tcx.hir().attrs(id);
-        let push = self.levels.push(attrs, is_crate_hir, Some(id));
-
-        if push.changed {
-            self.levels.register_id(id);
-        }
-        f(self);
-        self.levels.pop(push);
-    }
-}
-
-impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'tcx> {
-    type NestedFilter = nested_filter::All;
-
-    fn nested_visit_map(&mut self) -> Self::Map {
-        self.tcx.hir()
-    }
-
-    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
-        self.with_lint_attrs(param.hir_id, |builder| {
-            intravisit::walk_param(builder, param);
-        });
-    }
-
-    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
-        self.with_lint_attrs(it.hir_id(), |builder| {
-            intravisit::walk_item(builder, it);
-        });
-    }
-
-    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
-        self.with_lint_attrs(it.hir_id(), |builder| {
-            intravisit::walk_foreign_item(builder, it);
-        })
-    }
-
-    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
-        // We will call `with_lint_attrs` when we walk
-        // the `StmtKind`. The outer statement itself doesn't
-        // define the lint levels.
-        intravisit::walk_stmt(self, e);
-    }
-
-    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
-        self.with_lint_attrs(e.hir_id, |builder| {
-            intravisit::walk_expr(builder, e);
-        })
-    }
-
-    fn visit_expr_field(&mut self, field: &'tcx hir::ExprField<'tcx>) {
-        self.with_lint_attrs(field.hir_id, |builder| {
-            intravisit::walk_expr_field(builder, field);
-        })
-    }
-
-    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
-        self.with_lint_attrs(s.hir_id, |builder| {
-            intravisit::walk_field_def(builder, s);
-        })
-    }
-
-    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
-        self.with_lint_attrs(v.id, |builder| {
-            intravisit::walk_variant(builder, v);
-        })
-    }
-
-    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
-        self.with_lint_attrs(l.hir_id, |builder| {
-            intravisit::walk_local(builder, l);
-        })
-    }
-
-    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
-        self.with_lint_attrs(a.hir_id, |builder| {
-            intravisit::walk_arm(builder, a);
-        })
-    }
-
-    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
-        self.with_lint_attrs(trait_item.hir_id(), |builder| {
-            intravisit::walk_trait_item(builder, trait_item);
-        });
-    }
-
-    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
-        self.with_lint_attrs(impl_item.hir_id(), |builder| {
-            intravisit::walk_impl_item(builder, impl_item);
-        });
-    }
-
-    fn visit_pat_field(&mut self, field: &'tcx hir::PatField<'tcx>) {
-        self.with_lint_attrs(field.hir_id, |builder| {
-            intravisit::walk_pat_field(builder, field);
-        })
-    }
-
-    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
-        self.with_lint_attrs(p.hir_id, |builder| {
-            intravisit::walk_generic_param(builder, p);
-        });
-    }
 }
 
-pub fn provide(providers: &mut Providers) {
-    providers.lint_levels = lint_levels;
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { shallow_lint_levels_on, lint_expectations, ..*providers };
 }
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index e0e6f1a8c08..2a22a6e5be1 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -35,6 +35,7 @@
 #![feature(iter_order_by)]
 #![feature(let_chains)]
 #![feature(let_else)]
+#![feature(min_specialization)]
 #![feature(never_type)]
 #![recursion_limit = "256"]