Skip to content

Commit ca421fe

Browse files
committed
check rust lints when an unknown lint is detected
1 parent e927184 commit ca421fe

File tree

8 files changed

+85
-19
lines changed

8 files changed

+85
-19
lines changed

compiler/rustc_lint/messages.ftl

+8-2
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,14 @@ lint_unknown_gated_lint =
532532
533533
lint_unknown_lint =
534534
unknown lint: `{$name}`
535-
.suggestion = did you mean
536-
.help = did you mean: `{$replace}`
535+
.suggestion = {$from_rustc ->
536+
[true] a lint with a similar name exists in `rustc` lints
537+
*[false] did you mean
538+
}
539+
.help = {$from_rustc ->
540+
[true] a lint with a similar name exists in `rustc` lints: `{$replace}`
541+
*[false] did you mean: `{$replace}`
542+
}
537543
538544
lint_unknown_tool_in_scoped_lint = unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`
539545
.help = add `#![register_tool({$tool_name})]` to the crate root

compiler/rustc_lint/src/context.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, Ty
3333
use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
3434
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
3535
use rustc_session::{LintStoreMarker, Session};
36-
use rustc_span::edit_distance::find_best_match_for_name;
36+
use rustc_span::edit_distance::find_best_match_for_names;
3737
use rustc_span::symbol::{sym, Ident, Symbol};
3838
use rustc_span::Span;
3939
use rustc_target::abi;
@@ -117,7 +117,7 @@ struct LintGroup {
117117
pub enum CheckLintNameResult<'a> {
118118
Ok(&'a [LintId]),
119119
/// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
120-
NoLint(Option<Symbol>),
120+
NoLint(Option<(Symbol, bool)>),
121121
/// The lint refers to a tool that has not been registered.
122122
NoTool,
123123
/// The lint has been renamed to a new name.
@@ -377,7 +377,7 @@ impl LintStore {
377377
debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>());
378378
let tool_prefix = format!("{tool_name}::");
379379
return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) {
380-
self.no_lint_suggestion(&complete_name)
380+
self.no_lint_suggestion(&complete_name, tool_name.as_str())
381381
} else {
382382
// 2. The tool isn't currently running, so no lints will be registered.
383383
// To avoid giving a false positive, ignore all unknown lints.
@@ -419,13 +419,14 @@ impl LintStore {
419419
}
420420
}
421421

422-
fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> {
422+
fn no_lint_suggestion(&self, lint_name: &str, tool_name: &str) -> CheckLintNameResult<'_> {
423423
let name_lower = lint_name.to_lowercase();
424424

425425
if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() {
426426
// First check if the lint name is (partly) in upper case instead of lower case...
427-
return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower)));
427+
return CheckLintNameResult::NoLint(Some((Symbol::intern(&name_lower), false)));
428428
}
429+
429430
// ...if not, search for lints with a similar name
430431
// Note: find_best_match_for_name depends on the sort order of its input vector.
431432
// To ensure deterministic output, sort elements of the lint_groups hash map.
@@ -441,7 +442,16 @@ impl LintStore {
441442
let groups = groups.iter().map(|k| Symbol::intern(k));
442443
let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower()));
443444
let names: Vec<Symbol> = groups.chain(lints).collect();
444-
let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None);
445+
let mut lookups = vec![Symbol::intern(&name_lower)];
446+
if let Some(stripped) = name_lower.split("::").last() {
447+
lookups.push(Symbol::intern(stripped));
448+
}
449+
let res = find_best_match_for_names(&names, &lookups, None);
450+
let is_rustc = res.map_or_else(
451+
|| false,
452+
|s| name_lower.contains("::") && !s.as_str().starts_with(tool_name),
453+
);
454+
let suggestion = res.map(|s| (s, is_rustc));
445455
CheckLintNameResult::NoLint(suggestion)
446456
}
447457

