From e8a00a7621606f8c4a34b679cefaaa6aecf2442e Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 09:45:35 +0100 Subject: [PATCH 01/12] Add sub-fn for method call annotation in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index a0d9e9a72386c..56f4a2a70a9bf 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -666,6 +666,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn suggest_method_call_annotation( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + mode: Mode, + source: SelfSource<'tcx>, + expected: Expectation<'tcx>, + ) { + if let Mode::MethodCall = mode + && let SelfSource::MethodCall(cal) = source + { + self.suggest_await_before_method( + err, + item_ident, + rcvr_ty, + cal, + span, + expected.only_has_type(self), + ); + } + + self.suggest_on_pointer_type(err, source, rcvr_ty, item_ident); + + if let SelfSource::MethodCall(rcvr_expr) = source { + self.suggest_fn_call(err, rcvr_expr, rcvr_ty, |output_ty| { + let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id)); + let probe = self.lookup_probe_for_diagnostic( + item_ident, + output_ty, + call_expr, + ProbeScope::AllTraits, + expected.only_has_type(self), + ); + probe.is_ok() + }); + self.note_internal_mutation_in_method( + err, + rcvr_expr, + expected.to_option(self), + rcvr_ty, + ); + } + } + fn report_no_match_method_error( &self, mut span: Span, @@ -754,40 +800,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, ); - if let Mode::MethodCall = mode - && let SelfSource::MethodCall(cal) = source - { - self.suggest_await_before_method( - &mut err, - item_ident, - rcvr_ty, - cal, - span, - expected.only_has_type(self), - ); - } - - self.suggest_on_pointer_type(&mut err, source, rcvr_ty, item_ident); - - if let SelfSource::MethodCall(rcvr_expr) = source { - self.suggest_fn_call(&mut err, rcvr_expr, rcvr_ty, |output_ty| { - let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id)); - let probe = self.lookup_probe_for_diagnostic( - item_ident, - output_ty, - call_expr, - ProbeScope::AllTraits, - expected.only_has_type(self), - ); - probe.is_ok() - }); - self.note_internal_mutation_in_method( - &mut err, - rcvr_expr, - expected.to_option(self), - rcvr_ty, - ); - } + self.suggest_method_call_annotation( + &mut err, span, rcvr_ty, item_ident, mode, source, expected, + ); let mut custom_span_label = false; let mut static_candidates = no_match_data.static_candidates.clone(); From 117d6765db66155c166b0406fe41c9d163fa283f Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 09:57:22 +0100 Subject: [PATCH 02/12] Add sub-fn for static method candidates in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 119 +++++++++++------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 56f4a2a70a9bf..bc7e3ca4805c7 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -712,6 +712,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn suggest_static_method_candidates( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + source: SelfSource<'tcx>, + args: Option<&'tcx [hir::Expr<'tcx>]>, + sugg_span: Span, + no_match_data: &NoMatchData<'tcx>, + ) -> Vec { + let mut static_candidates = no_match_data.static_candidates.clone(); + + // `static_candidates` may have same candidates appended by + // inherent and extension, which may result in incorrect + // diagnostic. + static_candidates.dedup(); + + if !static_candidates.is_empty() { + err.note( + "found the following associated functions; to be used as methods, \ + functions must have a `self` parameter", + ); + err.span_label(span, "this is an associated function, not a method"); + } + if static_candidates.len() == 1 { + self.suggest_associated_call_syntax( + err, + &static_candidates, + rcvr_ty, + source, + item_ident, + args, + sugg_span, + ); + self.note_candidates_on_method_error( + rcvr_ty, + item_ident, + source, + args, + span, + err, + &mut static_candidates, + None, + ); + } else if static_candidates.len() > 1 { + self.note_candidates_on_method_error( + rcvr_ty, + item_ident, + source, + args, + span, + err, + &mut static_candidates, + Some(sugg_span), + ); + } + static_candidates + } + fn report_no_match_method_error( &self, mut span: Span, @@ -804,54 +864,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &mut err, span, rcvr_ty, item_ident, mode, source, expected, ); - let mut custom_span_label = false; - let mut static_candidates = no_match_data.static_candidates.clone(); - - // `static_candidates` may have same candidates appended by - // inherent and extension, which may result in incorrect - // diagnostic. - static_candidates.dedup(); - - if !static_candidates.is_empty() { - err.note( - "found the following associated functions; to be used as methods, \ - functions must have a `self` parameter", - ); - err.span_label(span, "this is an associated function, not a method"); - custom_span_label = true; - } - if static_candidates.len() == 1 { - self.suggest_associated_call_syntax( - &mut err, - &static_candidates, - rcvr_ty, - source, - item_ident, - args, - sugg_span, - ); - self.note_candidates_on_method_error( - rcvr_ty, - item_ident, - source, - args, - span, - &mut err, - &mut static_candidates, - None, - ); - } else if static_candidates.len() > 1 { - self.note_candidates_on_method_error( - rcvr_ty, - item_ident, - source, - args, - span, - &mut err, - &mut static_candidates, - Some(sugg_span), - ); - } + let static_candidates = self.suggest_static_method_candidates( + &mut err, + span, + rcvr_ty, + item_ident, + source, + args, + sugg_span, + &no_match_data, + ); + let mut custom_span_label = !static_candidates.is_empty(); let mut bound_spans: SortedMap> = Default::default(); let mut restrict_type_params = false; From f23675f7f3e7b61aadef33c2c35af9c17cb3bc18 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 10:14:59 +0100 Subject: [PATCH 03/12] Add sub-fn for unsatisfied ty or trait in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 222 +++++++++++------- 1 file changed, 132 insertions(+), 90 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index bc7e3ca4805c7..1364e07ef03c6 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -772,6 +772,120 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { static_candidates } + fn suggest_unsatisfied_ty_or_trait( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + item_kind: &str, + source: SelfSource<'tcx>, + unsatisfied_predicates: &UnsatisfiedPredicates<'tcx>, + static_candidates: &[CandidateSource], + ) -> Result<(bool, bool, bool, bool, SortedMap>), ()> { + let mut restrict_type_params = false; + let mut suggested_derive = false; + let mut unsatisfied_bounds = false; + let mut custom_span_label = !static_candidates.is_empty(); + let mut bound_spans: SortedMap> = Default::default(); + let tcx = self.tcx; + + if item_ident.name == sym::count && self.is_slice_ty(rcvr_ty, span) { + let msg = "consider using `len` instead"; + if let SelfSource::MethodCall(_expr) = source { + err.span_suggestion_short(span, msg, "len", Applicability::MachineApplicable); + } else { + err.span_label(span, msg); + } + if let Some(iterator_trait) = self.tcx.get_diagnostic_item(sym::Iterator) { + let iterator_trait = self.tcx.def_path_str(iterator_trait); + err.note(format!( + "`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement" + )); + } + } else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates) + { + err.span_label(span, format!("`{rcvr_ty}` is not an iterator")); + if !span.in_external_macro(self.tcx.sess.source_map()) { + err.multipart_suggestion_verbose( + "call `.into_iter()` first", + vec![(span.shrink_to_lo(), format!("into_iter()."))], + Applicability::MaybeIncorrect, + ); + } + // Report to emit the diagnostic + return Err(()); + } else if !unsatisfied_predicates.is_empty() { + if matches!(rcvr_ty.kind(), ty::Param(_)) { + // We special case the situation where we are looking for `_` in + // `::method` because otherwise the machinery will look for blanket + // implementations that have unsatisfied trait bounds to suggest, leading us to claim + // things like "we're looking for a trait with method `cmp`, both `Iterator` and `Ord` + // have one, in order to implement `Ord` you need to restrict `TypeParam: FnPtr` so + // that `impl Ord for T` can apply", which is not what we want. We have a type + // parameter, we want to directly say "`Ord::cmp` and `Iterator::cmp` exist, restrict + // `TypeParam: Ord` or `TypeParam: Iterator`"". That is done further down when calling + // `self.suggest_traits_to_import`, so we ignore the `unsatisfied_predicates` + // suggestions. + } else { + self.handle_unsatisfied_predicates( + err, + rcvr_ty, + item_ident, + item_kind, + span, + unsatisfied_predicates, + &mut restrict_type_params, + &mut suggested_derive, + &mut unsatisfied_bounds, + &mut custom_span_label, + &mut bound_spans, + ); + } + } else if let ty::Adt(def, targs) = rcvr_ty.kind() + && let SelfSource::MethodCall(rcvr_expr) = source + { + // This is useful for methods on arbitrary self types that might have a simple + // mutability difference, like calling a method on `Pin<&mut Self>` that is on + // `Pin<&Self>`. + if targs.len() == 1 { + let mut item_segment = hir::PathSegment::invalid(); + item_segment.ident = item_ident; + for t in [Ty::new_mut_ref, Ty::new_imm_ref, |_, _, t| t] { + let new_args = + tcx.mk_args_from_iter(targs.iter().map(|arg| match arg.as_type() { + Some(ty) => ty::GenericArg::from(t( + tcx, + tcx.lifetimes.re_erased, + ty.peel_refs(), + )), + _ => arg, + })); + let rcvr_ty = Ty::new_adt(tcx, *def, new_args); + if let Ok(method) = self.lookup_method_for_diagnostic( + rcvr_ty, + &item_segment, + span, + tcx.parent_hir_node(rcvr_expr.hir_id).expect_expr(), + rcvr_expr, + ) { + err.span_note( + tcx.def_span(method.def_id), + format!("{item_kind} is available for `{rcvr_ty}`"), + ); + } + } + } + } + Ok(( + restrict_type_params, + suggested_derive, + unsatisfied_bounds, + custom_span_label, + bound_spans, + )) + } + fn report_no_match_method_error( &self, mut span: Span, @@ -874,12 +988,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sugg_span, &no_match_data, ); - let mut custom_span_label = !static_candidates.is_empty(); - let mut bound_spans: SortedMap> = Default::default(); - let mut restrict_type_params = false; - let mut suggested_derive = false; - let mut unsatisfied_bounds = false; let mut ty_span = match rcvr_ty.kind() { ty::Param(param_type) => { Some(param_type.span_from_generics(self.tcx, self.body_id.to_def_id())) @@ -888,92 +997,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }; - if item_ident.name == sym::count && self.is_slice_ty(rcvr_ty, span) { - let msg = "consider using `len` instead"; - if let SelfSource::MethodCall(_expr) = source { - err.span_suggestion_short(span, msg, "len", Applicability::MachineApplicable); - } else { - err.span_label(span, msg); - } - if let Some(iterator_trait) = self.tcx.get_diagnostic_item(sym::Iterator) { - let iterator_trait = self.tcx.def_path_str(iterator_trait); - err.note(format!( - "`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement" - )); - } - } else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates) - { - err.span_label(span, format!("`{rcvr_ty}` is not an iterator")); - if !span.in_external_macro(self.tcx.sess.source_map()) { - err.multipart_suggestion_verbose( - "call `.into_iter()` first", - vec![(span.shrink_to_lo(), format!("into_iter()."))], - Applicability::MaybeIncorrect, - ); - } + let Ok(( + restrict_type_params, + suggested_derive, + unsatisfied_bounds, + custom_span_label, + bound_spans, + )) = self.suggest_unsatisfied_ty_or_trait( + &mut err, + span, + rcvr_ty, + item_ident, + item_kind, + source, + unsatisfied_predicates, + &static_candidates, + ) + else { return err.emit(); - } else if !unsatisfied_predicates.is_empty() { - if matches!(rcvr_ty.kind(), ty::Param(_)) { - // We special case the situation where we are looking for `_` in - // `::method` because otherwise the machinery will look for blanket - // implementations that have unsatisfied trait bounds to suggest, leading us to claim - // things like "we're looking for a trait with method `cmp`, both `Iterator` and `Ord` - // have one, in order to implement `Ord` you need to restrict `TypeParam: FnPtr` so - // that `impl Ord for T` can apply", which is not what we want. We have a type - // parameter, we want to directly say "`Ord::cmp` and `Iterator::cmp` exist, restrict - // `TypeParam: Ord` or `TypeParam: Iterator`"". That is done further down when calling - // `self.suggest_traits_to_import`, so we ignore the `unsatisfied_predicates` - // suggestions. - } else { - self.handle_unsatisfied_predicates( - &mut err, - rcvr_ty, - item_ident, - item_kind, - span, - unsatisfied_predicates, - &mut restrict_type_params, - &mut suggested_derive, - &mut unsatisfied_bounds, - &mut custom_span_label, - &mut bound_spans, - ); - } - } else if let ty::Adt(def, targs) = rcvr_ty.kind() - && let SelfSource::MethodCall(rcvr_expr) = source - { - // This is useful for methods on arbitrary self types that might have a simple - // mutability difference, like calling a method on `Pin<&mut Self>` that is on - // `Pin<&Self>`. - if targs.len() == 1 { - let mut item_segment = hir::PathSegment::invalid(); - item_segment.ident = item_ident; - for t in [Ty::new_mut_ref, Ty::new_imm_ref, |_, _, t| t] { - let new_args = - tcx.mk_args_from_iter(targs.iter().map(|arg| match arg.as_type() { - Some(ty) => ty::GenericArg::from(t( - tcx, - tcx.lifetimes.re_erased, - ty.peel_refs(), - )), - _ => arg, - })); - let rcvr_ty = Ty::new_adt(tcx, *def, new_args); - if let Ok(method) = self.lookup_method_for_diagnostic( - rcvr_ty, - &item_segment, - span, - tcx.parent_hir_node(rcvr_expr.hir_id).expect_expr(), - rcvr_expr, - ) { - err.span_note( - tcx.def_span(method.def_id), - format!("{item_kind} is available for `{rcvr_ty}`"), - ); - } - } - } - } + }; let mut find_candidate_for_method = false; let should_label_not_found = match source { From 56ce33f57a3e3d3012e1a3efc5c027319c134007 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 14:03:08 +0100 Subject: [PATCH 04/12] Add sub-fn for surround method calls in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 1364e07ef03c6..8fa951dbc4801 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -886,6 +886,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )) } + fn suggest_surround_method_call( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + source: SelfSource<'tcx>, + similar_candidate: &Option, + ) -> bool { + match source { + // If the method name is the name of a field with a function or closure type, + // give a helping note that it has to be called as `(x.f)(...)`. + SelfSource::MethodCall(expr) => { + !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_ident, err) + && similar_candidate.is_none() + } + _ => true, + } + } + fn report_no_match_method_error( &self, mut span: Span, @@ -1018,15 +1038,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let mut find_candidate_for_method = false; - let should_label_not_found = match source { - // If the method name is the name of a field with a function or closure type, - // give a helping note that it has to be called as `(x.f)(...)`. - SelfSource::MethodCall(expr) => { - !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_ident, &mut err) - && similar_candidate.is_none() - } - _ => true, - }; + let should_label_not_found = self.suggest_surround_method_call( + &mut err, + span, + rcvr_ty, + item_ident, + source, + &similar_candidate, + ); if should_label_not_found && !custom_span_label { self.set_not_found_span_label( From d7a8f6bc08a204959eb98272defb68f773eb935a Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 14:21:16 +0100 Subject: [PATCH 05/12] Add sub-fn to find possible candidates for method in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 103 ++++++++++++------ 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 8fa951dbc4801..6f7eaa73907da 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -906,6 +906,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn find_possible_candidates_for_method( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + item_kind: &str, + mode: Mode, + source: SelfSource<'tcx>, + no_match_data: &NoMatchData<'tcx>, + expected: Expectation<'tcx>, + should_label_not_found: bool, + custom_span_label: bool, + ) { + let mut find_candidate_for_method = false; + let unsatisfied_predicates = &no_match_data.unsatisfied_predicates; + + if should_label_not_found && !custom_span_label { + self.set_not_found_span_label( + err, + rcvr_ty, + item_ident, + item_kind, + mode, + source, + span, + unsatisfied_predicates, + &mut find_candidate_for_method, + ); + } + if !find_candidate_for_method { + self.lookup_segments_chain_for_no_match_method( + err, + item_ident, + item_kind, + source, + no_match_data, + ); + } + + // Don't suggest (for example) `expr.field.clone()` if `expr.clone()` + // can't be called due to `typeof(expr): Clone` not holding. + if unsatisfied_predicates.is_empty() { + self.suggest_calling_method_on_field( + err, + source, + span, + rcvr_ty, + item_ident, + expected.only_has_type(self), + ); + } + } + fn report_no_match_method_error( &self, mut span: Span, @@ -1037,7 +1091,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return err.emit(); }; - let mut find_candidate_for_method = false; let should_label_not_found = self.suggest_surround_method_call( &mut err, span, @@ -1047,41 +1100,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &similar_candidate, ); - if should_label_not_found && !custom_span_label { - self.set_not_found_span_label( - &mut err, - rcvr_ty, - item_ident, - item_kind, - mode, - source, - span, - unsatisfied_predicates, - &mut find_candidate_for_method, - ); - } - if !find_candidate_for_method { - self.lookup_segments_chain_for_no_match_method( - &mut err, - item_ident, - item_kind, - source, - no_match_data, - ); - } - - // Don't suggest (for example) `expr.field.clone()` if `expr.clone()` - // can't be called due to `typeof(expr): Clone` not holding. - if unsatisfied_predicates.is_empty() { - self.suggest_calling_method_on_field( - &mut err, - source, - span, - rcvr_ty, - item_ident, - expected.only_has_type(self), - ); - } + self.find_possible_candidates_for_method( + &mut err, + span, + rcvr_ty, + item_ident, + item_kind, + mode, + source, + no_match_data, + expected, + should_label_not_found, + custom_span_label, + ); self.suggest_unwrapping_inner_self(&mut err, source, rcvr_ty, item_ident); From a33555b6f52582b1fe68488ef6f71192e098fb64 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 14:34:10 +0100 Subject: [PATCH 06/12] Add sub-fn for confusable or similarly named methods in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 6f7eaa73907da..88cd5a320da31 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -960,6 +960,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn suggest_confusable_or_similarly_named_method( + &self, + err: &mut Diag<'_>, + span: Span, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + mode: Mode, + args: Option<&'tcx [hir::Expr<'tcx>]>, + unsatisfied_predicates: &UnsatisfiedPredicates<'tcx>, + similar_candidate: Option, + ) { + let confusable_suggested = self.confusable_method_name( + err, + rcvr_ty, + item_ident, + args.map(|args| { + args.iter() + .map(|expr| { + self.node_ty_opt(expr.hir_id).unwrap_or_else(|| self.next_ty_var(expr.span)) + }) + .collect() + }), + ); + if let Some(similar_candidate) = similar_candidate { + // Don't emit a suggestion if we found an actual method + // that had unsatisfied trait bounds + if unsatisfied_predicates.is_empty() + // ...or if we already suggested that name because of `rustc_confusable` annotation + && Some(similar_candidate.name()) != confusable_suggested + // and if we aren't in an expansion. + && !span.from_expansion() + { + self.find_likely_intended_associated_item(err, similar_candidate, span, args, mode); + } + } + } + fn report_no_match_method_error( &self, mut span: Span, @@ -1142,36 +1179,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { source, unsatisfied_predicates, ); - let confusable_suggested = self.confusable_method_name( + + self.suggest_confusable_or_similarly_named_method( &mut err, + span, rcvr_ty, item_ident, - args.map(|args| { - args.iter() - .map(|expr| { - self.node_ty_opt(expr.hir_id).unwrap_or_else(|| self.next_ty_var(expr.span)) - }) - .collect() - }), + mode, + args, + unsatisfied_predicates, + similar_candidate, ); - if let Some(similar_candidate) = similar_candidate { - // Don't emit a suggestion if we found an actual method - // that had unsatisfied trait bounds - if unsatisfied_predicates.is_empty() - // ...or if we already suggested that name because of `rustc_confusable` annotation - && Some(similar_candidate.name()) != confusable_suggested - // and if we aren't in an expansion. - && !span.from_expansion() - { - self.find_likely_intended_associated_item( - &mut err, - similar_candidate, - span, - args, - mode, - ); - } - } for (span, mut bounds) in bound_spans { if !tcx.sess.source_map().is_span_accessible(span) { From ae2c070a356091bf96adbc55cf110d6c3c500c7e Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 14:41:07 +0100 Subject: [PATCH 07/12] Add sub-fn for not found methods with unsatisfied bounds in FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit refactors it and improves its readability by adding sub-fn --- .../rustc_hir_typeck/src/method/suggest.rs | 100 ++++++++++-------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 88cd5a320da31..72644e0c4c227 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -997,6 +997,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn suggest_method_not_found_because_of_unsatisfied_bounds( + &self, + err: &mut Diag<'_>, + rcvr_ty: Ty<'tcx>, + item_ident: Ident, + item_kind: &str, + bound_spans: SortedMap>, + ) { + let mut ty_span = match rcvr_ty.kind() { + ty::Param(param_type) => { + Some(param_type.span_from_generics(self.tcx, self.body_id.to_def_id())) + } + ty::Adt(def, _) if def.did().is_local() => Some(self.tcx.def_span(def.did())), + _ => None, + }; + for (span, mut bounds) in bound_spans { + if !self.tcx.sess.source_map().is_span_accessible(span) { + continue; + } + bounds.sort(); + bounds.dedup(); + let pre = if Some(span) == ty_span { + ty_span.take(); + format!( + "{item_kind} `{item_ident}` not found for this {} because it ", + rcvr_ty.prefix_string(self.tcx) + ) + } else { + String::new() + }; + let msg = match &bounds[..] { + [bound] => format!("{pre}doesn't satisfy {bound}"), + bounds if bounds.len() > 4 => format!("doesn't satisfy {} bounds", bounds.len()), + [bounds @ .., last] => { + format!("{pre}doesn't satisfy {} or {last}", bounds.join(", ")) + } + [] => unreachable!(), + }; + err.span_label(span, msg); + } + if let Some(span) = ty_span { + err.span_label( + span, + format!( + "{item_kind} `{item_ident}` not found for this {}", + rcvr_ty.prefix_string(self.tcx) + ), + ); + } + } + fn report_no_match_method_error( &self, mut span: Span, @@ -1100,14 +1151,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &no_match_data, ); - let mut ty_span = match rcvr_ty.kind() { - ty::Param(param_type) => { - Some(param_type.span_from_generics(self.tcx, self.body_id.to_def_id())) - } - ty::Adt(def, _) if def.did().is_local() => Some(tcx.def_span(def.did())), - _ => None, - }; - let Ok(( restrict_type_params, suggested_derive, @@ -1191,40 +1234,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { similar_candidate, ); - for (span, mut bounds) in bound_spans { - if !tcx.sess.source_map().is_span_accessible(span) { - continue; - } - bounds.sort(); - bounds.dedup(); - let pre = if Some(span) == ty_span { - ty_span.take(); - format!( - "{item_kind} `{item_ident}` not found for this {} because it ", - rcvr_ty.prefix_string(self.tcx) - ) - } else { - String::new() - }; - let msg = match &bounds[..] { - [bound] => format!("{pre}doesn't satisfy {bound}"), - bounds if bounds.len() > 4 => format!("doesn't satisfy {} bounds", bounds.len()), - [bounds @ .., last] => { - format!("{pre}doesn't satisfy {} or {last}", bounds.join(", ")) - } - [] => unreachable!(), - }; - err.span_label(span, msg); - } - if let Some(span) = ty_span { - err.span_label( - span, - format!( - "{item_kind} `{item_ident}` not found for this {}", - rcvr_ty.prefix_string(self.tcx) - ), - ); - } + self.suggest_method_not_found_because_of_unsatisfied_bounds( + &mut err, + rcvr_ty, + item_ident, + item_kind, + bound_spans, + ); self.note_derefed_ty_has_method(&mut err, source, rcvr_ty, item_ident, expected); self.suggest_bounds_for_range_to_method(&mut err, source, item_ident); From 4c952ebcd602f8317fed987e33b088d773309375 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 12 Nov 2025 15:11:07 +0100 Subject: [PATCH 08/12] Refactor and cleanup FnCtxt::report_no_match_method_error Currently this method is quiet long and complex, this commit improves its readability, refactor and cleanup few things --- .../rustc_hir_typeck/src/method/suggest.rs | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 72644e0c4c227..9a657ab159035 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1050,7 +1050,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn report_no_match_method_error( &self, - mut span: Span, + span: Span, rcvr_ty: Ty<'tcx>, item_ident: Ident, expr_id: hir::HirId, @@ -1062,13 +1062,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_missing_method: bool, within_macro_span: Option, ) -> ErrorGuaranteed { - let mode = no_match_data.mode; let tcx = self.tcx; let rcvr_ty = self.resolve_vars_if_possible(rcvr_ty); + + if let Err(guar) = rcvr_ty.error_reported() { + return guar; + } + + // We could pass the file for long types into these two, but it isn't strictly necessary + // given how targeted they are. + if let Err(guar) = + self.report_failed_method_call_on_range_end(tcx, rcvr_ty, source, span, item_ident) + { + return guar; + } + let mut ty_file = None; + let mode = no_match_data.mode; let is_method = mode == Mode::MethodCall; - let unsatisfied_predicates = &no_match_data.unsatisfied_predicates; - let similar_candidate = no_match_data.similar_candidate; let item_kind = if is_method { "method" } else if rcvr_ty.is_enum() { @@ -1082,13 +1093,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - // We could pass the file for long types into these two, but it isn't strictly necessary - // given how targeted they are. - if let Err(guar) = - self.report_failed_method_call_on_range_end(tcx, rcvr_ty, source, span, item_ident) - { - return guar; - } if let Err(guar) = self.report_failed_method_call_on_numerical_infer_var( tcx, rcvr_ty, @@ -1100,8 +1104,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { return guar; } - span = item_ident.span; + let unsatisfied_predicates = &no_match_data.unsatisfied_predicates; let is_write = sugg_span.ctxt().outer_expn_data().macro_def_id.is_some_and(|def_id| { tcx.is_diagnostic_item(sym::write_macro, def_id) || tcx.is_diagnostic_item(sym::writeln_macro, def_id) @@ -1120,9 +1124,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { unsatisfied_predicates, ) }; - if rcvr_ty.references_error() { - err.downgrade_to_delayed_bug(); - } self.set_label_for_method_error( &mut err, @@ -1130,19 +1131,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rcvr_ty, item_ident, expr_id, - span, + item_ident.span, sugg_span, within_macro_span, args, ); self.suggest_method_call_annotation( - &mut err, span, rcvr_ty, item_ident, mode, source, expected, + &mut err, + item_ident.span, + rcvr_ty, + item_ident, + mode, + source, + expected, ); let static_candidates = self.suggest_static_method_candidates( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, source, @@ -1159,7 +1166,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { bound_spans, )) = self.suggest_unsatisfied_ty_or_trait( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, item_kind, @@ -1171,9 +1178,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return err.emit(); }; + let similar_candidate = no_match_data.similar_candidate; let should_label_not_found = self.suggest_surround_method_call( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, source, @@ -1182,7 +1190,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.find_possible_candidates_for_method( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, item_kind, @@ -1201,7 +1209,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { self.suggest_traits_to_import( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, args.map(|args| args.len() + 1), @@ -1218,14 +1226,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &mut err, rcvr_ty, item_ident, - span, + item_ident.span, source, unsatisfied_predicates, ); self.suggest_confusable_or_similarly_named_method( &mut err, - span, + item_ident.span, rcvr_ty, item_ident, mode, From a21affabf848c71e5a518410568cdadd455c509f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 24 Nov 2025 16:41:24 +0100 Subject: [PATCH 09/12] Fix invalid link generation for type alias methods --- src/librustdoc/html/render/write_shared.rs | 12 ++++-------- tests/rustdoc-gui/src/test_docs/lib.rs | 10 ++++++++++ tests/rustdoc-gui/type-alias.goml | 7 +++++++ 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 tests/rustdoc-gui/type-alias.goml diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 6045b9a77ecae..31da7b7de9206 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -568,18 +568,14 @@ impl TypeAliasPart { if let Some(ret) = &mut ret { ret.aliases.push(type_alias_fqp); } else { - let target_did = impl_ - .inner_impl() - .trait_ - .as_ref() - .map(|trait_| trait_.def_id()) - .or_else(|| impl_.inner_impl().for_.def_id(&cx.shared.cache)); + let target_trait_did = + impl_.inner_impl().trait_.as_ref().map(|trait_| trait_.def_id()); let provided_methods; - let assoc_link = if let Some(target_did) = target_did { + let assoc_link = if let Some(target_trait_did) = target_trait_did { provided_methods = impl_.inner_impl().provided_trait_methods(cx.tcx()); AssocItemLink::GotoSource( - ItemId::DefId(target_did), + ItemId::DefId(target_trait_did), &provided_methods, ) } else { diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index c0771583ab658..0ee2a66d4b689 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -786,3 +786,13 @@ pub mod tooltips { Vec::new() } } + +pub mod tyalias { + pub struct X(pub T); + + impl X { + pub fn blob(&self) {} + } + + pub type Y = X; +} diff --git a/tests/rustdoc-gui/type-alias.goml b/tests/rustdoc-gui/type-alias.goml new file mode 100644 index 0000000000000..a07f1b4eb8147 --- /dev/null +++ b/tests/rustdoc-gui/type-alias.goml @@ -0,0 +1,7 @@ +// This test ensures that we correctly generate links to methods on type aliases. +go-to: "file://" + |DOC_PATH| + "/test_docs/tyalias/type.Y.html" + +// It's generated with JS so we need to wait for it to be done generating. +wait-for: "#implementations" +// We check that it's "#method." and not "#tymethod.". +assert-text: ('#method\.blob a.fn[href="#method.blob"]', "blob") From 3181c21b6cf996047113e91a2bf7f2c8abdafd1e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 24 Nov 2025 16:41:30 +0100 Subject: [PATCH 10/12] Improve variable naming --- src/librustdoc/html/render/mod.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 871ed53bd3380..b5c44686cafcf 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2025,13 +2025,11 @@ fn render_impl( let mut methods = Vec::new(); if !impl_.is_negative_trait_impl() { - for trait_item in &impl_.items { - match trait_item.kind { - clean::MethodItem(..) | clean::RequiredMethodItem(_) => { - methods.push(trait_item) - } + for impl_item in &impl_.items { + match impl_item.kind { + clean::MethodItem(..) | clean::RequiredMethodItem(_) => methods.push(impl_item), clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => { - assoc_types.push(trait_item) + assoc_types.push(impl_item) } clean::RequiredAssocConstItem(..) | clean::ProvidedAssocConstItem(_) @@ -2041,7 +2039,7 @@ fn render_impl( &mut default_impl_items, &mut impl_items, cx, - trait_item, + impl_item, if trait_.is_some() { &i.impl_item } else { parent }, link, render_mode, From 30c2e26506eb672386c70ea2e4d7e0b102f86d2d Mon Sep 17 00:00:00 2001 From: Jane Losare-Lusby Date: Fri, 21 Nov 2025 14:08:33 -0800 Subject: [PATCH 11/12] Add test for derive helper compat collisions --- .../auxiliary/extra-empty-derive.rs | 7 ++++++ .../helper-attr-compat-collision.rs | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/ui/proc-macro/auxiliary/extra-empty-derive.rs create mode 100644 tests/ui/proc-macro/helper-attr-compat-collision.rs diff --git a/tests/ui/proc-macro/auxiliary/extra-empty-derive.rs b/tests/ui/proc-macro/auxiliary/extra-empty-derive.rs new file mode 100644 index 0000000000000..9a89e04364d75 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/extra-empty-derive.rs @@ -0,0 +1,7 @@ +extern crate proc_macro; +use proc_macro::{TokenStream, TokenTree}; + +#[proc_macro_derive(Empty2, attributes(empty_helper))] +pub fn empty_derive2(_: TokenStream) -> TokenStream { + TokenStream::new() +} diff --git a/tests/ui/proc-macro/helper-attr-compat-collision.rs b/tests/ui/proc-macro/helper-attr-compat-collision.rs new file mode 100644 index 0000000000000..1ed2f4c550663 --- /dev/null +++ b/tests/ui/proc-macro/helper-attr-compat-collision.rs @@ -0,0 +1,23 @@ +//@ proc-macro: test-macros.rs +//@ proc-macro: extra-empty-derive.rs +//@ build-pass + +#[macro_use(Empty)] +extern crate test_macros; +#[macro_use(Empty2)] +extern crate extra_empty_derive; + +// Testing the behavior of derive attributes with helpers that share the same name. +// +// Normally if the first derive below were absent the call to #[empty_helper] before it it +// introduced by its own derive would produce a future incompat error. +// +// With the extra derive also introducing that attribute in advanced the warning gets supressed. +// Demonstrates a lack of identity to helper attributes, the compiler does not track which derive +// introduced a helper, just that a derive introduced the helper. +#[derive(Empty)] +#[empty_helper] +#[derive(Empty2)] +struct S; + +fn main() {} From 7537b0bc068b021fb852e8df92c3476b61527e15 Mon Sep 17 00:00:00 2001 From: Jane Losare-Lusby Date: Mon, 24 Nov 2025 10:41:28 -0800 Subject: [PATCH 12/12] Update tests/ui/proc-macro/helper-attr-compat-collision.rs Co-authored-by: Vadim Petrochenkov --- tests/ui/proc-macro/helper-attr-compat-collision.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/proc-macro/helper-attr-compat-collision.rs b/tests/ui/proc-macro/helper-attr-compat-collision.rs index 1ed2f4c550663..7755582c46322 100644 --- a/tests/ui/proc-macro/helper-attr-compat-collision.rs +++ b/tests/ui/proc-macro/helper-attr-compat-collision.rs @@ -1,6 +1,6 @@ //@ proc-macro: test-macros.rs //@ proc-macro: extra-empty-derive.rs -//@ build-pass +//@ check-pass #[macro_use(Empty)] extern crate test_macros;