diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 76ac356efd6d5..480a551224929 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -2285,14 +2285,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // try to add a suggestion in case the field is a nested field of a field of the Adt if let Some((fields, substs)) = self.get_field_candidates(span, expr_t) { for candidate_field in fields.iter() { - if let Some(field_path) = self.check_for_nested_field( + if let Some(mut field_path) = self.check_for_nested_field_satisfying( span, - field, + &|candidate_field, _| candidate_field.ident(self.tcx()) == field, candidate_field, substs, vec![], self.tcx.parent_module(id).to_def_id(), ) { + // field_path includes `field` that we're looking for, so pop it. + field_path.pop(); + let field_path_str = field_path .iter() .map(|id| id.name.to_ident_string()) @@ -2312,7 +2315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err } - fn get_field_candidates( + crate fn get_field_candidates( &self, span: Span, base_t: Ty<'tcx>, @@ -2337,49 +2340,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// This method is called after we have encountered a missing field error to recursively /// search for the field - fn check_for_nested_field( + crate fn check_for_nested_field_satisfying( &self, span: Span, - target_field: Ident, + matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, candidate_field: &ty::FieldDef, subst: SubstsRef<'tcx>, mut field_path: Vec, id: DefId, ) -> Option> { debug!( - "check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}", + "check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}", span, candidate_field, field_path ); - if candidate_field.ident(self.tcx) == target_field { - Some(field_path) - } else if field_path.len() > 3 { + if field_path.len() > 3 { // For compile-time reasons and to avoid infinite recursion we only check for fields // up to a depth of three None } else { // recursively search fields of `candidate_field` if it's a ty::Adt - field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0()); let field_ty = candidate_field.ty(self.tcx, subst); if let Some((nested_fields, subst)) = self.get_field_candidates(span, field_ty) { for field in nested_fields.iter() { - let accessible = field.vis.is_accessible_from(id, self.tcx); - if accessible { - let ident = field.ident(self.tcx).normalize_to_macros_2_0(); - if ident == target_field { + if field.vis.is_accessible_from(id, self.tcx) { + if matches(candidate_field, field_ty) { return Some(field_path); - } - let field_path = field_path.clone(); - if let Some(path) = self.check_for_nested_field( + } else if let Some(field_path) = self.check_for_nested_field_satisfying( span, - target_field, + matches, field, subst, - field_path, + field_path.clone(), id, ) { - return Some(path); + return Some(field_path); } } } diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs index 2921176ca4b38..88e0a4bada845 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_typeck/src/check/method/suggest.rs @@ -28,7 +28,7 @@ use rustc_trait_selection::traits::{ use std::cmp::Ordering; use std::iter; -use super::probe::Mode; +use super::probe::{Mode, ProbeScope}; use super::{CandidateSource, MethodError, NoMatchData}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -1129,6 +1129,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { label_span_not_found(); } + if let SelfSource::MethodCall(expr) = source + && let Some((fields, substs)) = self.get_field_candidates(span, actual) + { + let call_expr = + self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id)); + for candidate_field in fields.iter() { + 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![], + self.tcx.parent_module(expr.hir_id).to_def_id(), + ) { + let field_path_str = field_path + .iter() + .map(|id| id.name.to_ident_string()) + .collect::>() + .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, + ); + } + } + } + bound_spans.sort(); bound_spans.dedup(); for (span, msg) in bound_spans.into_iter() { diff --git a/src/test/ui/hrtb/issue-30786.migrate.stderr b/src/test/ui/hrtb/issue-30786.migrate.stderr index 7ffe2f4cd7e1c..7157b186fc8ad 100644 --- a/src/test/ui/hrtb/issue-30786.migrate.stderr +++ b/src/test/ui/hrtb/issue-30786.migrate.stderr @@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied: | LL | impl StreamExt for T where for<'a> &'a mut T: Stream {} | --------- - ^^^^^^ unsatisfied trait bound introduced here +help: one of the expressions' fields has a method of the same name + | +LL | let filter = map.stream.filterx(|x: &_| true); + | +++++++ error[E0599]: the method `countx` exists for struct `Filter fn(&'r u64) -> &'r u64 {identity::}>, [closure@$DIR/issue-30786.rs:139:30: 139:42]>`, but its trait bounds were not satisfied --> $DIR/issue-30786.rs:140:24 @@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied: | LL | impl StreamExt for T where for<'a> &'a mut T: Stream {} | --------- - ^^^^^^ unsatisfied trait bound introduced here +help: one of the expressions' fields has a method of the same name + | +LL | let count = filter.stream.countx(); + | +++++++ error: aborting due to 2 previous errors diff --git a/src/test/ui/hrtb/issue-30786.nll.stderr b/src/test/ui/hrtb/issue-30786.nll.stderr index 7ffe2f4cd7e1c..7157b186fc8ad 100644 --- a/src/test/ui/hrtb/issue-30786.nll.stderr +++ b/src/test/ui/hrtb/issue-30786.nll.stderr @@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied: | LL | impl StreamExt for T where for<'a> &'a mut T: Stream {} | --------- - ^^^^^^ unsatisfied trait bound introduced here +help: one of the expressions' fields has a method of the same name + | +LL | let filter = map.stream.filterx(|x: &_| true); + | +++++++ error[E0599]: the method `countx` exists for struct `Filter fn(&'r u64) -> &'r u64 {identity::}>, [closure@$DIR/issue-30786.rs:139:30: 139:42]>`, but its trait bounds were not satisfied --> $DIR/issue-30786.rs:140:24 @@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied: | LL | impl StreamExt for T where for<'a> &'a mut T: Stream {} | --------- - ^^^^^^ unsatisfied trait bound introduced here +help: one of the expressions' fields has a method of the same name + | +LL | let count = filter.stream.countx(); + | +++++++ error: aborting due to 2 previous errors diff --git a/src/test/ui/suggestions/field-has-method.rs b/src/test/ui/suggestions/field-has-method.rs new file mode 100644 index 0000000000000..980000151e2f7 --- /dev/null +++ b/src/test/ui/suggestions/field-has-method.rs @@ -0,0 +1,23 @@ +struct Kind; + +struct Ty { + kind: Kind, +} + +impl Ty { + fn kind(&self) -> Kind { + todo!() + } +} + +struct InferOk { + value: T, + predicates: Vec<()>, +} + +fn foo(i: InferOk) { + let k = i.kind(); + //~^ no method named `kind` found for struct `InferOk` in the current scope +} + +fn main() {} diff --git a/src/test/ui/suggestions/field-has-method.stderr b/src/test/ui/suggestions/field-has-method.stderr new file mode 100644 index 0000000000000..3a57436f200ba --- /dev/null +++ b/src/test/ui/suggestions/field-has-method.stderr @@ -0,0 +1,17 @@ +error[E0599]: no method named `kind` found for struct `InferOk` in the current scope + --> $DIR/field-has-method.rs:19:15 + | +LL | struct InferOk { + | ----------------- method `kind` not found for this +... +LL | let k = i.kind(); + | ^^^^ method not found in `InferOk` + | +help: one of the expressions' fields has a method of the same name + | +LL | let k = i.value.kind(); + | ++++++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`.