Skip to content

Commit 11da267

Browse files
committed
Auto merge of rust-lang#112239 - jieyouxu:targeted-no-method-suggestions, r=cjgillot
Add `#[rustc_confusables]` attribute to allow targeted "no method" error suggestions on standard library types After this PR, the standard library developer can annotate methods on e.g. `BTreeSet::push` with `#[rustc_confusables("insert")]`. When the user mistypes `btreeset.push()`, `BTreeSet::insert` will be suggested if there are no other candidates to suggest. This PR lays the foundations for contributors to add `rustc_confusables` annotations to standard library types for targeted suggestions, as specified in rust-lang#59450, or to address cases such as rust-lang#108437. ### Example Assume `BTreeSet` is the standard library type: ``` // Standard library definition #![feature(rustc_attrs)] struct BTreeSet; impl BTreeSet { #[rustc_confusables("push")] fn insert(&self) {} } // User code fn main() { let x = BTreeSet {}; x.push(); } ``` A new suggestion (which has lower precedence than suggestions for misspellings and only is shown when there are no misspellings suggestions) will be added to hint the user maybe they intended to write `x.insert()` instead: ``` error[E0599]: no method named `push` found for struct `BTreeSet` in the current scope --> test.rs:12:7 | 3 | struct BTreeSet; | --------------- method `push` not found for this struct ... 12 | x.push(); | ^^^^ method not found in `BTreeSet` | help: you might have meant to use `insert` | 12 | x.insert(); | ~~~~~~ error: aborting due to previous error ```
2 parents 4a07b2b + 08c77a6 commit 11da267

File tree

12 files changed

+259
-4
lines changed

12 files changed

+259
-4
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3665,6 +3665,7 @@ name = "rustc_hir_typeck"
36653665
version = "0.1.0"
36663666
dependencies = [
36673667
"rustc_ast",
3668+
"rustc_attr",
36683669
"rustc_data_structures",
36693670
"rustc_errors",
36703671
"rustc_fluent_macro",

compiler/rustc_attr/src/builtin.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1217,3 +1217,20 @@ pub fn parse_alignment(node: &ast::LitKind) -> Result<u32, &'static str> {
12171217
Err("not an unsuffixed integer")
12181218
}
12191219
}
1220+
1221+
/// Read the content of a `rustc_confusables` attribute, and return the list of candidate names.
1222+
pub fn parse_confusables(attr: &Attribute) -> Option<Vec<Symbol>> {
1223+
let meta = attr.meta()?;
1224+
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
1225+
1226+
let mut candidates = Vec::new();
1227+
1228+
for meta in metas {
1229+
let NestedMetaItem::Lit(meta_lit) = meta else {
1230+
return None;
1231+
};
1232+
candidates.push(meta_lit.symbol);
1233+
}
1234+
1235+
return Some(candidates);
1236+
}

