Skip to content

Commit 08c77a6

Browse files
committed
Add infrastructure #[rustc_confusables] attribute to allow targeted
"no method" errors on standard library types 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.
1 parent 00a39cc commit 08c77a6

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)