about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-08-29 21:12:54 +0200
committerGitHub <noreply@github.com>2022-08-29 21:12:54 +0200
commit091017c244af24adc3ef44a853189eebbee943cf (patch)
tree04a9f7bf46d019fb2eaed83a5e9742321cf54906
parentfcc2bddd262b26c3a4aeff2328e39b8e2b6d3254 (diff)
parentc3f568b3312bed99d0e4b95daecd3c9eca1ae895 (diff)
downloadrust-091017c244af24adc3ef44a853189eebbee943cf.tar.gz
rust-091017c244af24adc3ef44a853189eebbee943cf.zip
Rollup merge of #100898 - compiler-errors:too-many-expr-fields, r=spastorino
Do not report too many expr field candidates

When considering "this expressions' field has a {field/method}" suggestions:
1. Don't report methods that are out of scope
2. Use `span_suggestions` instead of reporting each field candidate, which caps the number of suggestions to 4
4. Blacklist some common traits like `Clone` and `Deref`

Fixes #100894
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs51
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs88
-rw-r--r--src/test/ui/copy-a-resource.stderr4
-rw-r--r--src/test/ui/issues/issue-2823.stderr4
-rw-r--r--src/test/ui/noncopyable-class.stderr8
-rw-r--r--src/test/ui/suggestions/too-many-field-suggestions.rs29
-rw-r--r--src/test/ui/suggestions/too-many-field-suggestions.stderr44
8 files changed, 160 insertions, 69 deletions
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 1b47d11f6f8..6eca7dc52b2 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -157,6 +157,7 @@ symbols! {
         BTreeSet,
         BinaryHeap,
         Borrow,
+        BorrowMut,
         Break,
         C,
         CStr,
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 71ae54bedce..33d74249e7b 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -2605,32 +2605,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         if let Some((fields, substs)) =
             self.get_field_candidates_considering_privacy(span, expr_t, mod_id)
         {
-            for candidate_field in fields {
-                if let Some(mut field_path) = self.check_for_nested_field_satisfying(
-                    span,
-                    &|candidate_field, _| candidate_field.ident(self.tcx()) == field,
-                    candidate_field,
-                    substs,
-                    vec![],
-                    mod_id,
-                ) {
-                    // field_path includes `field` that we're looking for, so pop it.
+            let candidate_fields: Vec<_> = fields
+                .filter_map(|candidate_field| {
+                    self.check_for_nested_field_satisfying(
+                        span,
+                        &|candidate_field, _| candidate_field.ident(self.tcx()) == field,
+                        candidate_field,
+                        substs,
+                        vec![],
+                        mod_id,
+                    )
+                })
+                .map(|mut field_path| {
                     field_path.pop();
-
-                    let field_path_str = field_path
+                    field_path
                         .iter()
                         .map(|id| id.name.to_ident_string())
                         .collect::<Vec<String>>()
-                        .join(".");
-                    debug!("field_path_str: {:?}", field_path_str);
-
-                    err.span_suggestion_verbose(
-                        field.span.shrink_to_lo(),
-                        "one of the expressions' fields has a field of the same name",
-                        format!("{field_path_str}."),
-                        Applicability::MaybeIncorrect,
-                    );
-                }
+                        .join(".")
+                })
+                .collect::<Vec<_>>();
+
+            let len = candidate_fields.len();
+            if len > 0 {
+                err.span_suggestions(
+                    field.span.shrink_to_lo(),
+                    format!(
+                        "{} of the expressions' fields {} a field of the same name",
+                        if len > 1 { "some" } else { "one" },
+                        if len > 1 { "have" } else { "has" },
+                    ),
+                    candidate_fields.iter().map(|path| format!("{path}.")),
+                    Applicability::MaybeIncorrect,
+                );
             }
         }
         err
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index 8bb8c7ac515..e532f392157 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -1350,42 +1350,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         item_name: Ident,
     ) {
         if let SelfSource::MethodCall(expr) = source
-            && let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
-            && let Some((fields, substs)) = self.get_field_candidates_considering_privacy(span, actual, mod_id)
+        && let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
+        && let Some((fields, substs)) =
+            self.get_field_candidates_considering_privacy(span, actual, mod_id)
         {
             let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
-            for candidate_field in fields {
-                if let Some(field_path) = self.check_for_nested_field_satisfying(
-                    span,
-                    &|_, field_ty| {
-                        self.lookup_probe(
-                            span,
-                            item_name,
-                            field_ty,
-                            call_expr,
-                            ProbeScope::AllTraits,
-                        )
-                        .is_ok()
-                    },
-                    candidate_field,
-                    substs,
-                    vec![],
-                    mod_id,
-                ) {
-                    let field_path_str = field_path
+
+            let lang_items = self.tcx.lang_items();
+            let never_mention_traits = [
+                lang_items.clone_trait(),
+                lang_items.deref_trait(),
+                lang_items.deref_mut_trait(),
+                self.tcx.get_diagnostic_item(sym::AsRef),
+                self.tcx.get_diagnostic_item(sym::AsMut),
+                self.tcx.get_diagnostic_item(sym::Borrow),
+                self.tcx.get_diagnostic_item(sym::BorrowMut),
+            ];
+            let candidate_fields: Vec<_> = fields
+                .filter_map(|candidate_field| {
+                    self.check_for_nested_field_satisfying(
+                        span,
+                        &|_, field_ty| {
+                            self.lookup_probe(
+                                span,
+                                item_name,
+                                field_ty,
+                                call_expr,
+                                ProbeScope::TraitsInScope,
+                            )
+                            .map_or(false, |pick| {
+                                !never_mention_traits
+                                    .iter()
+                                    .flatten()
+                                    .any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
+                            })
+                        },
+                        candidate_field,
+                        substs,
+                        vec![],
+                        mod_id,
+                    )
+                })
+                .map(|field_path| {
+                    field_path
                         .iter()
                         .map(|id| id.name.to_ident_string())
                         .collect::<Vec<String>>()
-                        .join(".");
-                    debug!("field_path_str: {:?}", field_path_str);
-
-                    err.span_suggestion_verbose(
-                        item_name.span.shrink_to_lo(),
-                        "one of the expressions' fields has a method of the same name",
-                        format!("{field_path_str}."),
-                        Applicability::MaybeIncorrect,
-                    );
-                }
+                        .join(".")
+                })
+                .collect();
+
+            let len = candidate_fields.len();
+            if len > 0 {
+                err.span_suggestions(
+                    item_name.span.shrink_to_lo(),
+                    format!(
+                        "{} of the expressions' fields {} a method of the same name",
+                        if len > 1 { "some" } else { "one" },
+                        if len > 1 { "have" } else { "has" },
+                    ),
+                    candidate_fields.iter().map(|path| format!("{path}.")),
+                    Applicability::MaybeIncorrect,
+                );
             }
         }
     }
diff --git a/src/test/ui/copy-a-resource.stderr b/src/test/ui/copy-a-resource.stderr
index b92449c6e0a..128087f1e37 100644
--- a/src/test/ui/copy-a-resource.stderr
+++ b/src/test/ui/copy-a-resource.stderr
@@ -10,10 +10,6 @@ LL |     let _y = x.clone();
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following trait defines an item `clone`, perhaps you need to implement it:
            candidate #1: `Clone`
-help: one of the expressions' fields has a method of the same name
-   |
-LL |     let _y = x.i.clone();
-   |                ++
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/issues/issue-2823.stderr b/src/test/ui/issues/issue-2823.stderr
index 208b340d064..b5a2b2f55a6 100644
--- a/src/test/ui/issues/issue-2823.stderr
+++ b/src/test/ui/issues/issue-2823.stderr
@@ -10,10 +10,6 @@ LL |     let _d = c.clone();
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following trait defines an item `clone`, perhaps you need to implement it:
            candidate #1: `Clone`
-help: one of the expressions' fields has a method of the same name
-   |
-LL |     let _d = c.x.clone();
-   |                ++
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/noncopyable-class.stderr b/src/test/ui/noncopyable-class.stderr
index 15e22e946da..0c696163a26 100644
--- a/src/test/ui/noncopyable-class.stderr
+++ b/src/test/ui/noncopyable-class.stderr
@@ -10,14 +10,6 @@ LL |     let _y = x.clone();
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following trait defines an item `clone`, perhaps you need to implement it:
            candidate #1: `Clone`
-help: one of the expressions' fields has a method of the same name
-   |
-LL |     let _y = x.i.clone();
-   |                ++
-help: one of the expressions' fields has a method of the same name
-   |
-LL |     let _y = x.j.x.clone();
-   |                ++++
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/suggestions/too-many-field-suggestions.rs b/src/test/ui/suggestions/too-many-field-suggestions.rs
new file mode 100644
index 00000000000..905f9502cf5
--- /dev/null
+++ b/src/test/ui/suggestions/too-many-field-suggestions.rs
@@ -0,0 +1,29 @@
+struct Thing {
+    a0: Foo,
+    a1: Foo,
+    a2: Foo,
+    a3: Foo,
+    a4: Foo,
+    a5: Foo,
+    a6: Foo,
+    a7: Foo,
+    a8: Foo,
+    a9: Foo,
+}
+
+struct Foo {
+    field: Field,
+}
+
+struct Field;
+
+impl Foo {
+    fn bar(&self) {}
+}
+
+fn bar(t: Thing) {
+    t.bar(); //~ ERROR no method named `bar` found for struct `Thing`
+    t.field; //~ ERROR no field `field` on type `Thing`
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/too-many-field-suggestions.stderr b/src/test/ui/suggestions/too-many-field-suggestions.stderr
new file mode 100644
index 00000000000..63ad6fdb169
--- /dev/null
+++ b/src/test/ui/suggestions/too-many-field-suggestions.stderr
@@ -0,0 +1,44 @@
+error[E0599]: no method named `bar` found for struct `Thing` in the current scope
+  --> $DIR/too-many-field-suggestions.rs:25:7
+   |
+LL | struct Thing {
+   | ------------ method `bar` not found for this struct
+...
+LL |     t.bar();
+   |       ^^^ method not found in `Thing`
+   |
+help: some of the expressions' fields have a method of the same name
+   |
+LL |     t.a0.bar();
+   |       +++
+LL |     t.a1.bar();
+   |       +++
+LL |     t.a2.bar();
+   |       +++
+LL |     t.a3.bar();
+   |       +++
+     and 6 other candidates
+
+error[E0609]: no field `field` on type `Thing`
+  --> $DIR/too-many-field-suggestions.rs:26:7
+   |
+LL |     t.field;
+   |       ^^^^^ unknown field
+   |
+   = note: available fields are: `a0`, `a1`, `a2`, `a3`, `a4` ... and 5 others
+help: some of the expressions' fields have a field of the same name
+   |
+LL |     t.a0.field;
+   |       +++
+LL |     t.a1.field;
+   |       +++
+LL |     t.a2.field;
+   |       +++
+LL |     t.a3.field;
+   |       +++
+     and 6 other candidates
+
+error: aborting due to 2 previous errors
+
+Some errors have detailed explanations: E0599, E0609.
+For more information about an error, try `rustc --explain E0599`.