Skip to content

Commit

Permalink
Add infrastructure #[rustc_confusables] attribute to allow targeted
Browse files Browse the repository at this point in the history
"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.
  • Loading branch information
jieyouxu committed Jun 3, 2023
1 parent 7d5b746 commit 7240568
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 0 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
ErrorFollowing,
INTERNAL_UNSTABLE
),
rustc_attr!(
rustc_confusables, Normal,
template!(List: "name1, name2, ..."),
ErrorFollowing,
INTERNAL_UNSTABLE,
),
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
rustc_attr!(
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir_typeck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ hir_typeck_functional_record_update_on_non_struct =
hir_typeck_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml`
hir_typeck_help_set_edition_standalone = pass `--edition {$edition}` to `rustc`
hir_typeck_invalid_rustc_confusable_attr = invalid `rustc_confusable` attribute
hir_typeck_lang_start_expected_sig_note = the `start` lang item should have the signature `fn(fn() -> T, isize, *const *const u8, u8) -> isize`
hir_typeck_lang_start_incorrect_number_params = incorrect number of parameters for the `start` lang item
Expand Down
78 changes: 78 additions & 0 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use crate::errors::NoAssociatedItem;
use crate::Expectation;
use crate::FnCtxt;
use rustc_ast::ast::Mutability;
use rustc_ast::token;
use rustc_ast::tokenstream::TokenTree;
use rustc_ast::{AttrArgs, AttrKind, Attribute, DelimArgs, NormalAttr};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::StashKey;
use rustc_errors::{
Expand All @@ -24,6 +27,7 @@ use rustc_infer::infer::{
type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
RegionVariableOrigin,
};
use rustc_macros::Diagnostic;
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
use rustc_middle::traits::util::supertraits;
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
Expand All @@ -48,6 +52,42 @@ use rustc_hir::intravisit::Visitor;
use std::cmp::{self, Ordering};
use std::iter;

#[derive(Debug)]
struct ConfusablesDirective {
names: Vec<String>,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_invalid_rustc_confusable_attr)]
struct ConfusablesDirectiveParseErr {
#[primary_span]
#[label]
span: Span,
}

impl ConfusablesDirective {
fn try_parse(attr: &NormalAttr) -> Result<Self, ConfusablesDirectiveParseErr> {
let AttrArgs::Delimited(DelimArgs { tokens, .. }) = &attr.item.args else {
return Err(ConfusablesDirectiveParseErr {
span: attr.item.span(),
});
};

let mut names = Vec::new();
for tok in tokens.trees() {
if let TokenTree::Token(tok, _) = tok
&& let token::Token { kind, .. } = tok
&& let token::TokenKind::Literal(lit) = kind
&& let token::Lit { kind: token::LitKind::Str, symbol, .. } = lit
{
names.push(symbol.to_string());
}
}

Ok(ConfusablesDirective { names })
}
}

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
let tcx = self.tcx;
Expand Down Expand Up @@ -1026,6 +1066,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"the {item_kind} was found for\n{}{}",
type_candidates, additional_types
));
} else {
let mut candidate_confusable = None;

for inherent_impl_did in self.tcx.inherent_impls(adt.did()) {
for inherent_method in
self.tcx.associated_items(inherent_impl_did).in_definition_order()
{
if let Some(Attribute { kind: AttrKind::Normal(attr), .. }) = self
.tcx
.get_attr(inherent_method.def_id, sym::rustc_confusables)
{
match ConfusablesDirective::try_parse(attr) {
Ok(c) => {
if c.names.contains(&item_name.to_string()) {
candidate_confusable = Some(inherent_method);
break;
}
}
Err(e) => {
self.sess().emit_err(e);
break;
}
}
}
}
}

if let Some(candidate_confusable) = candidate_confusable {
err.span_suggestion_verbose(
item_name.span,
format!(
"you might have meant to use `{}`",
candidate_confusable.name.as_str()
),
candidate_confusable.name.as_str(),
Applicability::MaybeIncorrect,
);
}
}
}
} else {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ symbols! {
rustc_clean,
rustc_coherence_is_core,
rustc_coinductive,
rustc_confusables,
rustc_const_stable,
rustc_const_unstable,
rustc_conversion_suggestion,
Expand Down
11 changes: 11 additions & 0 deletions tests/ui/attributes/auxiliary/rustc_confusables_across_crate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![feature(rustc_attrs)]

pub struct BTreeSet;

impl BTreeSet {
#[rustc_confusables("push", "test_b")]
pub fn insert(&self) {}

#[rustc_confusables("pulled")]
pub fn pull(&self) {}
}
20 changes: 20 additions & 0 deletions tests/ui/attributes/rustc_confusables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// aux-build: rustc_confusables_across_crate.rs

extern crate rustc_confusables_across_crate;

use rustc_confusables_across_crate::BTreeSet;

fn main() {
// Misspellings (similarly named methods) take precedence over `rustc_confusables`.
let x = BTreeSet {};
x.inser();
//~^ ERROR no method named
x.foo();
//~^ ERROR no method named
x.push();
//~^ ERROR no method named
x.test();
//~^ ERROR no method named
x.pulled();
//~^ ERROR no method named
}
38 changes: 38 additions & 0 deletions tests/ui/attributes/rustc_confusables.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
error[E0599]: no method named `inser` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
--> $DIR/rustc_confusables.rs:10:7
|
LL | x.inser();
| ^^^^^ help: there is a method with a similar name: `insert`

error[E0599]: no method named `foo` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
--> $DIR/rustc_confusables.rs:12:7
|
LL | x.foo();
| ^^^ method not found in `BTreeSet`

error[E0599]: no method named `push` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
--> $DIR/rustc_confusables.rs:14:7
|
LL | x.push();
| ^^^^ method not found in `BTreeSet`
|
help: you might have meant to use `insert`
|
LL | x.insert();
| ~~~~~~

error[E0599]: no method named `test` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
--> $DIR/rustc_confusables.rs:16:7
|
LL | x.test();
| ^^^^ method not found in `BTreeSet`

error[E0599]: no method named `pulled` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
--> $DIR/rustc_confusables.rs:18:7
|
LL | x.pulled();
| ^^^^^^ help: there is a method with a similar name: `pull`

error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0599`.

0 comments on commit 7240568

Please sign in to comment.