@@ -454,7 +464,7 @@ impl LintStore {
454464
match self.by_name.get(&complete_name) {
455465
None => match self.lint_groups.get(&*complete_name) {
456466
// Now we are sure, that this lint exists nowhere
457-
None => self.no_lint_suggestion(lint_name),
467+
None => self.no_lint_suggestion(lint_name, tool_name),
458468
Some(LintGroup { lint_ids, depr, .. }) => {
459469
// Reaching this would be weird, but let's cover this case anyway
460470
if let Some(LintAlias { name, silent }) = depr {

compiler/rustc_lint/src/levels.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
582582
}
583583
CheckLintNameResult::NoLint(suggestion) => {
584584
let name = lint_name.clone();
585-
let suggestion =
586-
suggestion.map(|replace| UnknownLintSuggestion::WithoutSpan { replace });
585+
let suggestion = suggestion.map(|(replace, from_rustc)| {
586+
UnknownLintSuggestion::WithoutSpan { replace, from_rustc }
587+
});
587588
let requested_level = RequestedLevel { level, lint_name };
588589
let lint = UnknownLintFromCommandLine { name, suggestion, requested_level };
589590
self.emit_lint(UNKNOWN_LINTS, lint);
@@ -990,8 +991,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
990991
} else {
991992
name.to_string()
992993
};
993-
let suggestion = suggestion.map(|replace| {
994-
UnknownLintSuggestion::WithSpan { suggestion: sp, replace }
994+
let suggestion = suggestion.map(|(replace, from_rustc)| {
995+
UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc }
995996
});
996997
let lint = UnknownLint { name, suggestion };
997998
self.emit_spanned_lint(UNKNOWN_LINTS, sp.into(), lint);

compiler/rustc_lint/src/lints.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1050,9 +1050,10 @@ pub enum UnknownLintSuggestion {
10501050
#[primary_span]
10511051
suggestion: Span,
10521052
replace: Symbol,
1053+
from_rustc: bool,
10531054
},
10541055
#[help(lint_help)]
1055-
WithoutSpan { replace: Symbol },
1056+
WithoutSpan { replace: Symbol, from_rustc: bool },
10561057
}
10571058

10581059
#[derive(LintDiagnostic)]

compiler/rustc_span/src/edit_distance.rs

+28
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ pub fn find_best_match_for_name(
170170
find_best_match_for_name_impl(false, candidates, lookup, dist)
171171
}
172172

173+
/// Find the best match for multiple words
174+
///
175+
/// This function is intended for use when the desired match would never be
176+
/// returned due to a substring in `lookup` which is superfluous.
177+
///
178+
/// For example, when looking for the closest lint name to `clippy:missing_docs`,
179+
/// we would find `clippy::erasing_op`, despite `missing_docs` existing and being a better suggestion.
180+
/// `missing_docs` would have a larger edit distance because it does not contain the `clippy` tool prefix.
181+
/// In order to find `missing_docs`, this function takes multiple lookup strings, computes the best match
182+
/// for each and returns the match which had the lowest edit distance. In our example, `clippy:missing_docs` and
183+
/// `missing_docs` would be `lookups`, enabling `missing_docs` to be the best match, as desired.
184+
pub fn find_best_match_for_names(
185+
candidates: &[Symbol],
186+
lookups: &[Symbol],
187+
dist: Option<usize>,
188+
) -> Option<Symbol> {
189+
lookups
190+
.iter()
191+
.map(|s| (s, find_best_match_for_name_impl(false, candidates, *s, dist)))
192+
.filter_map(|(s, r)| r.map(|r| (s, r)))
193+
.min_by(|(s1, r1), (s2, r2)| {
194+
let d1 = edit_distance(s1.as_str(), r1.as_str(), usize::MAX).unwrap();
195+
let d2 = edit_distance(s2.as_str(), r2.as_str(), usize::MAX).unwrap();
196+
d1.cmp(&d2)
197+
})
198+
.map(|(_, r)| r)
199+
}
200+
173201
#[cold]
174202
fn find_best_match_for_name_impl(
175203
use_substring_score: bool,

src/tools/clippy/tests/ui/unknown_clippy_lints.fixed

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
#[warn(clippy::if_not_else)]
88
#[warn(clippy::unnecessary_cast)]
99
#[warn(clippy::useless_transmute)]
10-
// Shouldn't suggest rustc lint name(`dead_code`)
11-
#[warn(clippy::eq_op)]
10+
// Should suggest rustc lint name(`dead_code`)
11+
#[warn(dead_code)]
1212
// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`)
1313
#[warn(clippy::unused_self)]
1414
// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`)
1515
#[warn(clippy::redundant_static_lifetimes)]
16+
// issue #118183, should report `missing_docs` from rustc lint
17+
#[warn(missing_docs)]
1618
fn main() {}

src/tools/clippy/tests/ui/unknown_clippy_lints.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
#[warn(clippy::if_not_els)]
88
#[warn(clippy::UNNecsaRy_cAst)]
99
#[warn(clippy::useles_transute)]
10-
// Shouldn't suggest rustc lint name(`dead_code`)
10+
// Should suggest rustc lint name(`dead_code`)
1111
#[warn(clippy::dead_cod)]
1212
// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`)
1313
#[warn(clippy::unused_colle)]
1414
// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`)
1515
#[warn(clippy::const_static_lifetim)]
16+
// issue #118183, should report `missing_docs` from rustc lint
17+
#[warn(clippy::missing_docs)]
1618
fn main() {}

src/tools/clippy/tests/ui/unknown_clippy_lints.stderr

+18-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ error: unknown lint: `clippy::dead_cod`
3535
--> $DIR/unknown_clippy_lints.rs:11:8
3636
|
3737
LL | #[warn(clippy::dead_cod)]
38-
| ^^^^^^^^^^^^^^^^ help: did you mean: `clippy::eq_op`
38+
| ^^^^^^^^^^^^^^^^
39+
|
40+
help: a lint with a similar name exists in `rustc` lints
41+
|
42+
LL | #[warn(dead_code)]
43+
| ~~~~~~~~~
3944

4045
error: unknown lint: `clippy::unused_colle`
4146
--> $DIR/unknown_clippy_lints.rs:13:8
@@ -49,5 +54,16 @@ error: unknown lint: `clippy::const_static_lifetim`
4954
LL | #[warn(clippy::const_static_lifetim)]
5055
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::redundant_static_lifetimes`
5156

52-
error: aborting due to 8 previous errors
57+
error: unknown lint: `clippy::missing_docs`
58+
--> $DIR/unknown_clippy_lints.rs:17:8
59+
|
60+
LL | #[warn(clippy::missing_docs)]
61+
| ^^^^^^^^^^^^^^^^^^^^
62+
|
63+
help: a lint with a similar name exists in `rustc` lints
64+
|
65+
LL | #[warn(missing_docs)]
66+
| ~~~~~~~~~~~~
67+
68+
error: aborting due to 9 previous errors
5369

0 commit comments

Comments
 (0)