From cbacb6d93108a7e5156c8b37d1a78f0dca351606 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 2 Nov 2024 03:53:59 +0000 Subject: [PATCH] Use extract_callable_info, generalize it to async closure --- .../rustc_hir_typeck/src/method/suggest.rs | 55 +----- .../src/error_reporting/traits/suggestions.rs | 177 ++++++++++-------- .../issue-18343.stderr | 2 +- .../issue-2392.stderr | 14 +- .../issue-32128.stderr | 2 +- .../issue-33784.stderr | 6 +- ...rg-where-it-should-have-been-called.stderr | 4 + 7 files changed, 121 insertions(+), 139 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index eaf40a193a6..4269e8b9f1e 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -31,6 +31,7 @@ use rustc_span::symbol::{Ident, kw, sym}; use rustc_span::{ DUMMY_SP, ErrorGuaranteed, ExpnKind, FileName, MacroKind, Span, Symbol, edit_distance, }; +use rustc_trait_selection::error_reporting::traits::DefIdOrName; use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedNote; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -45,50 +46,6 @@ use crate::errors::{self, CandidateTraitNote, NoAssociatedItem}; use crate::{Expectation, FnCtxt}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { - let tcx = self.tcx; - match ty.kind() { - // Not all of these (e.g., unsafe fns) implement `FnOnce`, - // so we look for these beforehand. - // FIXME(async_closures): These don't impl `FnOnce` by default. - ty::Closure(..) | ty::FnDef(..) | ty::FnPtr(..) => true, - // If it's not a simple function, look for things which implement `FnOnce`. - _ => { - let Some(fn_once) = tcx.lang_items().fn_once_trait() else { - return false; - }; - - // This conditional prevents us from asking to call errors and unresolved types. - // It might seem that we can use `predicate_must_hold_modulo_regions`, - // but since a Dummy binder is used to fill in the FnOnce trait's arguments, - // type resolution always gives a "maybe" here. - if self.autoderef(span, ty).silence_errors().any(|(ty, _)| { - info!("check deref {:?} error", ty); - matches!(ty.kind(), ty::Error(_) | ty::Infer(_)) - }) { - return false; - } - - self.autoderef(span, ty).silence_errors().any(|(ty, _)| { - info!("check deref {:?} impl FnOnce", ty); - self.probe(|_| { - let trait_ref = - ty::TraitRef::new(tcx, fn_once, [ty, self.next_ty_var(span)]); - let poly_trait_ref = ty::Binder::dummy(trait_ref); - let obligation = Obligation::misc( - tcx, - span, - self.body_id, - self.param_env, - poly_trait_ref, - ); - self.predicate_may_hold(&obligation) - }) - }) - } - } - } - fn is_slice_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { self.autoderef(span, ty) .silence_errors() @@ -2365,12 +2322,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let is_accessible = field.vis.is_accessible_from(scope, tcx); if is_accessible { - if self.is_fn_ty(field_ty, span) { + if let Some((what, _, _)) = self.extract_callable_info(field_ty) { + let what = match what { + DefIdOrName::DefId(def_id) => self.tcx.def_descr(def_id), + DefIdOrName::Name(what) => what, + }; let expr_span = expr.span.to(item_name.span); err.multipart_suggestion( format!( - "to call the function stored in `{item_name}`, \ - surround the field access with parentheses", + "to call the {what} stored in `{item_name}`, \ + surround the field access with parentheses", ), vec![ (expr_span.shrink_to_lo(), '('.to_string()), diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 2ce46ed5a79..d1d6ad04c42 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1075,93 +1075,110 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ) -> Option<(DefIdOrName, Ty<'tcx>, Vec>)> { // Autoderef is useful here because sometimes we box callables, etc. let Some((def_id_or_name, output, inputs)) = - (self.autoderef_steps)(found).into_iter().find_map(|(found, _)| { - match *found.kind() { - ty::FnPtr(sig_tys, _) => Some(( - DefIdOrName::Name("function pointer"), - sig_tys.output(), - sig_tys.inputs(), - )), - ty::FnDef(def_id, _) => { - let fn_sig = found.fn_sig(self.tcx); - Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) - } - ty::Closure(def_id, args) => { - let fn_sig = args.as_closure().sig(); - Some(( - DefIdOrName::DefId(def_id), - fn_sig.output(), - fn_sig.inputs().map_bound(|inputs| inputs[0].tuple_fields().as_slice()), - )) - } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { - self.tcx - .item_super_predicates(def_id) - .instantiate(self.tcx, args) - .iter() - .find_map(|pred| { - if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() - && self.tcx.is_lang_item(proj.projection_term.def_id, LangItem::FnOnceOutput) - // args tuple will always be args[1] - && let ty::Tuple(args) = proj.projection_term.args.type_at(1).kind() - { - Some(( - DefIdOrName::DefId(def_id), - pred.kind().rebind(proj.term.expect_type()), - pred.kind().rebind(args.as_slice()), - )) - } else { - None - } - }) - } - ty::Dynamic(data, _, ty::Dyn) => { - data.iter().find_map(|pred| { - if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() + (self.autoderef_steps)(found).into_iter().find_map(|(found, _)| match *found.kind() { + ty::FnPtr(sig_tys, _) => Some(( + DefIdOrName::Name("function pointer"), + sig_tys.output(), + sig_tys.inputs(), + )), + ty::FnDef(def_id, _) => { + let fn_sig = found.fn_sig(self.tcx); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) + } + ty::Closure(def_id, args) => { + let fn_sig = args.as_closure().sig(); + Some(( + DefIdOrName::DefId(def_id), + fn_sig.output(), + fn_sig.inputs().map_bound(|inputs| inputs[0].tuple_fields().as_slice()), + )) + } + ty::CoroutineClosure(def_id, args) => { + let sig_parts = args.as_coroutine_closure().coroutine_closure_sig(); + Some(( + DefIdOrName::DefId(def_id), + sig_parts.map_bound(|sig| { + sig.to_coroutine( + self.tcx, + args.as_coroutine_closure().parent_args(), + // Just use infer vars here, since we don't really care + // what these types are, just that we're returning a coroutine. + self.next_ty_var(DUMMY_SP), + self.tcx.coroutine_for_closure(def_id), + self.next_ty_var(DUMMY_SP), + ) + }), + sig_parts.map_bound(|sig| sig.tupled_inputs_ty.tuple_fields().as_slice()), + )) + } + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => self + .tcx + .item_super_predicates(def_id) + .instantiate(self.tcx, args) + .iter() + .find_map(|pred| { + if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() + && self + .tcx + .is_lang_item(proj.projection_term.def_id, LangItem::FnOnceOutput) + // args tuple will always be args[1] + && let ty::Tuple(args) = proj.projection_term.args.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.expect_type()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }), + ty::Dynamic(data, _, ty::Dyn) => data.iter().find_map(|pred| { + if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() && self.tcx.is_lang_item(proj.def_id, LangItem::FnOnceOutput) // for existential projection, args are shifted over by 1 && let ty::Tuple(args) = proj.args.type_at(0).kind() - { - Some(( - DefIdOrName::Name("trait object"), - pred.rebind(proj.term.expect_type()), - pred.rebind(args.as_slice()), - )) - } else { - None - } - }) + { + Some(( + DefIdOrName::Name("trait object"), + pred.rebind(proj.term.expect_type()), + pred.rebind(args.as_slice()), + )) + } else { + None } - ty::Param(param) => { - let generics = self.tcx.generics_of(body_id); - let name = if generics.count() > param.index as usize - && let def = generics.param_at(param.index as usize, self.tcx) - && matches!(def.kind, ty::GenericParamDefKind::Type { .. }) - && def.name == param.name + }), + ty::Param(param) => { + let generics = self.tcx.generics_of(body_id); + let name = if generics.count() > param.index as usize + && let def = generics.param_at(param.index as usize, self.tcx) + && matches!(def.kind, ty::GenericParamDefKind::Type { .. }) + && def.name == param.name + { + DefIdOrName::DefId(def.def_id) + } else { + DefIdOrName::Name("type parameter") + }; + param_env.caller_bounds().iter().find_map(|pred| { + if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() + && self + .tcx + .is_lang_item(proj.projection_term.def_id, LangItem::FnOnceOutput) + && proj.projection_term.self_ty() == found + // args tuple will always be args[1] + && let ty::Tuple(args) = proj.projection_term.args.type_at(1).kind() { - DefIdOrName::DefId(def.def_id) + Some(( + name, + pred.kind().rebind(proj.term.expect_type()), + pred.kind().rebind(args.as_slice()), + )) } else { - DefIdOrName::Name("type parameter") - }; - param_env.caller_bounds().iter().find_map(|pred| { - if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() - && self.tcx.is_lang_item(proj.projection_term.def_id, LangItem::FnOnceOutput) - && proj.projection_term.self_ty() == found - // args tuple will always be args[1] - && let ty::Tuple(args) = proj.projection_term.args.type_at(1).kind() - { - Some(( - name, - pred.kind().rebind(proj.term.expect_type()), - pred.kind().rebind(args.as_slice()), - )) - } else { - None - } - }) - } - _ => None, + None + } + }) } + _ => None, }) else { return None; diff --git a/tests/ui/confuse-field-and-method/issue-18343.stderr b/tests/ui/confuse-field-and-method/issue-18343.stderr index a51fd4f02aa..e50c971d837 100644 --- a/tests/ui/confuse-field-and-method/issue-18343.stderr +++ b/tests/ui/confuse-field-and-method/issue-18343.stderr @@ -7,7 +7,7 @@ LL | struct Obj where F: FnMut() -> u32 { LL | o.closure(); | ^^^^^^^ field, not a method | -help: to call the function stored in `closure`, surround the field access with parentheses +help: to call the closure stored in `closure`, surround the field access with parentheses | LL | (o.closure)(); | + + diff --git a/tests/ui/confuse-field-and-method/issue-2392.stderr b/tests/ui/confuse-field-and-method/issue-2392.stderr index 440fbb27c00..77930de44a7 100644 --- a/tests/ui/confuse-field-and-method/issue-2392.stderr +++ b/tests/ui/confuse-field-and-method/issue-2392.stderr @@ -7,7 +7,7 @@ LL | struct Obj where F: FnOnce() -> u32 { LL | o_closure.closure(); | ^^^^^^^ field, not a method | -help: to call the function stored in `closure`, surround the field access with parentheses +help: to call the closure stored in `closure`, surround the field access with parentheses | LL | (o_closure.closure)(); | + + @@ -46,7 +46,7 @@ LL | struct BoxedObj { LL | boxed_fn.boxed_closure(); | ^^^^^^^^^^^^^ field, not a method | -help: to call the function stored in `boxed_closure`, surround the field access with parentheses +help: to call the trait object stored in `boxed_closure`, surround the field access with parentheses | LL | (boxed_fn.boxed_closure)(); | + + @@ -60,7 +60,7 @@ LL | struct BoxedObj { LL | boxed_closure.boxed_closure(); | ^^^^^^^^^^^^^ field, not a method | -help: to call the function stored in `boxed_closure`, surround the field access with parentheses +help: to call the trait object stored in `boxed_closure`, surround the field access with parentheses | LL | (boxed_closure.boxed_closure)(); | + + @@ -99,7 +99,7 @@ LL | struct Obj where F: FnOnce() -> u32 { LL | check_expression().closure(); | ^^^^^^^ field, not a method | -help: to call the function stored in `closure`, surround the field access with parentheses +help: to call the trait object stored in `closure`, surround the field access with parentheses | LL | (check_expression().closure)(); | + + @@ -113,7 +113,7 @@ LL | struct FuncContainer { LL | (*self.container).f1(1); | ^^ field, not a method | -help: to call the function stored in `f1`, surround the field access with parentheses +help: to call the function pointer stored in `f1`, surround the field access with parentheses | LL | ((*self.container).f1)(1); | + + @@ -127,7 +127,7 @@ LL | struct FuncContainer { LL | (*self.container).f2(1); | ^^ field, not a method | -help: to call the function stored in `f2`, surround the field access with parentheses +help: to call the function pointer stored in `f2`, surround the field access with parentheses | LL | ((*self.container).f2)(1); | + + @@ -141,7 +141,7 @@ LL | struct FuncContainer { LL | (*self.container).f3(1); | ^^ field, not a method | -help: to call the function stored in `f3`, surround the field access with parentheses +help: to call the function pointer stored in `f3`, surround the field access with parentheses | LL | ((*self.container).f3)(1); | + + diff --git a/tests/ui/confuse-field-and-method/issue-32128.stderr b/tests/ui/confuse-field-and-method/issue-32128.stderr index 3d860d8c85a..aaec15a41dc 100644 --- a/tests/ui/confuse-field-and-method/issue-32128.stderr +++ b/tests/ui/confuse-field-and-method/issue-32128.stderr @@ -7,7 +7,7 @@ LL | struct Example { LL | demo.example(1); | ^^^^^^^ field, not a method | -help: to call the function stored in `example`, surround the field access with parentheses +help: to call the trait object stored in `example`, surround the field access with parentheses | LL | (demo.example)(1); | + + diff --git a/tests/ui/confuse-field-and-method/issue-33784.stderr b/tests/ui/confuse-field-and-method/issue-33784.stderr index 8acd1f8ff1e..59a6f4fccd8 100644 --- a/tests/ui/confuse-field-and-method/issue-33784.stderr +++ b/tests/ui/confuse-field-and-method/issue-33784.stderr @@ -4,7 +4,7 @@ error[E0599]: no method named `closure` found for reference `&Obj<{closure@$DIR/ LL | p.closure(); | ^^^^^^^ field, not a method | -help: to call the function stored in `closure`, surround the field access with parentheses +help: to call the closure stored in `closure`, surround the field access with parentheses | LL | (p.closure)(); | + + @@ -19,7 +19,7 @@ error[E0599]: no method named `fn_ptr` found for reference `&&Obj<{closure@$DIR/ LL | q.fn_ptr(); | ^^^^^^ field, not a method | -help: to call the function stored in `fn_ptr`, surround the field access with parentheses +help: to call the function pointer stored in `fn_ptr`, surround the field access with parentheses | LL | (q.fn_ptr)(); | + + @@ -30,7 +30,7 @@ error[E0599]: no method named `c_fn_ptr` found for reference `&D` in the current LL | s.c_fn_ptr(); | ^^^^^^^^ field, not a method | -help: to call the function stored in `c_fn_ptr`, surround the field access with parentheses +help: to call the function pointer stored in `c_fn_ptr`, surround the field access with parentheses | LL | (s.c_fn_ptr)(); | + + diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr index 05e087fd9f9..a040e71cf3b 100644 --- a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr @@ -31,6 +31,10 @@ note: required by a bound in `bar` | LL | fn bar(f: impl Future) {} | ^^^^^^^^^^^^^^^^^ required by this bound in `bar` +help: use parentheses to call this closure + | +LL | bar(async_closure()); + | ++ error: aborting due to 2 previous errors