about summary refs log tree commit diff
diff options
context:
space:
mode:
author许杰友 Jieyou Xu (Joe) <jieyouxu@outlook.com>2023-07-16 18:18:38 +0800
committer许杰友 Jieyou Xu (Joe) <jieyouxu@outlook.com>2023-07-16 19:22:03 +0800
commit08c77a6eb403768ed64ae567980887984f506ef9 (patch)
tree3da9b7971e6ad61ebedf5c5dd716886948ba3e89
parent00a39cc785fcbb4f1e9c9454e9ee8a85e6ff021c (diff)
downloadrust-08c77a6eb403768ed64ae567980887984f506ef9.tar.gz
rust-08c77a6eb403768ed64ae567980887984f506ef9.zip
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.
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_attr/src/builtin.rs17
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs6
-rw-r--r--compiler/rustc_hir_typeck/Cargo.toml1
-rw-r--r--compiler/rustc_hir_typeck/src/method/suggest.rs29
-rw-r--r--compiler/rustc_passes/messages.ftl9
-rw-r--r--compiler/rustc_passes/src/check_attr.rs41
-rw-r--r--compiler/rustc_passes/src/errors.rs32
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/ui/attributes/auxiliary/rustc_confusables_across_crate.rs11
-rw-r--r--tests/ui/attributes/rustc_confusables.rs47
-rw-r--r--tests/ui/attributes/rustc_confusables.stderr68
12 files changed, 259 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9f1561b503d..2390850b567 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3665,6 +3665,7 @@ name = "rustc_hir_typeck"
 version = "0.1.0"
 dependencies = [
  "rustc_ast",
+ "rustc_attr",
  "rustc_data_structures",
  "rustc_errors",
  "rustc_fluent_macro",
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index 372a58857f3..6cce3a56f08 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -1217,3 +1217,20 @@ pub fn parse_alignment(node: &ast::LitKind) -> Result<u32, &'static str> {
         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);
+}
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index a70671dd9fb..a183cfd8776 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -625,6 +625,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         ErrorFollowing,
         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.
     rustc_attr!(
         rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
diff --git a/compiler/rustc_hir_typeck/Cargo.toml b/compiler/rustc_hir_typeck/Cargo.toml
index 13e1ea31c4d..ce91d023a0a 100644
--- a/compiler/rustc_hir_typeck/Cargo.toml
+++ b/compiler/rustc_hir_typeck/Cargo.toml
@@ -9,6 +9,7 @@ edition = "2021"
 smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
 tracing = "0.1"
 rustc_ast = { path = "../rustc_ast" }
+rustc_attr = { path = "../rustc_attr" }
 rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_graphviz = { path = "../rustc_graphviz" }
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index e3e0eff23d2..3d7187cb16f 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -2,13 +2,12 @@
 //! found or is otherwise invalid.
 
 use crate::errors;
-use crate::errors::CandidateTraitNote;
-use crate::errors::NoAssociatedItem;
+use crate::errors::{CandidateTraitNote, NoAssociatedItem};
 use crate::Expectation;
 use crate::FnCtxt;
 use rustc_ast::ast::Mutability;
-use rustc_data_structures::fx::FxIndexMap;
-use rustc_data_structures::fx::FxIndexSet;
+use rustc_attr::parse_confusables;
+use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::unord::UnordSet;
 use rustc_errors::StashKey;
 use rustc_errors::{
@@ -1038,6 +1037,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             "the {item_kind} was found for\n{}{}",
                             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 {
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index a607e483c97..0aa3d6265dc 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -98,6 +98,9 @@ passes_collapse_debuginfo =
     `collapse_debuginfo` attribute should be applied to macro definitions
     .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 =
     const `impl`s must be for traits marked 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}
     .second_definition_path = second definition in `{$crate_name}` loaded from {$path}
 
+passes_empty_confusables =
+    expected at least one confusable name
+
 passes_export_name =
     attribute should be applied to 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 =
     `#[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 =
     `{$name}` language item must be applied to a {$kind} with {$at_least ->
         [true] at least {$num}
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index ee14ffa6da8..4d7ebe3fefe 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -183,6 +183,7 @@ impl CheckAttrVisitor<'_> {
                 | sym::rustc_allowed_through_unstable_modules
                 | sym::rustc_promotable => self.check_stability_promotable(&attr, span, target),
                 sym::link_ordinal => self.check_link_ordinal(&attr, span, target),
+                sym::rustc_confusables => self.check_confusables(&attr, target),
                 _ => 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) {
         match target {
             Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 3fe7feb9dfa..eae13f86049 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -618,6 +618,38 @@ pub struct LinkOrdinal {
 }
 
 #[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)]
 #[diag(passes_stability_promotable)]
 pub struct StabilityPromotable {
     #[primary_span]
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 4fc440ef947..08925761b39 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1265,6 +1265,7 @@ symbols! {
         rustc_clean,
         rustc_coherence_is_core,
         rustc_coinductive,
+        rustc_confusables,
         rustc_const_stable,
         rustc_const_unstable,
         rustc_conversion_suggestion,
diff --git a/tests/ui/attributes/auxiliary/rustc_confusables_across_crate.rs b/tests/ui/attributes/auxiliary/rustc_confusables_across_crate.rs
new file mode 100644
index 00000000000..2fb2d3ad4c4
--- /dev/null
+++ b/tests/ui/attributes/auxiliary/rustc_confusables_across_crate.rs
@@ -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) {}
+}
diff --git a/tests/ui/attributes/rustc_confusables.rs b/tests/ui/attributes/rustc_confusables.rs
new file mode 100644
index 00000000000..352e91d065f
--- /dev/null
+++ b/tests/ui/attributes/rustc_confusables.rs
@@ -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() {}
diff --git a/tests/ui/attributes/rustc_confusables.stderr b/tests/ui/attributes/rustc_confusables.stderr
new file mode 100644
index 00000000000..9fd4470cdbb
--- /dev/null
+++ b/tests/ui/attributes/rustc_confusables.stderr
@@ -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`.