about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_lint/src/context.rs33
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs5
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/duplicate_mod.rs22
-rw-r--r--src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/d.rs0
-rw-r--r--src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.rs13
-rw-r--r--src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.stderr19
7 files changed, 92 insertions, 7 deletions
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 83328093e9f..5725c240320 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -34,7 +34,7 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt};
-use rustc_session::lint::BuiltinLintDiagnostics;
+use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
 use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
 use rustc_session::Session;
 use rustc_span::lev_distance::find_best_match_for_name;
@@ -906,6 +906,29 @@ pub trait LintContext: Sized {
     ) {
         self.lookup(lint, None as Option<Span>, decorate);
     }
+
+    /// This returns the lint level for the given lint at the current location.
+    fn get_lint_level(&self, lint: &'static Lint) -> Level;
+
+    /// This function can be used to manually fulfill an expectation. This can
+    /// be used for lints which contain several spans, and should be suppressed,
+    /// if either location was marked with an expectation.
+    ///
+    /// Note that this function should only be called for [`LintExpectationId`]s
+    /// retrieved from the current lint pass. Buffered or manually created ids can
+    /// cause ICEs.
+    fn fulfill_expectation(&self, expectation: LintExpectationId) {
+        // We need to make sure that submitted expectation ids are correctly fulfilled suppressed
+        // and stored between compilation sessions. To not manually do these steps, we simply create
+        // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal
+        // expected lint diagnostic.
+        self.sess()
+            .struct_expect(
+                "this is a dummy diagnostic, to submit and store an expectation",
+                expectation,
+            )
+            .emit();
+    }
 }
 
 impl<'a> EarlyContext<'a> {
@@ -953,6 +976,10 @@ impl LintContext for LateContext<'_> {
             None => self.tcx.struct_lint_node(lint, hir_id, decorate),
         }
     }
+
+    fn get_lint_level(&self, lint: &'static Lint) -> Level {
+        self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0
+    }
 }
 
 impl LintContext for EarlyContext<'_> {
@@ -975,6 +1002,10 @@ impl LintContext for EarlyContext<'_> {
     ) {
         self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
     }
+
+    fn get_lint_level(&self, lint: &'static Lint) -> Level {
+        self.builder.lint_level(lint).0
+    }
 }
 
 impl<'tcx> LateContext<'tcx> {
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 40601bb5aad..9fc2249b290 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -520,6 +520,11 @@ declare_lint! {
     /// The `expect` attribute can be removed if this is intended behavior otherwise
     /// it should be investigated why the expected lint is no longer issued.
     ///
+    /// In rare cases, the expectation might be emitted at a different location than
+    /// shown in the shown code snippet. In most cases, the `#[expect]` attribute
+    /// works when added to the outer scope. A few lints can only be expected
+    /// on a crate level.
+    ///
     /// Part of RFC 2383. The progress is being tracked in [#54503]
     ///
     /// [#54503]: https://github.com/rust-lang/rust/issues/54503
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 1cd19c7eaab..48f441e69d6 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -232,6 +232,13 @@ impl Level {
             Level::Deny | Level::Forbid => true,
         }
     }
+
+    pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
+        match self {
+            Level::Expect(id) | Level::ForceWarn(Some(id)) => Some(*id),
+            _ => None,
+        }
+    }
 }
 
 /// Specification of a single lint.
diff --git a/src/tools/clippy/clippy_lints/src/duplicate_mod.rs b/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
index c6c7b959d4f..4f49bb879f5 100644
--- a/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
+++ b/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_help;
 use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
 use rustc_errors::MultiSpan;
-use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Level};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::{FileName, Span};
 use std::collections::BTreeMap;
@@ -49,6 +49,7 @@ declare_clippy_lint! {
 struct Modules {
     local_path: PathBuf,
     spans: Vec<Span>,
+    lint_levels: Vec<Level>,
 }
 
 #[derive(Default)]
@@ -70,13 +71,30 @@ impl EarlyLintPass for DuplicateMod {
             let modules = self.modules.entry(absolute_path).or_insert(Modules {
                 local_path,
                 spans: Vec::new(),
+                lint_levels: Vec::new(),
             });
             modules.spans.push(item.span_with_attributes());
+            modules.lint_levels.push(cx.get_lint_level(DUPLICATE_MOD));
         }
     }
 
     fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
-        for Modules { local_path, spans } in self.modules.values() {
+        for Modules { local_path, spans, lint_levels } in self.modules.values() {
+            if spans.len() < 2 {
+                continue;
+            }
+
+            // At this point the lint would be emitted
+            assert_eq!(spans.len(), lint_levels.len());
+            let spans: Vec<_> = spans.into_iter().zip(lint_levels).filter_map(|(span, lvl)|{
+                if let Some(id) = lvl.get_expectation_id() {
+                    cx.fulfill_expectation(id);
+                }
+
+                (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
+            })
+            .collect();
+
             if spans.len() < 2 {
                 continue;
             }
diff --git a/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/d.rs b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/d.rs
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/d.rs
diff --git a/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.rs
index 79b343da247..99ca538b6e4 100644
--- a/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.rs
+++ b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.rs
@@ -1,3 +1,5 @@
+#[feature(lint_reasons)]
+
 mod a;
 
 mod b;
@@ -13,4 +15,15 @@ mod c3;
 mod from_other_module;
 mod other_module;
 
+mod d;
+#[path = "d.rs"]
+mod d2;
+#[path = "d.rs"]
+#[expect(clippy::duplicate_mod)]
+mod d3;
+#[path = "d.rs"]
+#[allow(clippy::duplicate_mod)]
+mod d4;
+
+
 fn main() {}
diff --git a/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.stderr b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.stderr
index 00d7739c8a2..61df1ad5d50 100644
--- a/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.stderr
+++ b/src/tools/clippy/tests/ui-cargo/duplicate_mod/fail/src/main.stderr
@@ -1,5 +1,5 @@
 error: file is loaded as a module multiple times: `$DIR/b.rs`
-  --> $DIR/main.rs:3:1
+  --> $DIR/main.rs:5:1
    |
 LL |   mod b;
    |   ^^^^^^ first loaded here
@@ -11,7 +11,7 @@ LL | | mod b2;
    = help: replace all but one `mod` item with `use` items
 
 error: file is loaded as a module multiple times: `$DIR/c.rs`
-  --> $DIR/main.rs:7:1
+  --> $DIR/main.rs:9:1
    |
 LL |   mod c;
    |   ^^^^^^ first loaded here
@@ -25,7 +25,7 @@ LL | | mod c3;
    = help: replace all but one `mod` item with `use` items
 
 error: file is loaded as a module multiple times: `$DIR/from_other_module.rs`
-  --> $DIR/main.rs:13:1
+  --> $DIR/main.rs:15:1
    |
 LL |   mod from_other_module;
    |   ^^^^^^^^^^^^^^^^^^^^^^ first loaded here
@@ -38,5 +38,16 @@ LL | | mod m;
    |
    = help: replace all but one `mod` item with `use` items
 
-error: aborting due to 3 previous errors
+error: file is loaded as a module multiple times: `$DIR/b.rs`
+  --> $DIR/main.rs:18:1
+   |
+LL |   mod d;
+   |   ^^^^^^ first loaded here
+LL | / #[path = "d.rs"]
+LL | | mod d2;
+   | |_______^ loaded again here
+   |
+   = help: replace all but one `mod` item with `use` items
+
+error: aborting due to 4 previous errors