diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 43a5145b7e7..edfe12963dc 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -20,11 +20,10 @@ use rustc_infer::infer::{ }; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::traits::util::supertraits; +use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::fast_reject::{simplify_type, TreatParams}; use rustc_middle::ty::print::with_crate_prefix; -use rustc_middle::ty::{ - self, DefIdTree, GenericArg, GenericArgKind, ToPredicate, Ty, TyCtxt, TypeVisitable, -}; +use rustc_middle::ty::{self, DefIdTree, GenericArgKind, ToPredicate, Ty, TyCtxt, TypeVisitable}; use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::Symbol; @@ -263,15 +262,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) => { let tcx = self.tcx; - let actual = self.resolve_vars_if_possible(rcvr_ty); - let ty_str = self.ty_to_string(actual); + let rcvr_ty = self.resolve_vars_if_possible(rcvr_ty); + let ty_str = self.ty_to_string(rcvr_ty); let is_method = mode == Mode::MethodCall; let item_kind = if is_method { "method" - } else if actual.is_enum() { + } else if rcvr_ty.is_enum() { "variant or associated item" } else { - match (item_name.as_str().chars().next(), actual.is_fresh_ty()) { + match (item_name.as_str().chars().next(), rcvr_ty.is_fresh_ty()) { (Some(name), false) if name.is_lowercase() => "function or associated item", (Some(_), false) => "associated item", (Some(_), true) | (None, false) => "variant or associated item", @@ -280,9 +279,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if self.suggest_wrapping_range_with_parens( - tcx, actual, source, span, item_name, &ty_str, + tcx, rcvr_ty, source, span, item_name, &ty_str, ) || self.suggest_constraining_numerical_ty( - tcx, actual, source, span, item_kind, item_name, &ty_str, + tcx, rcvr_ty, source, span, item_kind, item_name, &ty_str, ) { return None; } @@ -290,9 +289,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Don't show generic arguments when the method can't be found in any implementation (#81576). let mut ty_str_reported = ty_str.clone(); - if let ty::Adt(_, generics) = actual.kind() { + if let ty::Adt(_, generics) = rcvr_ty.kind() { if generics.len() > 0 { - let mut autoderef = self.autoderef(span, actual); + let mut autoderef = self.autoderef(span, rcvr_ty); let candidate_found = autoderef.any(|(ty, _)| { if let ty::Adt(adt_def, _) = ty.kind() { self.tcx @@ -321,16 +320,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "no {} named `{}` found for {} `{}` in the current scope", item_kind, item_name, - actual.prefix_string(self.tcx), + rcvr_ty.prefix_string(self.tcx), ty_str_reported, ); - if actual.references_error() { + if rcvr_ty.references_error() { err.downgrade_to_delayed_bug(); } if let Mode::MethodCall = mode && let SelfSource::MethodCall(cal) = source { self.suggest_await_before_method( - &mut err, item_name, actual, cal, span, + &mut err, item_name, rcvr_ty, cal, span, ); } if let Some(span) = tcx.resolutions(()).confused_type_with_std_module.get(&span) { @@ -341,7 +340,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MachineApplicable, ); } - if let ty::RawPtr(_) = &actual.kind() { + if let ty::RawPtr(_) = &rcvr_ty.kind() { err.note( "try using `<*const T>::as_ref()` to get a reference to the \ type behind the pointer: https://doc.rust-lang.org/std/\ @@ -353,7 +352,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - let ty_span = match actual.kind() { + let ty_span = match rcvr_ty.kind() { ty::Param(param_type) => Some( param_type.span_from_generics(self.tcx, self.body_id.owner.to_def_id()), ), @@ -365,7 +364,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, format!( "{item_kind} `{item_name}` not found for this {}", - actual.prefix_string(self.tcx) + rcvr_ty.prefix_string(self.tcx) ), ); } @@ -398,122 +397,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { custom_span_label = true; } if static_candidates.len() == 1 { - let mut has_unsuggestable_args = false; - let ty_str = if let Some(CandidateSource::Impl(impl_did)) = - static_candidates.get(0) - { - // When the "method" is resolved through dereferencing, we really want the - // original type that has the associated function for accurate suggestions. - // (#61411) - let ty = tcx.at(span).type_of(*impl_did); - match (&ty.peel_refs().kind(), &actual.peel_refs().kind()) { - (ty::Adt(def, _), ty::Adt(def_actual, substs)) if def == def_actual => { - // If there are any inferred arguments, (`{integer}`), we should replace - // them with underscores to allow the compiler to infer them - let infer_substs: Vec> = substs - .into_iter() - .map(|arg| { - if !arg.is_suggestable(tcx, true) { - has_unsuggestable_args = true; - match arg.unpack() { - GenericArgKind::Lifetime(_) => self - .next_region_var(RegionVariableOrigin::MiscVariable( - rustc_span::DUMMY_SP, - )) - .into(), - GenericArgKind::Type(_) => self - .next_ty_var(TypeVariableOrigin { - span: rustc_span::DUMMY_SP, - kind: TypeVariableOriginKind::MiscVariable, - }) - .into(), - GenericArgKind::Const(arg) => self - .next_const_var( - arg.ty(), - ConstVariableOrigin { - span: rustc_span::DUMMY_SP, - kind: ConstVariableOriginKind::MiscVariable, - }, - ) - .into(), - } - } else { - arg - } - }) - .collect::>(); - - tcx.value_path_str_with_substs( - def_actual.did(), - tcx.intern_substs(&infer_substs), - ) - } - _ => self.ty_to_value_string(ty.peel_refs()), - } - } else { - self.ty_to_value_string(actual.peel_refs()) - }; - if let SelfSource::MethodCall(_) = source { - let first_arg = if let Some(CandidateSource::Impl(impl_did)) = static_candidates.get(0) && - let Some(assoc) = self.associated_value(*impl_did, item_name) { - let sig = self.tcx.fn_sig(assoc.def_id); - if let Some(first) = sig.inputs().skip_binder().get(0) { - if first.peel_refs() == rcvr_ty.peel_refs() { - None - } else { - Some(if first.is_region_ptr() { - if first.is_mutable_ptr() { "&mut " } else { "&" } - } else { - "" - }) - } - } else { - None - } - } else { - None - }; - let mut applicability = Applicability::MachineApplicable; - let args = if let Some((receiver, args)) = args { - // The first arg is the same kind as the receiver - let explicit_args = if first_arg.is_some() { - std::iter::once(receiver).chain(args.iter()).collect::>() - } else { - // There is no `Self` kind to infer the arguments from - if has_unsuggestable_args { - applicability = Applicability::HasPlaceholders; - } - args.iter().collect() - }; - format!( - "({}{})", - first_arg.unwrap_or(""), - explicit_args - .iter() - .map(|arg| tcx - .sess - .source_map() - .span_to_snippet(arg.span) - .unwrap_or_else(|_| { - applicability = Applicability::HasPlaceholders; - "_".to_owned() - })) - .collect::>() - .join(", "), - ) - } else { - applicability = Applicability::HasPlaceholders; - "(...)".to_owned() - }; - err.span_suggestion( - sugg_span, - "use associated function syntax instead", - format!("{}::{}{}", ty_str, item_name, args), - applicability, - ); - } else { - err.help(&format!("try with `{}::{}`", ty_str, item_name,)); - } + self.suggest_associated_call_syntax( + &mut err, + &static_candidates, + rcvr_ty, + source, + item_name, + args, + sugg_span, + ); report_candidates(span, &mut err, &mut static_candidates, sugg_span); } else if static_candidates.len() > 1 { @@ -523,7 +415,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut bound_spans = vec![]; let mut restrict_type_params = false; let mut unsatisfied_bounds = false; - if item_name.name == sym::count && self.is_slice_ty(actual, span) { + if item_name.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( @@ -537,7 +429,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } 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 `{actual}` does not implement")); + err.note(&format!("`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement")); } } else if !unsatisfied_predicates.is_empty() { let mut type_params = FxHashMap::default(); @@ -876,7 +768,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .map(|(_, path)| path) .collect::>() .join("\n"); - let actual_prefix = actual.prefix_string(self.tcx); + let actual_prefix = rcvr_ty.prefix_string(self.tcx); info!("unimplemented_traits.len() == {}", unimplemented_traits.len()); let (primary_message, label) = if unimplemented_traits.len() == 1 && unimplemented_traits_only { @@ -885,7 +777,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .next() .map(|(_, (trait_ref, obligation))| { if trait_ref.self_ty().references_error() - || actual.references_error() + || rcvr_ty.references_error() { // Avoid crashing. return (None, None); @@ -921,7 +813,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let label_span_not_found = |err: &mut Diagnostic| { if unsatisfied_predicates.is_empty() { err.span_label(span, format!("{item_kind} not found in `{ty_str}`")); - let is_string_or_ref_str = match actual.kind() { + let is_string_or_ref_str = match rcvr_ty.kind() { ty::Ref(_, ty, _) => { ty.is_str() || matches!( @@ -957,7 +849,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // different from the received one // So we avoid suggestion method with Box // for instance - self.tcx.at(span).type_of(*def_id) != actual + self.tcx.at(span).type_of(*def_id) != rcvr_ty && self.tcx.at(span).type_of(*def_id) != rcvr_ty } (Mode::Path, false, _) => true, @@ -1017,10 +909,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // 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, actual, item_name); + self.suggest_calling_method_on_field( + &mut err, source, span, rcvr_ty, item_name, + ); } - self.check_for_inner_self(&mut err, source, span, actual, item_name); + self.check_for_inner_self(&mut err, source, span, rcvr_ty, item_name); bound_spans.sort(); bound_spans.dedup(); @@ -1028,7 +922,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(span, &msg); } - if actual.is_numeric() && actual.is_fresh() || restrict_type_params { + if rcvr_ty.is_numeric() && rcvr_ty.is_fresh() || restrict_type_params { } else { self.suggest_traits_to_import( &mut err, @@ -1046,8 +940,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Don't emit a suggestion if we found an actual method // that had unsatisfied trait bounds - if unsatisfied_predicates.is_empty() && actual.is_enum() { - let adt_def = actual.ty_adt_def().expect("enum is not an ADT"); + if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() { + let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT"); if let Some(suggestion) = lev_distance::find_best_match_for_name( &adt_def.variants().iter().map(|s| s.name).collect::>(), item_name.name, @@ -1062,7 +956,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - if item_name.name == sym::as_str && actual.peel_refs().is_str() { + if item_name.name == sym::as_str && rcvr_ty.peel_refs().is_str() { let msg = "remove this method call"; let mut fallback_span = true; if let SelfSource::MethodCall(expr) = source { @@ -1178,6 +1072,138 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None } + /// Suggest calling `Ty::method` if `.method()` isn't found because the method + /// doesn't take a `self` receiver. + fn suggest_associated_call_syntax( + &self, + err: &mut Diagnostic, + static_candidates: &Vec, + rcvr_ty: Ty<'tcx>, + source: SelfSource<'tcx>, + item_name: Ident, + args: Option<(&hir::Expr<'tcx>, &[hir::Expr<'tcx>])>, + sugg_span: Span, + ) { + let mut has_unsuggestable_args = false; + let ty_str = if let Some(CandidateSource::Impl(impl_did)) = static_candidates.get(0) { + // When the "method" is resolved through dereferencing, we really want the + // original type that has the associated function for accurate suggestions. + // (#61411) + let impl_ty = self.tcx.type_of(*impl_did); + let target_ty = self + .autoderef(sugg_span, rcvr_ty) + .find(|(rcvr_ty, _)| { + DeepRejectCtxt { treat_obligation_params: TreatParams::AsInfer } + .types_may_unify(*rcvr_ty, impl_ty) + }) + .map_or(impl_ty, |(ty, _)| ty) + .peel_refs(); + if let ty::Adt(def, substs) = target_ty.kind() { + // If there are any inferred arguments, (`{integer}`), we should replace + // them with underscores to allow the compiler to infer them + let infer_substs = self.tcx.mk_substs(substs.into_iter().map(|arg| { + if !arg.is_suggestable(self.tcx, true) { + has_unsuggestable_args = true; + match arg.unpack() { + GenericArgKind::Lifetime(_) => self + .next_region_var(RegionVariableOrigin::MiscVariable( + rustc_span::DUMMY_SP, + )) + .into(), + GenericArgKind::Type(_) => self + .next_ty_var(TypeVariableOrigin { + span: rustc_span::DUMMY_SP, + kind: TypeVariableOriginKind::MiscVariable, + }) + .into(), + GenericArgKind::Const(arg) => self + .next_const_var( + arg.ty(), + ConstVariableOrigin { + span: rustc_span::DUMMY_SP, + kind: ConstVariableOriginKind::MiscVariable, + }, + ) + .into(), + } + } else { + arg + } + })); + + self.tcx.value_path_str_with_substs(def.did(), infer_substs) + } else { + self.ty_to_value_string(target_ty) + } + } else { + self.ty_to_value_string(rcvr_ty.peel_refs()) + }; + if let SelfSource::MethodCall(_) = source { + let first_arg = if let Some(CandidateSource::Impl(impl_did)) = static_candidates.get(0) + && let Some(assoc) = self.associated_value(*impl_did, item_name) + && assoc.kind == ty::AssocKind::Fn + { + let sig = self.tcx.fn_sig(assoc.def_id); + if let Some(first) = sig.inputs().skip_binder().get(0) { + if first.peel_refs() == rcvr_ty.peel_refs() { + None + } else { + Some(if first.is_region_ptr() { + if first.is_mutable_ptr() { "&mut " } else { "&" } + } else { + "" + }) + } + } else { + None + } + } else { + None + }; + let mut applicability = Applicability::MachineApplicable; + let args = if let Some((receiver, args)) = args { + // The first arg is the same kind as the receiver + let explicit_args = if first_arg.is_some() { + std::iter::once(receiver).chain(args.iter()).collect::>() + } else { + // There is no `Self` kind to infer the arguments from + if has_unsuggestable_args { + applicability = Applicability::HasPlaceholders; + } + args.iter().collect() + }; + format!( + "({}{})", + first_arg.unwrap_or(""), + explicit_args + .iter() + .map(|arg| self + .tcx + .sess + .source_map() + .span_to_snippet(arg.span) + .unwrap_or_else(|_| { + applicability = Applicability::HasPlaceholders; + "_".to_owned() + })) + .collect::>() + .join(", "), + ) + } else { + applicability = Applicability::HasPlaceholders; + "(...)".to_owned() + }; + err.span_suggestion( + sugg_span, + "use associated function syntax instead", + format!("{}::{}{}", ty_str, item_name, args), + applicability, + ); + } else { + err.help(&format!("try with `{}::{}`", ty_str, item_name,)); + } + } + /// Suggest calling a field with a type that implements the `Fn*` traits instead of a method with /// the same name as the field i.e. `(a.my_fn_ptr)(10)` instead of `a.my_fn_ptr(10)`. fn suggest_calling_field_as_fn( diff --git a/src/test/ui/suggestions/dont-suggest-ufcs-for-const.rs b/src/test/ui/suggestions/dont-suggest-ufcs-for-const.rs new file mode 100644 index 00000000000..06cf243f1b4 --- /dev/null +++ b/src/test/ui/suggestions/dont-suggest-ufcs-for-const.rs @@ -0,0 +1,4 @@ +fn main() { + 1_u32.MAX(); + //~^ ERROR no method named `MAX` found for type `u32` in the current scope +} diff --git a/src/test/ui/suggestions/dont-suggest-ufcs-for-const.stderr b/src/test/ui/suggestions/dont-suggest-ufcs-for-const.stderr new file mode 100644 index 00000000000..04e0511d788 --- /dev/null +++ b/src/test/ui/suggestions/dont-suggest-ufcs-for-const.stderr @@ -0,0 +1,15 @@ +error[E0599]: no method named `MAX` found for type `u32` in the current scope + --> $DIR/dont-suggest-ufcs-for-const.rs:2:11 + | +LL | 1_u32.MAX(); + | ------^^^-- + | | | + | | this is an associated function, not a method + | help: use associated function syntax instead: `u32::MAX()` + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter + = note: the candidate is defined in an impl for the type `u32` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`. diff --git a/src/test/ui/suggestions/suggest-assoc-fn-call-deref.fixed b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.fixed new file mode 100644 index 00000000000..8d96cf590c3 --- /dev/null +++ b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.fixed @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(unused)] + +struct Foo(T); + +impl Foo { + fn test() -> i32 { 1 } +} + +fn main() { + let x = Box::new(Foo(1i32)); + Foo::::test(); + //~^ ERROR no method named `test` found for struct `Box>` in the current scope +} diff --git a/src/test/ui/suggestions/suggest-assoc-fn-call-deref.rs b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.rs new file mode 100644 index 00000000000..186901f75a8 --- /dev/null +++ b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.rs @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(unused)] + +struct Foo(T); + +impl Foo { + fn test() -> i32 { 1 } +} + +fn main() { + let x = Box::new(Foo(1i32)); + x.test(); + //~^ ERROR no method named `test` found for struct `Box>` in the current scope +} diff --git a/src/test/ui/suggestions/suggest-assoc-fn-call-deref.stderr b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.stderr new file mode 100644 index 00000000000..00fb96f0326 --- /dev/null +++ b/src/test/ui/suggestions/suggest-assoc-fn-call-deref.stderr @@ -0,0 +1,19 @@ +error[E0599]: no method named `test` found for struct `Box>` in the current scope + --> $DIR/suggest-assoc-fn-call-deref.rs:13:7 + | +LL | x.test(); + | --^^^^-- + | | | + | | this is an associated function, not a method + | help: use associated function syntax instead: `Foo::::test()` + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in an impl for the type `Foo` + --> $DIR/suggest-assoc-fn-call-deref.rs:8:5 + | +LL | fn test() -> i32 { 1 } + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`.