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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 25 additions & 4 deletions
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 41 additions & 0 deletions
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

Lines changed: 32 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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,
Lines changed: 11 additions & 0 deletions
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+
}

0 commit comments

Comments
 (0)