about summary refs log tree commit diff
diff options
context:
space:
mode:
authoryukang <moorekang@gmail.com>2024-01-11 00:24:46 +0800
committeryukang <moorekang@gmail.com>2024-01-12 18:50:36 +0800
commitca421fe1d3cba0dbd1b0dcc52c3d040fce63971a (patch)
treed683810ebd784e4a2e576adf7b32bb2f25d6ff65
parente9271846294c4ee5bd7706df68180320c0b5ff20 (diff)
downloadrust-ca421fe1d3cba0dbd1b0dcc52c3d040fce63971a.tar.gz
rust-ca421fe1d3cba0dbd1b0dcc52c3d040fce63971a.zip
check rust lints when an unknown lint is detected
-rw-r--r--compiler/rustc_lint/messages.ftl10
-rw-r--r--compiler/rustc_lint/src/context.rs24
-rw-r--r--compiler/rustc_lint/src/levels.rs9
-rw-r--r--compiler/rustc_lint/src/lints.rs3
-rw-r--r--compiler/rustc_span/src/edit_distance.rs28
-rw-r--r--src/tools/clippy/tests/ui/unknown_clippy_lints.fixed6
-rw-r--r--src/tools/clippy/tests/ui/unknown_clippy_lints.rs4
-rw-r--r--src/tools/clippy/tests/ui/unknown_clippy_lints.stderr20
8 files changed, 85 insertions, 19 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index 40e6b1b579f..b6fa2f1f221 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -532,8 +532,14 @@ lint_unknown_gated_lint =
 
 lint_unknown_lint =
     unknown lint: `{$name}`
-    .suggestion = did you mean
-    .help = did you mean: `{$replace}`
+    .suggestion = {$from_rustc ->
+        [true] a lint with a similar name exists in `rustc` lints
+        *[false] did you mean
+    }
+    .help = {$from_rustc ->
+        [true] a lint with a similar name exists in `rustc` lints: `{$replace}`
+        *[false] did you mean: `{$replace}`
+    }
 
 lint_unknown_tool_in_scoped_lint = unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`
     .help = add `#![register_tool({$tool_name})]` to the crate root
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index d0fd019a8b1..ffd8f1b3c79 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -33,7 +33,7 @@ use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, Ty
 use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
 use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
 use rustc_session::{LintStoreMarker, Session};
-use rustc_span::edit_distance::find_best_match_for_name;
+use rustc_span::edit_distance::find_best_match_for_names;
 use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
 use rustc_target::abi;
@@ -117,7 +117,7 @@ struct LintGroup {
 pub enum CheckLintNameResult<'a> {
     Ok(&'a [LintId]),
     /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
-    NoLint(Option<Symbol>),
+    NoLint(Option<(Symbol, bool)>),
     /// The lint refers to a tool that has not been registered.
     NoTool,
     /// The lint has been renamed to a new name.
@@ -377,7 +377,7 @@ impl LintStore {
                         debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>());
                         let tool_prefix = format!("{tool_name}::");
                         return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) {
-                            self.no_lint_suggestion(&complete_name)
+                            self.no_lint_suggestion(&complete_name, tool_name.as_str())
                         } else {
                             // 2. The tool isn't currently running, so no lints will be registered.
                             // To avoid giving a false positive, ignore all unknown lints.
@@ -419,13 +419,14 @@ impl LintStore {
         }
     }
 
-    fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> {
+    fn no_lint_suggestion(&self, lint_name: &str, tool_name: &str) -> CheckLintNameResult<'_> {
         let name_lower = lint_name.to_lowercase();
 
         if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() {
             // First check if the lint name is (partly) in upper case instead of lower case...
-            return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower)));
+            return CheckLintNameResult::NoLint(Some((Symbol::intern(&name_lower), false)));
         }
+
         // ...if not, search for lints with a similar name
         // Note: find_best_match_for_name depends on the sort order of its input vector.
         // To ensure deterministic output, sort elements of the lint_groups hash map.
