From 280a1d8edb8322280bb9b10fab41c7bfb43d67b2 Mon Sep 17 00:00:00 2001
From: Camille GILLOT <gillot.camille@gmail.com>
Date: Tue, 3 Dec 2024 00:12:24 +0000
Subject: [PATCH] Do not visit whole crate to compute
 `lints_that_dont_need_to_run`.

---
 compiler/rustc_lint/src/levels.rs             | 117 ++++--------------
 compiler/rustc_middle/src/query/mod.rs        |   2 +-
 .../unsafe/proc-unsafe-attributes.rs          |   2 +
 .../unsafe/proc-unsafe-attributes.stderr      |  18 ++-
 .../deduplicate-diagnostics.duplicate.stderr  |  10 +-
 tests/ui/deduplicate-diagnostics.rs           |   1 +
 tests/ui/tool-attributes/tool_lints.rs        |   1 +
 tests/ui/tool-attributes/tool_lints.stderr    |  11 +-
 .../tool-attributes/unknown-lint-tool-name.rs |   1 +
 .../unknown-lint-tool-name.stderr             |  11 +-
 10 files changed, 79 insertions(+), 95 deletions(-)

diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index f1fe07cfcfa..d0b1e7bf255 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -1,10 +1,11 @@
 use rustc_ast::attr::AttributeExt;
 use rustc_ast_pretty::pprust;
-use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
+use rustc_data_structures::unord::UnordSet;
 use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
 use rustc_feature::{Features, GateIssue};
+use rustc_hir::HirId;
 use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{CRATE_HIR_ID, HirId};
 use rustc_index::IndexVec;
 use rustc_middle::bug;
 use rustc_middle::hir::nested_filter;
@@ -115,12 +116,11 @@ impl LintLevelSets {
     }
 }
 
-fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
+fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
     let store = unerased_lint_store(&tcx.sess);
+    let root_map = tcx.shallow_lint_levels_on(hir::CRATE_OWNER_ID);
 
-    let map = tcx.shallow_lint_levels_on(rustc_hir::CRATE_OWNER_ID);
-
-    let dont_need_to_run: FxIndexSet<LintId> = store
+    let mut dont_need_to_run: FxHashSet<LintId> = store
         .get_lints()
         .into_iter()
         .filter(|lint| {
@@ -129,24 +129,31 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
                 lint.future_incompatible.is_some_and(|fut| fut.reason.has_future_breakage());
             !has_future_breakage && !lint.eval_always
         })
-        .filter_map(|lint| {
-            let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID);
-            if matches!(lint_level.level, Level::Allow)
-                || (matches!(lint_level.src, LintLevelSource::Default))
-                    && lint.default_level(tcx.sess.edition()) == Level::Allow
-            {
-                Some(LintId::of(lint))
-            } else {
-                None
-            }
+        .filter(|lint| {
+            let lint_level =
+                root_map.lint_level_id_at_node(tcx, LintId::of(lint), hir::CRATE_HIR_ID);
+            // Only include lints that are allowed at crate root or by default.
+            matches!(lint_level.level, Level::Allow)
+                || (matches!(lint_level.src, LintLevelSource::Default)
+                    && lint.default_level(tcx.sess.edition()) == Level::Allow)
         })
+        .map(|lint| LintId::of(*lint))
         .collect();
 
-    let mut visitor = LintLevelMaximum { tcx, dont_need_to_run };
-    visitor.process_opts();
-    tcx.hir_walk_attributes(&mut visitor);
+    for owner in tcx.hir_crate_items(()).owners() {
+        let map = tcx.shallow_lint_levels_on(owner);
 
-    visitor.dont_need_to_run
+        // All lints that appear with a non-allow level must be run.
+        for (_, specs) in map.specs.iter() {
+            for (lint, level_and_source) in specs.iter() {
+                if !matches!(level_and_source.level, Level::Allow) {
+                    dont_need_to_run.remove(lint);
+                }
+            }
+        }
+    }
+
+    dont_need_to_run.into()
 }
 
 #[instrument(level = "trace", skip(tcx), ret)]