compiler/rustc_feature/src/builtin_attrs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
625625
ErrorFollowing,
626626
INTERNAL_UNSTABLE
627627
),
628+
rustc_attr!(
629+
rustc_confusables, Normal,
630+
template!(List: r#""name1", "name2", ..."#),
631+
ErrorFollowing,
632+
INTERNAL_UNSTABLE,
633+
),
628634
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
629635
rustc_attr!(
630636
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE

compiler/rustc_hir_typeck/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ edition = "2021"
99
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
1010
tracing = "0.1"
1111
rustc_ast = { path = "../rustc_ast" }
12+
rustc_attr = { path = "../rustc_attr" }
1213
rustc_data_structures = { path = "../rustc_data_structures" }
1314
rustc_errors = { path = "../rustc_errors" }
1415
rustc_graphviz = { path = "../rustc_graphviz" }

compiler/rustc_hir_typeck/src/method/suggest.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
//! found or is otherwise invalid.
33
44
use crate::errors;
5-
use crate::errors::CandidateTraitNote;
6-
use crate::errors::NoAssociatedItem;
5+
use crate::errors::{CandidateTraitNote, NoAssociatedItem};
76
use crate::Expectation;
87
use crate::FnCtxt;
98
use rustc_ast::ast::Mutability;
10-
use rustc_data_structures::fx::FxIndexMap;
11-
use rustc_data_structures::fx::FxIndexSet;
9+
use rustc_attr::parse_confusables;
10+
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
1211
use rustc_data_structures::unord::UnordSet;
1312
use rustc_errors::StashKey;
1413
use rustc_errors::{
@@ -1038,6 +1037,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10381037
"the {item_kind} was found for\n{}{}",
10391038
type_candidates, additional_types
10401039
));
1040+
} else {
1041+
'outer: for inherent_impl_did in self.tcx.inherent_impls(adt.did()) {
1042+
for inherent_method in
1043+
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1044+
{
1045+
if let Some(attr) = self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
1046+
&& let Some(candidates) = parse_confusables(attr)
1047+
&& candidates.contains(&item_name.name)
1048+
{
1049+
err.span_suggestion_verbose(
1050+
item_name.span,
1051+
format!(
1052+
"you might have meant to use `{}`",
1053+
inherent_method.name.as_str()
1054+
),
1055+
inherent_method.name.as_str(),
1056+
Applicability::MaybeIncorrect,
1057+
);
1058+
break 'outer;
1059+
}
1060+
}
1061+
}
10411062
}
10421063
}
10431064
} else {

compiler/rustc_passes/messages.ftl

+9
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ passes_collapse_debuginfo =
9898
`collapse_debuginfo` attribute should be applied to macro definitions
9999
.label = not a macro definition
100100
101+
passes_confusables = attribute should be applied to an inherent method
102+
.label = not an inherent method
103+
101104
passes_const_impl_const_trait =
102105
const `impl`s must be for traits marked with `#[const_trait]`
103106
.note = this trait must be annotated with `#[const_trait]`
@@ -266,6 +269,9 @@ passes_duplicate_lang_item_crate_depends =
266269
.first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path}
267270
.second_definition_path = second definition in `{$crate_name}` loaded from {$path}
268271
272+
passes_empty_confusables =
273+
expected at least one confusable name
274+
269275
passes_export_name =
270276
attribute should be applied to a free function, impl method or static
271277
.label = not a free function, impl method or static
@@ -326,6 +332,9 @@ passes_implied_feature_not_exist =
326332
passes_incorrect_do_not_recommend_location =
327333
`#[do_not_recommend]` can only be placed on trait implementations
328334
335+
passes_incorrect_meta_item = expected a quoted string literal
336+
passes_incorrect_meta_item_suggestion = consider surrounding this with quotes
337+
329338
passes_incorrect_target =
330339
`{$name}` language item must be applied to a {$kind} with {$at_least ->
331340
[true] at least {$num}

compiler/rustc_passes/src/check_attr.rs

+41
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ impl CheckAttrVisitor<'_> {
183183
| sym::rustc_allowed_through_unstable_modules
184184
| sym::rustc_promotable => self.check_stability_promotable(&attr, span, target),
185185
sym::link_ordinal => self.check_link_ordinal(&attr, span, target),
186+
sym::rustc_confusables => self.check_confusables(&attr, target),
186187
_ => true,
187188
};
188189

@@ -1985,6 +1986,46 @@ impl CheckAttrVisitor<'_> {
19851986
}
19861987
}
19871988

