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.
This commit is contained in:
parent
00a39cc785
commit
08c77a6eb4
12 changed files with 259 additions and 4 deletions
|
@ -3665,6 +3665,7 @@ name = "rustc_hir_typeck"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc_ast",
|
"rustc_ast",
|
||||||
|
"rustc_attr",
|
||||||
"rustc_data_structures",
|
"rustc_data_structures",
|
||||||
"rustc_errors",
|
"rustc_errors",
|
||||||
"rustc_fluent_macro",
|
"rustc_fluent_macro",
|
||||||
|
|
|
@ -1217,3 +1217,20 @@ pub fn parse_alignment(node: &ast::LitKind) -> Result<u32, &'static str> {
|
||||||
Err("not an unsuffixed integer")
|
Err("not an unsuffixed integer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the content of a `rustc_confusables` attribute, and return the list of candidate names.
|
||||||
|
pub fn parse_confusables(attr: &Attribute) -> Option<Vec<Symbol>> {
|
||||||
|
let meta = attr.meta()?;
|
||||||
|
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
|
||||||
|
|
||||||
|
let mut candidates = Vec::new();
|
||||||
|
|
||||||
|
for meta in metas {
|
||||||
|
let NestedMetaItem::Lit(meta_lit) = meta else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
candidates.push(meta_lit.symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(candidates);
|
||||||
|
}
|
||||||
|
|
|
@ -625,6 +625,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
|
||||||
ErrorFollowing,
|
ErrorFollowing,
|
||||||
INTERNAL_UNSTABLE
|
INTERNAL_UNSTABLE
|
||||||
),
|
),
|
||||||
|
rustc_attr!(
|
||||||
|
rustc_confusables, Normal,
|
||||||
|
template!(List: r#""name1", "name2", ..."#),
|
||||||
|
ErrorFollowing,
|
||||||
|
INTERNAL_UNSTABLE,
|
||||||
|
),
|
||||||
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
|
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
|
||||||
rustc_attr!(
|
rustc_attr!(
|
||||||
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||||
|
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||||
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
|
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
rustc_ast = { path = "../rustc_ast" }
|
rustc_ast = { path = "../rustc_ast" }
|
||||||
|
rustc_attr = { path = "../rustc_attr" }
|
||||||
rustc_data_structures = { path = "../rustc_data_structures" }
|
rustc_data_structures = { path = "../rustc_data_structures" }
|
||||||
rustc_errors = { path = "../rustc_errors" }
|
rustc_errors = { path = "../rustc_errors" }
|
||||||
rustc_graphviz = { path = "../rustc_graphviz" }
|
rustc_graphviz = { path = "../rustc_graphviz" }
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
//! found or is otherwise invalid.
|
//! found or is otherwise invalid.
|
||||||
|
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
use crate::errors::CandidateTraitNote;
|
use crate::errors::{CandidateTraitNote, NoAssociatedItem};
|
||||||
use crate::errors::NoAssociatedItem;
|
|
||||||
use crate::Expectation;
|
use crate::Expectation;
|
||||||
use crate::FnCtxt;
|
use crate::FnCtxt;
|
||||||
use rustc_ast::ast::Mutability;
|
use rustc_ast::ast::Mutability;
|
||||||
use rustc_data_structures::fx::FxIndexMap;
|
use rustc_attr::parse_confusables;
|
||||||
use rustc_data_structures::fx::FxIndexSet;
|
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
|
||||||
use rustc_data_structures::unord::UnordSet;
|
use rustc_data_structures::unord::UnordSet;
|
||||||
use rustc_errors::StashKey;
|
use rustc_errors::StashKey;
|
||||||
use rustc_errors::{
|
use rustc_errors::{
|
||||||
|
@ -1038,6 +1037,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
"the {item_kind} was found for\n{}{}",
|
"the {item_kind} was found for\n{}{}",
|
||||||
type_candidates, additional_types
|
type_candidates, additional_types
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
'outer: 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(attr) = self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
|
||||||
|
&& let Some(candidates) = parse_confusables(attr)
|
||||||
|
&& candidates.contains(&item_name.name)
|
||||||
|
{
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
item_name.span,
|
||||||
|
format!(
|
||||||
|
"you might have meant to use `{}`",
|
||||||
|
inherent_method.name.as_str()
|
||||||
|
),
|
||||||
|
inherent_method.name.as_str(),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -98,6 +98,9 @@ passes_collapse_debuginfo =
|
||||||
`collapse_debuginfo` attribute should be applied to macro definitions
|
`collapse_debuginfo` attribute should be applied to macro definitions
|
||||||
.label = not a macro definition
|
.label = not a macro definition
|
||||||
|
|
||||||
|
passes_confusables = attribute should be applied to an inherent method
|
||||||
|
.label = not an inherent method
|
||||||
|
|
||||||
passes_const_impl_const_trait =
|
passes_const_impl_const_trait =
|
||||||
const `impl`s must be for traits marked with `#[const_trait]`
|
const `impl`s must be for traits marked with `#[const_trait]`
|
||||||
.note = this trait must be annotated with `#[const_trait]`
|
.note = this trait must be annotated with `#[const_trait]`
|
||||||
|
@ -266,6 +269,9 @@ passes_duplicate_lang_item_crate_depends =
|
||||||
.first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path}
|
.first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path}
|
||||||
.second_definition_path = second definition in `{$crate_name}` loaded from {$path}
|
.second_definition_path = second definition in `{$crate_name}` loaded from {$path}
|
||||||
|
|
||||||
|
passes_empty_confusables =
|
||||||
|
expected at least one confusable name
|
||||||
|
|
||||||
passes_export_name =
|
passes_export_name =
|
||||||
attribute should be applied to a free function, impl method or static
|
attribute should be applied to a free function, impl method or static
|
||||||
.label = not a free function, impl method or static
|
.label = not a free function, impl method or static
|
||||||
|
@ -326,6 +332,9 @@ passes_implied_feature_not_exist =
|
||||||
passes_incorrect_do_not_recommend_location =
|
passes_incorrect_do_not_recommend_location =
|
||||||
`#[do_not_recommend]` can only be placed on trait implementations
|
`#[do_not_recommend]` can only be placed on trait implementations
|
||||||
|
|
||||||
|
passes_incorrect_meta_item = expected a quoted string literal
|
||||||
|
passes_incorrect_meta_item_suggestion = consider surrounding this with quotes
|
||||||
|
|
||||||
passes_incorrect_target =
|
passes_incorrect_target =
|
||||||
`{$name}` language item must be applied to a {$kind} with {$at_least ->
|
`{$name}` language item must be applied to a {$kind} with {$at_least ->
|
||||||
[true] at least {$num}
|
[true] at least {$num}
|
||||||
|
|
|
@ -183,6 +183,7 @@ impl CheckAttrVisitor<'_> {
|
||||||
| sym::rustc_allowed_through_unstable_modules
|
| sym::rustc_allowed_through_unstable_modules
|
||||||
| sym::rustc_promotable => self.check_stability_promotable(&attr, span, target),
|
| sym::rustc_promotable => self.check_stability_promotable(&attr, span, target),
|
||||||
sym::link_ordinal => self.check_link_ordinal(&attr, span, target),
|
sym::link_ordinal => self.check_link_ordinal(&attr, span, target),
|
||||||
|
sym::rustc_confusables => self.check_confusables(&attr, target),
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1985,6 +1986,46 @@ impl CheckAttrVisitor<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_confusables(&self, attr: &Attribute, target: Target) -> bool {
|
||||||
|
match target {
|
||||||
|
Target::Method(MethodKind::Inherent) => {
|
||||||
|
let Some(meta) = attr.meta() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let ast::MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut candidates = Vec::new();
|
||||||
|
|
||||||
|
for meta in metas {
|
||||||
|
let NestedMetaItem::Lit(meta_lit) = meta else {
|
||||||
|
self.tcx.sess.emit_err(errors::IncorrectMetaItem {
|
||||||
|
span: meta.span(),
|
||||||
|
suggestion: errors::IncorrectMetaItemSuggestion {
|
||||||
|
lo: meta.span().shrink_to_lo(),
|
||||||
|
hi: meta.span().shrink_to_hi(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
candidates.push(meta_lit.symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if candidates.is_empty() {
|
||||||
|
self.tcx.sess.emit_err(errors::EmptyConfusables { span: attr.span });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.tcx.sess.emit_err(errors::Confusables { attr_span: attr.span });
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) {
|
fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) {
|
||||||
match target {
|
match target {
|
||||||
Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
|
Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
|
||||||
|
|
|
@ -617,6 +617,38 @@ pub struct LinkOrdinal {
|
||||||
pub attr_span: Span,
|
pub attr_span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Diagnostic)]
|
||||||
|
#[diag(passes_confusables)]
|
||||||
|
pub struct Confusables {
|
||||||
|
#[primary_span]
|
||||||
|
pub attr_span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Diagnostic)]
|
||||||
|
#[diag(passes_empty_confusables)]
|
||||||
|
pub(crate) struct EmptyConfusables {
|
||||||
|
#[primary_span]
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Diagnostic)]
|
||||||
|
#[diag(passes_incorrect_meta_item, code = "E0539")]
|
||||||
|
pub(crate) struct IncorrectMetaItem {
|
||||||
|
#[primary_span]
|
||||||
|
pub span: Span,
|
||||||
|
#[subdiagnostic]
|
||||||
|
pub suggestion: IncorrectMetaItemSuggestion,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subdiagnostic)]
|
||||||
|
#[multipart_suggestion(passes_incorrect_meta_item_suggestion, applicability = "maybe-incorrect")]
|
||||||
|
pub(crate) struct IncorrectMetaItemSuggestion {
|
||||||
|
#[suggestion_part(code = "\"")]
|
||||||
|
pub lo: Span,
|
||||||
|
#[suggestion_part(code = "\"")]
|
||||||
|
pub hi: Span,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Diagnostic)]
|
#[derive(Diagnostic)]
|
||||||
#[diag(passes_stability_promotable)]
|
#[diag(passes_stability_promotable)]
|
||||||
pub struct StabilityPromotable {
|
pub struct StabilityPromotable {
|
||||||
|
|
|
@ -1265,6 +1265,7 @@ symbols! {
|
||||||
rustc_clean,
|
rustc_clean,
|
||||||
rustc_coherence_is_core,
|
rustc_coherence_is_core,
|
||||||
rustc_coinductive,
|
rustc_coinductive,
|
||||||
|
rustc_confusables,
|
||||||
rustc_const_stable,
|
rustc_const_stable,
|
||||||
rustc_const_unstable,
|
rustc_const_unstable,
|
||||||
rustc_conversion_suggestion,
|
rustc_conversion_suggestion,
|
||||||
|
|
|
@ -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) {}
|
||||||
|
}
|
47
tests/ui/attributes/rustc_confusables.rs
Normal file
47
tests/ui/attributes/rustc_confusables.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// aux-build: rustc_confusables_across_crate.rs
|
||||||
|
|
||||||
|
#![feature(rustc_attrs)]
|
||||||
|
|
||||||
|
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
|
||||||
|
//~| HELP there is a method with a similar name
|
||||||
|
x.foo();
|
||||||
|
//~^ ERROR no method named
|
||||||
|
x.push();
|
||||||
|
//~^ ERROR no method named
|
||||||
|
//~| HELP you might have meant to use `insert`
|
||||||
|
x.test();
|
||||||
|
//~^ ERROR no method named
|
||||||
|
x.pulled();
|
||||||
|
//~^ ERROR no method named
|
||||||
|
//~| HELP there is a method with a similar name
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
impl Bar {
|
||||||
|
#[rustc_confusables()]
|
||||||
|
//~^ ERROR expected at least one confusable name
|
||||||
|
fn baz() {}
|
||||||
|
|
||||||
|
#[rustc_confusables]
|
||||||
|
//~^ ERROR malformed `rustc_confusables` attribute input
|
||||||
|
//~| HELP must be of the form
|
||||||
|
fn qux() {}
|
||||||
|
|
||||||
|
#[rustc_confusables(invalid_meta_item)]
|
||||||
|
//~^ ERROR expected a quoted string literal
|
||||||
|
//~| HELP consider surrounding this with quotes
|
||||||
|
fn quux() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustc_confusables("blah")]
|
||||||
|
//~^ ERROR attribute should be applied to an inherent method
|
||||||
|
fn not_inherent_impl_method() {}
|
68
tests/ui/attributes/rustc_confusables.stderr
Normal file
68
tests/ui/attributes/rustc_confusables.stderr
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
error: malformed `rustc_confusables` attribute input
|
||||||
|
--> $DIR/rustc_confusables.rs:34:5
|
||||||
|
|
|
||||||
|
LL | #[rustc_confusables]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]`
|
||||||
|
|
||||||
|
error: attribute should be applied to an inherent method
|
||||||
|
--> $DIR/rustc_confusables.rs:45:1
|
||||||
|
|
|
||||||
|
LL | #[rustc_confusables("blah")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected at least one confusable name
|
||||||
|
--> $DIR/rustc_confusables.rs:30:5
|
||||||
|
|
|
||||||
|
LL | #[rustc_confusables()]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0539]: expected a quoted string literal
|
||||||
|
--> $DIR/rustc_confusables.rs:39:25
|
||||||
|
|
|
||||||
|
LL | #[rustc_confusables(invalid_meta_item)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: consider surrounding this with quotes
|
||||||
|
|
|
||||||
|
LL | #[rustc_confusables("invalid_meta_item")]
|
||||||
|
| + +
|
||||||
|
|
||||||
|
error[E0599]: no method named `inser` found for struct `rustc_confusables_across_crate::BTreeSet` in the current scope
|
||||||
|
--> $DIR/rustc_confusables.rs:12: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:15: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:17: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:20: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:22:7
|
||||||
|
|
|
||||||
|
LL | x.pulled();
|
||||||
|
| ^^^^^^ help: there is a method with a similar name: `pull`
|
||||||
|
|
||||||
|
error: aborting due to 9 previous errors
|
||||||
|
|
||||||
|
Some errors have detailed explanations: E0539, E0599.
|
||||||
|
For more information about an error, try `rustc --explain E0539`.
|
Loading…
Add table
Reference in a new issue