@@ -340,76 +347,6 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
     }
 }
 
-/// Visitor with the only function of visiting every item-like in a crate and
-/// computing the highest level that every lint gets put to.
-///
-/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item
-/// uses #[warn(lint)], this visitor will set that lint level as `Warn`
-struct LintLevelMaximum<'tcx> {
-    tcx: TyCtxt<'tcx>,
-    /// The actual list of detected lints.
-    dont_need_to_run: FxIndexSet<LintId>,
-}
-
-impl<'tcx> LintLevelMaximum<'tcx> {
-    fn process_opts(&mut self) {
-        let store = unerased_lint_store(self.tcx.sess);
-        for (lint_group, level) in &self.tcx.sess.opts.lint_opts {
-            if *level != Level::Allow {
-                let Ok(lints) = store.find_lints(lint_group) else {
-                    return;
-                };
-                for lint in lints {
-                    self.dont_need_to_run.swap_remove(&lint);
-                }
-            }
-        }
-    }
-}
-
-impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> {
-    type NestedFilter = nested_filter::All;
-
-    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
-        self.tcx
-    }
-
-    /// FIXME(blyxyas): In a future revision, we should also graph #![allow]s,
-    /// but that is handled with more care
-    fn visit_attribute(&mut self, attribute: &'tcx hir::Attribute) {
-        if matches!(
-            Level::from_attr(attribute),
-            Some((Level::Warn | Level::Deny | Level::Forbid | Level::Expect | Level::ForceWarn, _))
-        ) {
-            let store = unerased_lint_store(self.tcx.sess);
-            // Lint attributes are always a metalist inside a
-            // metalist (even with just one lint).
-            let Some(meta_item_list) = attribute.meta_item_list() else { return };
-
-            for meta_list in meta_item_list {
-                // Convert Path to String
-                let Some(meta_item) = meta_list.meta_item() else { return };
-                let ident: &str = &meta_item
-                    .path
-                    .segments
-                    .iter()
-                    .map(|segment| segment.ident.as_str())
-                    .collect::<Vec<&str>>()
-                    .join("::");
-                let Ok(lints) = store.find_lints(
-                    // Lint attributes can only have literals
-                    ident,
-                ) else {
-                    return;
-                };
-                for lint in lints {
-                    self.dont_need_to_run.swap_remove(&lint);
-                }
-            }
-        }
-    }
-}
-
 pub struct LintLevelsBuilder<'s, P> {
     sess: &'s Session,
     features: &'s Features,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 698859c663b..b9a113b976e 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -476,7 +476,7 @@ rustc_queries! {
         desc { "computing `#[expect]`ed lints in this crate" }
     }
 
-    query lints_that_dont_need_to_run(_: ()) -> &'tcx FxIndexSet<LintId> {
+    query lints_that_dont_need_to_run(_: ()) -> &'tcx UnordSet<LintId> {
         arena_cache
         desc { "Computing all lints that are explicitly enabled or with a default level greater than Allow" }
     }
diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs
index eaf8706369a..2f17d9620b4 100644
--- a/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs
+++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs
@@ -30,6 +30,8 @@ pub fn e() {}
 //~| ERROR: expected identifier, found keyword `unsafe`
 //~| ERROR: malformed lint attribute input
 //~| ERROR: malformed lint attribute input
+//~| ERROR: malformed lint attribute input
+//~| ERROR: malformed lint attribute input
 pub fn f() {}
 
 fn main() {}
diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr
index 9c5751c82e4..25b83a26e17 100644
--- a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr
+++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr
@@ -114,6 +114,22 @@ LL | #[unsafe(allow(unsafe(dead_code)))]
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
-error: aborting due to 15 previous errors
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:26:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:26:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 17 previous errors
 
 For more information about this error, try `rustc --explain E0452`.
