Skip to content

Commit 7240568

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 7d5b746 commit 7240568

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed

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: "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/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ hir_typeck_functional_record_update_on_non_struct =
5050
5151
hir_typeck_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml`
5252
hir_typeck_help_set_edition_standalone = pass `--edition {$edition}` to `rustc`
53+
54+
hir_typeck_invalid_rustc_confusable_attr = invalid `rustc_confusable` attribute
55+
5356
hir_typeck_lang_start_expected_sig_note = the `start` lang item should have the signature `fn(fn() -> T, isize, *const *const u8, u8) -> isize`
5457
5558
hir_typeck_lang_start_incorrect_number_params = incorrect number of parameters for the `start` lang item

compiler/rustc_hir_typeck/src/method/suggest.rs

+78
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use crate::errors::NoAssociatedItem;
77
use crate::Expectation;
88
use crate::FnCtxt;
99
use rustc_ast::ast::Mutability;
10+
use rustc_ast::token;
11+
use rustc_ast::tokenstream::TokenTree;
12+
use rustc_ast::{AttrArgs, AttrKind, Attribute, DelimArgs, NormalAttr};
1013
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1114
use rustc_errors::StashKey;
1215
use rustc_errors::{
@@ -24,6 +27,7 @@ use rustc_infer::infer::{
2427
type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
2528
RegionVariableOrigin,
2629
};
30+
use rustc_macros::Diagnostic;
2731
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
2832
use rustc_middle::traits::util::supertraits;
2933
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
@@ -48,6 +52,42 @@ use rustc_hir::intravisit::Visitor;
4852
use std::cmp::{self, Ordering};
4953
use std::iter;
5054

55+
#[derive(Debug)]
56+
struct ConfusablesDirective {
57+
names: Vec<String>,
58+
}
59+
60+
#[derive(Diagnostic)]
61+
#[diag(hir_typeck_invalid_rustc_confusable_attr)]
62+
struct ConfusablesDirectiveParseErr {
63+
#[primary_span]
64+
#[label]
65+
span: Span,
66+
}
67+
68+
impl ConfusablesDirective {
69+
fn try_parse(attr: &NormalAttr) -> Result<Self, ConfusablesDirectiveParseErr> {
70+
let AttrArgs::Delimited(DelimArgs { tokens, .. }) = &attr.item.args else {
71+
return Err(ConfusablesDirectiveParseErr {
72+
span: attr.item.span(),
73+
});
74+
};
75+
76+
let mut names = Vec::new();
77+
for tok in tokens.trees() {
78+
if let TokenTree::Token(tok, _) = tok
79+
&& let token::Token { kind, .. } = tok
80+
&& let token::TokenKind::Literal(lit) = kind
81+
&& let token::Lit { kind: token::LitKind::Str, symbol, .. } = lit
82+
{
83+
names.push(symbol.to_string());
84+
}
85+
}
86+
87+
Ok(ConfusablesDirective { names })
88+
}
89+
}
90+
5191
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5292
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
5393
let tcx = self.tcx;
@@ -1026,6 +1066,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10261066
"the {item_kind} was found for\n{}{}",
10271067
type_candidates, additional_types
10281068
));
1069+
} else {
1070+
let mut candidate_confusable = None;
1071+
1072+
for inherent_impl_did in self.tcx.inherent_impls(adt.did()) {
1073+
for inherent_method in
1074+
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1075+
{
1076+
if let Some(Attribute { kind: AttrKind::Normal(attr), .. }) = self
1077+
.tcx
1078+
.get_attr(inherent_method.def_id, sym::rustc_confusables)
1079+
{
1080+
match ConfusablesDirective::try_parse(attr) {
1081+
Ok(c) => {
1082+
if c.names.contains(&item_name.to_string()) {
1083+
candidate_confusable = Some(inherent_method);
1084+
break;
1085+
}
1086+
}
1087+
Err(e) => {
1088+
self.sess().emit_err(e);
1089+
break;
1090+
}
1091+
}
1092+
}
1093+
}
1094+
}
1095+
1096+
if let Some(candidate_confusable) = candidate_confusable {
1097+
err.span_suggestion_verbose(
1098+
item_name.span,
1099+
format!(
1100+
"you might have meant to use `{}`",
1101+
candidate_confusable.name.as_str()
1102+
),
1103+
candidate_confusable.name.as_str(),
1104+
Applicability::MaybeIncorrect,
1105+
);
1106+
}
10291107
}
10301108
}
10311109
} else {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,7 @@ symbols! {
12571257
rustc_clean,
12581258
rustc_coherence_is_core,
12591259
rustc_coinductive,
1260+
rustc_confusables,
12601261
rustc_const_stable,
12611262
rustc_const_unstable,
12621263
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+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// aux-build: rustc_confusables_across_crate.rs
2+
3+
extern crate rustc_confusables_across_crate;
4+
5+
use rustc_confusables_across_crate::BTreeSet;
6+
7+
fn main() {
8+
// Misspellings (similarly named methods) take precedence over `rustc_confusables`.
9+
let x = BTreeSet {};
10+
x.inser();
11+
//~^ ERROR no method named
12+
x.foo();
13+
//~^ ERROR no method named
14+
x.push();
15+
//~^ ERROR no method named
16+
x.test();
17+
//~^ ERROR no method named
18+
x.pulled();
19+
//~^ ERROR no method named
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error[E0599]: no method named `inser` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
2+
--> $DIR/rustc_confusables.rs:10:7
3+
|
4+
LL | x.inser();
5+
| ^^^^^ help: there is a method with a similar name: `insert`
6+
7+
error[E0599]: no method named `foo` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
8+
--> $DIR/rustc_confusables.rs:12:7
9+
|
10+
LL | x.foo();
11+
| ^^^ method not found in `BTreeSet`
12+
13+
error[E0599]: no method named `push` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
14+
--> $DIR/rustc_confusables.rs:14:7
15+
|
16+
LL | x.push();
17+
| ^^^^ method not found in `BTreeSet`
18+
|
19+
help: you might have meant to use `insert`
20+
|
21+
LL | x.insert();
22+
| ~~~~~~
23+
24+
error[E0599]: no method named `test` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
25+
--> $DIR/rustc_confusables.rs:16:7
26+
|
27+
LL | x.test();
28+
| ^^^^ method not found in `BTreeSet`
29+
30+
error[E0599]: no method named `pulled` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
31+
--> $DIR/rustc_confusables.rs:18:7
32+
|
33+
LL | x.pulled();
34+
| ^^^^^^ help: there is a method with a similar name: `pull`
35+
36+
error: aborting due to 5 previous errors
37+
38+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)