@@ -441,7 +442,16 @@ impl LintStore {
         let groups = groups.iter().map(|k| Symbol::intern(k));
         let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower()));
         let names: Vec<Symbol> = groups.chain(lints).collect();
-        let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None);
+        let mut lookups = vec![Symbol::intern(&name_lower)];
+        if let Some(stripped) = name_lower.split("::").last() {
+            lookups.push(Symbol::intern(stripped));
+        }
+        let res = find_best_match_for_names(&names, &lookups, None);
+        let is_rustc = res.map_or_else(
+            || false,
+            |s| name_lower.contains("::") && !s.as_str().starts_with(tool_name),
+        );
+        let suggestion = res.map(|s| (s, is_rustc));
         CheckLintNameResult::NoLint(suggestion)
     }
 
@@ -454,7 +464,7 @@ impl LintStore {
         match self.by_name.get(&complete_name) {
             None => match self.lint_groups.get(&*complete_name) {
                 // Now we are sure, that this lint exists nowhere
-                None => self.no_lint_suggestion(lint_name),
+                None => self.no_lint_suggestion(lint_name, tool_name),
                 Some(LintGroup { lint_ids, depr, .. }) => {
                     // Reaching this would be weird, but let's cover this case anyway
                     if let Some(LintAlias { name, silent }) = depr {
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 49821437b76..3c2d0c7b205 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -582,8 +582,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 }
                 CheckLintNameResult::NoLint(suggestion) => {
                     let name = lint_name.clone();
-                    let suggestion =
-                        suggestion.map(|replace| UnknownLintSuggestion::WithoutSpan { replace });
+                    let suggestion = suggestion.map(|(replace, from_rustc)| {
+                        UnknownLintSuggestion::WithoutSpan { replace, from_rustc }
+                    });
                     let requested_level = RequestedLevel { level, lint_name };
                     let lint = UnknownLintFromCommandLine { name, suggestion, requested_level };
                     self.emit_lint(UNKNOWN_LINTS, lint);
@@ -990,8 +991,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                         } else {
                             name.to_string()
                         };
-                        let suggestion = suggestion.map(|replace| {
-                            UnknownLintSuggestion::WithSpan { suggestion: sp, replace }
+                        let suggestion = suggestion.map(|(replace, from_rustc)| {
+                            UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc }
                         });
                         let lint = UnknownLint { name, suggestion };
                         self.emit_spanned_lint(UNKNOWN_LINTS, sp.into(), lint);
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index bc9a9d7b745..f370c4392b3 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -1050,9 +1050,10 @@ pub enum UnknownLintSuggestion {
         #[primary_span]
         suggestion: Span,
         replace: Symbol,
+        from_rustc: bool,
     },
     #[help(lint_help)]
-    WithoutSpan { replace: Symbol },
+    WithoutSpan { replace: Symbol, from_rustc: bool },
 }
 
 #[derive(LintDiagnostic)]
diff --git a/compiler/rustc_span/src/edit_distance.rs b/compiler/rustc_span/src/edit_distance.rs
index 14cb1d6d362..87a0ccbb1a5 100644
--- a/compiler/rustc_span/src/edit_distance.rs
+++ b/compiler/rustc_span/src/edit_distance.rs
@@ -170,6 +170,34 @@ pub fn find_best_match_for_name(
     find_best_match_for_name_impl(false, candidates, lookup, dist)
 }
 
+/// Find the best match for multiple words
+///
+/// This function is intended for use when the desired match would never be
+/// returned due to a substring in `lookup` which is superfluous.
+///
+/// For example, when looking for the closest lint name to `clippy:missing_docs`,
+/// we would find `clippy::erasing_op`, despite `missing_docs` existing and being a better suggestion.
+/// `missing_docs` would have a larger edit distance because it does not contain the `clippy` tool prefix.
+/// In order to find `missing_docs`, this function takes multiple lookup strings, computes the best match
+/// for each and returns the match which had the lowest edit distance. In our example, `clippy:missing_docs` and
+/// `missing_docs` would be `lookups`, enabling `missing_docs` to be the best match, as desired.
+pub fn find_best_match_for_names(
+    candidates: &[Symbol],
+    lookups: &[Symbol],
+    dist: Option<usize>,
+) -> Option<Symbol> {
+    lookups
+        .iter()
+        .map(|s| (s, find_best_match_for_name_impl(false, candidates, *s, dist)))
+        .filter_map(|(s, r)| r.map(|r| (s, r)))
+        .min_by(|(s1, r1), (s2, r2)| {
+            let d1 = edit_distance(s1.as_str(), r1.as_str(), usize::MAX).unwrap();
+            let d2 = edit_distance(s2.as_str(), r2.as_str(), usize::MAX).unwrap();
+            d1.cmp(&d2)
+        })
+        .map(|(_, r)| r)
+}
+
 #[cold]
 fn find_best_match_for_name_impl(
     use_substring_score: bool,
diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed
index cba32ce6b77..c0ccd41c19a 100644
--- a/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed
+++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed
@@ -7,10 +7,12 @@
 #[warn(clippy::if_not_else)]
 #[warn(clippy::unnecessary_cast)]
 #[warn(clippy::useless_transmute)]
-// Shouldn't suggest rustc lint name(`dead_code`)
-#[warn(clippy::eq_op)]
+// Should suggest rustc lint name(`dead_code`)
+#[warn(dead_code)]
 // Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`)
 #[warn(clippy::unused_self)]
 // Shouldn't suggest renamed clippy lint name(`const_static_lifetime`)
 #[warn(clippy::redundant_static_lifetimes)]
+// issue #118183, should report `missing_docs` from rustc lint
+#[warn(missing_docs)]
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.rs b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs
index c15d541974f..7a59ad364aa 100644
--- a/src/tools/clippy/tests/ui/unknown_clippy_lints.rs
+++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs
@@ -7,10 +7,12 @@
 #[warn(clippy::if_not_els)]
 #[warn(clippy::UNNecsaRy_cAst)]
 #[warn(clippy::useles_transute)]
-// Shouldn't suggest rustc lint name(`dead_code`)
+// Should suggest rustc lint name(`dead_code`)
 #[warn(clippy::dead_cod)]
 // Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`)
 #[warn(clippy::unused_colle)]
 // Shouldn't suggest renamed clippy lint name(`const_static_lifetime`)
 #[warn(clippy::const_static_lifetim)]
+// issue #118183, should report `missing_docs` from rustc lint
+#[warn(clippy::missing_docs)]
 fn main() {}
diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr
index ee82db31c2c..432c7f72e32 100644
--- a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr
+++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr
@@ -35,7 +35,12 @@ error: unknown lint: `clippy::dead_cod`
   --> $DIR/unknown_clippy_lints.rs:11:8
    |
 LL | #[warn(clippy::dead_cod)]
-   |        ^^^^^^^^^^^^^^^^ help: did you mean: `clippy::eq_op`
+   |        ^^^^^^^^^^^^^^^^
+   |
+help: a lint with a similar name exists in `rustc` lints
+   |
+LL | #[warn(dead_code)]
+   |        ~~~~~~~~~
 
 error: unknown lint: `clippy::unused_colle`
   --> $DIR/unknown_clippy_lints.rs:13:8
@@ -49,5 +54,16 @@ error: unknown lint: `clippy::const_static_lifetim`
 LL | #[warn(clippy::const_static_lifetim)]
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::redundant_static_lifetimes`
 
-error: aborting due to 8 previous errors
+error: unknown lint: `clippy::missing_docs`
+  --> $DIR/unknown_clippy_lints.rs:17:8
+   |
+LL | #[warn(clippy::missing_docs)]
+   |        ^^^^^^^^^^^^^^^^^^^^
+   |
+help: a lint with a similar name exists in `rustc` lints
+   |
+LL | #[warn(missing_docs)]
+   |        ~~~~~~~~~~~~
+
+error: aborting due to 9 previous errors