diff --git a/tests/ui/deduplicate-diagnostics.duplicate.stderr b/tests/ui/deduplicate-diagnostics.duplicate.stderr
index 0544b993278..48e2ba7b86a 100644
--- a/tests/ui/deduplicate-diagnostics.duplicate.stderr
+++ b/tests/ui/deduplicate-diagnostics.duplicate.stderr
@@ -26,6 +26,14 @@ LL | #[deny("literal")]
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
-error: aborting due to 4 previous errors
+error[E0452]: malformed lint attribute input
+  --> $DIR/deduplicate-diagnostics.rs:8:8
+   |
+LL | #[deny("literal")]
+   |        ^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 5 previous errors
 
 For more information about this error, try `rustc --explain E0452`.
diff --git a/tests/ui/deduplicate-diagnostics.rs b/tests/ui/deduplicate-diagnostics.rs
index 54bd5dd5098..299c1f5f461 100644
--- a/tests/ui/deduplicate-diagnostics.rs
+++ b/tests/ui/deduplicate-diagnostics.rs
@@ -7,4 +7,5 @@ struct S;
 
 #[deny("literal")] //~ ERROR malformed lint attribute input
                    //[duplicate]~| ERROR malformed lint attribute input
+                   //[duplicate]~| ERROR malformed lint attribute input
 fn main() {}
diff --git a/tests/ui/tool-attributes/tool_lints.rs b/tests/ui/tool-attributes/tool_lints.rs
index ef27532f6de..9e4aa7a939a 100644
--- a/tests/ui/tool-attributes/tool_lints.rs
+++ b/tests/ui/tool-attributes/tool_lints.rs
@@ -1,4 +1,5 @@
 #[warn(foo::bar)]
 //~^ ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
 //~| ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
+//~| ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
 fn main() {}
diff --git a/tests/ui/tool-attributes/tool_lints.stderr b/tests/ui/tool-attributes/tool_lints.stderr
index f1d825caba1..eee0a9784ec 100644
--- a/tests/ui/tool-attributes/tool_lints.stderr
+++ b/tests/ui/tool-attributes/tool_lints.stderr
@@ -15,6 +15,15 @@ LL | #[warn(foo::bar)]
    = help: add `#![register_tool(foo)]` to the crate root
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
-error: aborting due to 2 previous errors
+error[E0710]: unknown tool name `foo` found in scoped lint: `foo::bar`
+  --> $DIR/tool_lints.rs:1:8
+   |
+LL | #[warn(foo::bar)]
+   |        ^^^
+   |
+   = help: add `#![register_tool(foo)]` to the crate root
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 3 previous errors
 
 For more information about this error, try `rustc --explain E0710`.
diff --git a/tests/ui/tool-attributes/unknown-lint-tool-name.rs b/tests/ui/tool-attributes/unknown-lint-tool-name.rs
index 59fc56d820e..84ab7c1944a 100644
--- a/tests/ui/tool-attributes/unknown-lint-tool-name.rs
+++ b/tests/ui/tool-attributes/unknown-lint-tool-name.rs
@@ -4,4 +4,5 @@
 
 #[allow(foo::bar)] //~ ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
                    //~| ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
+                   //~| ERROR unknown tool name `foo` found in scoped lint: `foo::bar`
 fn main() {}
diff --git a/tests/ui/tool-attributes/unknown-lint-tool-name.stderr b/tests/ui/tool-attributes/unknown-lint-tool-name.stderr
index 5d99777a14a..91baf882737 100644
--- a/tests/ui/tool-attributes/unknown-lint-tool-name.stderr
+++ b/tests/ui/tool-attributes/unknown-lint-tool-name.stderr
@@ -41,6 +41,15 @@ LL | #![deny(foo::bar)]
    = help: add `#![register_tool(foo)]` to the crate root
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
-error: aborting due to 5 previous errors
+error[E0710]: unknown tool name `foo` found in scoped lint: `foo::bar`
+  --> $DIR/unknown-lint-tool-name.rs:5:9
+   |
+LL | #[allow(foo::bar)]
+   |         ^^^
+   |
+   = help: add `#![register_tool(foo)]` to the crate root
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 6 previous errors
 
 For more information about this error, try `rustc --explain E0710`.