1989+
fn check_confusables(&self, attr: &Attribute, target: Target) -> bool {
1990+
match target {
1991+
Target::Method(MethodKind::Inherent) => {
1992+
let Some(meta) = attr.meta() else {
1993+
return false;
1994+
};
1995+
let ast::MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else {
1996+
return false;
1997+
};
1998+
1999+
let mut candidates = Vec::new();
2000+
2001+
for meta in metas {
2002+
let NestedMetaItem::Lit(meta_lit) = meta else {
2003+
self.tcx.sess.emit_err(errors::IncorrectMetaItem {
2004+
span: meta.span(),
2005+
suggestion: errors::IncorrectMetaItemSuggestion {
2006+
lo: meta.span().shrink_to_lo(),
2007+
hi: meta.span().shrink_to_hi(),
2008+
},
2009+
});
2010+
return false;
2011+
};
2012+
candidates.push(meta_lit.symbol);
2013+
}
2014+
2015+
if candidates.is_empty() {
2016+
self.tcx.sess.emit_err(errors::EmptyConfusables { span: attr.span });
2017+
return false;
2018+
}
2019+
2020+
true
2021+
}
2022+
_ => {
2023+
self.tcx.sess.emit_err(errors::Confusables { attr_span: attr.span });
2024+
false
2025+
}
2026+
}
2027+
}
2028+
19882029
fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) {
19892030
match target {
19902031
Target::Closure | Target::Expression | Target::Statement | Target::Arm => {

compiler/rustc_passes/src/errors.rs

+32
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,38 @@ pub struct LinkOrdinal {
617617
pub attr_span: Span,
618618
}
619619

620+
#[derive(Diagnostic)]
621+
#[diag(passes_confusables)]
622+
pub struct Confusables {
623+
#[primary_span]
624+
pub attr_span: Span,
625+
}
626+
627+
#[derive(Diagnostic)]
628+
#[diag(passes_empty_confusables)]
629+
pub(crate) struct EmptyConfusables {
630+
#[primary_span]
631+
pub span: Span,
632+
}
633+
634+
#[derive(Diagnostic)]
635+
#[diag(passes_incorrect_meta_item, code = "E0539")]
636+
pub(crate) struct IncorrectMetaItem {
637+
#[primary_span]
638+
pub span: Span,
639+
#[subdiagnostic]
640+
pub suggestion: IncorrectMetaItemSuggestion,
641+
}
642+
643+
#[derive(Subdiagnostic)]
644+
#[multipart_suggestion(passes_incorrect_meta_item_suggestion, applicability = "maybe-incorrect")]
645+
pub(crate) struct IncorrectMetaItemSuggestion {
646+
#[suggestion_part(code = "\"")]
647+
pub lo: Span,
648+
#[suggestion_part(code = "\"")]
649+
pub hi: Span,
650+
}
651+
620652
#[derive(Diagnostic)]
621653
#[diag(passes_stability_promotable)]
622654
pub struct StabilityPromotable {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ symbols! {
12651265
rustc_clean,
12661266
rustc_coherence_is_core,
12671267
rustc_coinductive,
1268+
rustc_confusables,
12681269
rustc_const_stable,
12691270
rustc_const_unstable,
12701271
rustc_conversion_suggestion,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![feature(rustc_attrs)]
2+
3+
pub struct BTreeSet;
4+
5+
impl BTreeSet {
6+
#[rustc_confusables("push", "test_b")]
7+
pub fn insert(&self) {}
8+
9+
#[rustc_confusables("pulled")]
10+
pub fn pull(&self) {}
11+
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// aux-build: rustc_confusables_across_crate.rs
2+
3+
#![feature(rustc_attrs)]
4+
5+
extern crate rustc_confusables_across_crate;
6+
7+
use rustc_confusables_across_crate::BTreeSet;
8+
9+
fn main() {
10+
// Misspellings (similarly named methods) take precedence over `rustc_confusables`.
11+
let x = BTreeSet {};
12+
x.inser();
13+
//~^ ERROR no method named
14+
//~| HELP there is a method with a similar name
15+
x.foo();
16+
//~^ ERROR no method named
17+
x.push();
18+
//~^ ERROR no method named
19+
//~| HELP you might have meant to use `insert`
20+
x.test();
21+
//~^ ERROR no method named
22+
x.pulled();
23+
//~^ ERROR no method named
24+
//~| HELP there is a method with a similar name
25+
}
26+
27+
struct Bar;
28+
29+
impl Bar {
30+
#[rustc_confusables()]
31+
//~^ ERROR expected at least one confusable name
32+
fn baz() {}
33+
34+
#[rustc_confusables]
35+
//~^ ERROR malformed `rustc_confusables` attribute input
36+
//~| HELP must be of the form
37+
fn qux() {}
38+
39+
#[rustc_confusables(invalid_meta_item)]
40+
//~^ ERROR expected a quoted string literal
41+
//~| HELP consider surrounding this with quotes
42+
fn quux() {}
43+
}
44+
45+
#[rustc_confusables("blah")]
46+
//~^ ERROR attribute should be applied to an inherent method
47+
fn not_inherent_impl_method() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
error: malformed `rustc_confusables` attribute input
2+
--> $DIR/rustc_confusables.rs:34:5
3+
|
4+
LL | #[rustc_confusables]
5+
| ^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]`
6+
7+
error: attribute should be applied to an inherent method
8+
--> $DIR/rustc_confusables.rs:45:1
9+
|
10+
LL | #[rustc_confusables("blah")]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: expected at least one confusable name
14+
--> $DIR/rustc_confusables.rs:30:5
15+
|
16+
LL | #[rustc_confusables()]
17+
| ^^^^^^^^^^^^^^^^^^^^^^
18+
19+
error[E0539]: expected a quoted string literal
20+
--> $DIR/rustc_confusables.rs:39:25
21+
|
22+
LL | #[rustc_confusables(invalid_meta_item)]
23+
| ^^^^^^^^^^^^^^^^^
24+
|
25+
help: consider surrounding this with quotes
26+
|
27+
LL | #[rustc_confusables("invalid_meta_item")]
28+
| + +
29+
30+
error[E0599]: no method named `inser` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
31+
--> $DIR/rustc_confusables.rs:12:7
32+
|
33+
LL | x.inser();
34+
| ^^^^^ help: there is a method with a similar name: `insert`
35+
36+
error[E0599]: no method named `foo` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
37+
--> $DIR/rustc_confusables.rs:15:7
38+
|
39+
LL | x.foo();
40+
| ^^^ method not found in `BTreeSet`
41+
42+
error[E0599]: no method named `push` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
43+
--> $DIR/rustc_confusables.rs:17:7
44+
|
45+
LL | x.push();
46+
| ^^^^ method not found in `BTreeSet`
47+
|
48+
help: you might have meant to use `insert`
49+
|
50+
LL | x.insert();
51+
| ~~~~~~
52+
53+
error[E0599]: no method named `test` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
54+
--> $DIR/rustc_confusables.rs:20:7
55+
|
56+
LL | x.test();
57+
| ^^^^ method not found in `BTreeSet`
58+
59+
error[E0599]: no method named `pulled` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
60+
--> $DIR/rustc_confusables.rs:22:7
61+
|
62+
LL | x.pulled();
63+
| ^^^^^^ help: there is a method with a similar name: `pull`
64+
65+
error: aborting due to 9 previous errors
66+
67+
Some errors have detailed explanations: E0539, E0599.
68+
For more information about an error, try `rustc --explain E0539`.

0 commit comments

Comments
 (0)