From aa030d0a1f9592e7359d99f2c4563380aa03394d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 21 Oct 2023 01:18:41 +0000 Subject: [PATCH] When not finding assoc fn on type, look for builder fn When we have a resolution error when looking at a fully qualified path on a type, look for all associated functions on inherent impls that return `Self` and mention them to the user. Fix #69512. --- .../rustc_hir_typeck/src/method/suggest.rs | 72 +++++++++++++++++++ tests/ui/issues/issue-42880.stderr | 7 ++ tests/ui/resolve/fn-new-doesnt-exist.rs | 5 ++ tests/ui/resolve/fn-new-doesnt-exist.stderr | 14 ++++ tests/ui/resolve/issue-82865.stderr | 10 +++ 5 files changed, 108 insertions(+) create mode 100644 tests/ui/resolve/fn-new-doesnt-exist.rs create mode 100644 tests/ui/resolve/fn-new-doesnt-exist.stderr diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 6b0dc73d49c68..976f4de5377a8 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -409,6 +409,78 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.downgrade_to_delayed_bug(); } + if let (ty::Adt(adt_def, _), SelfSource::QPath(_)) = (rcvr_ty.kind(), source) { + let mut items = self + .tcx + .inherent_impls(adt_def.did()) + .iter() + .flat_map(|i| self.tcx.associated_items(i).in_definition_order()) + .filter(|item| { + matches!(item.kind, ty::AssocKind::Fn) && !item.fn_has_self_parameter + }) + .filter_map(|item| { + let ret_ty = self.tcx.fn_sig(item.def_id).skip_binder().output().skip_binder(); + let ty::Adt(def, args) = ret_ty.kind() else { + return None; + }; + if ![ + self.tcx.lang_items().option_type(), + self.tcx.get_diagnostic_item(sym::Result), + ] + .contains(&Some(def.did())) + { + return None; + } + let arg = args.get(0)?; + if self.can_eq(self.param_env, rcvr_ty, arg.expect_ty()) + || self.can_eq(self.param_env, ret_ty, rcvr_ty) + { + Some((item.def_id, ret_ty)) + } else { + None + } + }) + .collect::>(); + let post = if items.len() > 9 { + let items_len = items.len(); + items.truncate(8); + format!("and {} others", items_len - 8) + } else { + String::new() + }; + match &items[..] { + [] => {} + [(def_id, ret_ty)] => { + err.span_note( + self.tcx.def_span(def_id), + format!( + "if you're trying to build a new `{rcvr_ty}`, consider using `{}` \ + which returns `{ret_ty}`", + self.tcx.def_path_str(def_id), + ), + ); + } + _ => { + let span: MultiSpan = items + .iter() + .map(|(def_id, _)| self.tcx.def_span(def_id)) + .collect::>() + .into(); + err.span_note( + span, + format!( + "if you're trying to build a new `{rcvr_ty}` consider using one of the \ + following associated functions:\n{}{post}", + items + .iter() + .map(|(def_id, _ret_ty)| self.tcx.def_path_str(def_id)) + .collect::>() + .join("\n") + ), + ); + } + } + }; if tcx.ty_is_opaque_future(rcvr_ty) && item_name.name == sym::poll { err.help(format!( "method `poll` found on `Pin<&mut {ty_str}>`, \ diff --git a/tests/ui/issues/issue-42880.stderr b/tests/ui/issues/issue-42880.stderr index bec14429d38f1..fe13e15609fd5 100644 --- a/tests/ui/issues/issue-42880.stderr +++ b/tests/ui/issues/issue-42880.stderr @@ -3,6 +3,13 @@ error[E0599]: no associated item named `String` found for struct `String` in the | LL | let f = |&Value::String(_)| (); | ^^^^^^ associated item not found in `String` + | +note: if you're trying to build a new `String` consider using one of the following associated functions: + String::from_utf8 + String::from_utf16 + String::from_utf16le + String::from_utf16be + --> $SRC_DIR/alloc/src/string.rs:LL:COL error: aborting due to previous error diff --git a/tests/ui/resolve/fn-new-doesnt-exist.rs b/tests/ui/resolve/fn-new-doesnt-exist.rs new file mode 100644 index 0000000000000..4f6290808fc03 --- /dev/null +++ b/tests/ui/resolve/fn-new-doesnt-exist.rs @@ -0,0 +1,5 @@ +use std::net::TcpStream; + +fn main() { + let stream = TcpStream::new(); //~ ERROR no function or associated item named `new` found +} diff --git a/tests/ui/resolve/fn-new-doesnt-exist.stderr b/tests/ui/resolve/fn-new-doesnt-exist.stderr new file mode 100644 index 0000000000000..39adc0fde44ef --- /dev/null +++ b/tests/ui/resolve/fn-new-doesnt-exist.stderr @@ -0,0 +1,14 @@ +error[E0599]: no function or associated item named `new` found for struct `TcpStream` in the current scope + --> $DIR/fn-new-doesnt-exist.rs:4:28 + | +LL | let stream = TcpStream::new(); + | ^^^ function or associated item not found in `TcpStream` + | +note: if you're trying to build a new `TcpStream` consider using one of the following associated functions: + TcpStream::connect + TcpStream::connect_timeout + --> $SRC_DIR/std/src/net/tcp.rs:LL:COL + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/resolve/issue-82865.stderr b/tests/ui/resolve/issue-82865.stderr index 730fd6d602645..3bbac83281595 100644 --- a/tests/ui/resolve/issue-82865.stderr +++ b/tests/ui/resolve/issue-82865.stderr @@ -15,6 +15,16 @@ LL | Box::z LL | mac!(); | ------ in this macro invocation | +note: if you're trying to build a new `Box<_, _>` consider using one of the following associated functions: + Box::::try_new + Box::::try_new_uninit + Box::::try_new_zeroed + Box::::try_new_in + Box::::try_new_uninit_in + Box::::try_new_zeroed_in + Box::<[T]>::try_new_uninit_slice + Box::<[T]>::try_new_zeroed_slice + --> $SRC_DIR/alloc/src/boxed.rs:LL:COL = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors