From b2df88bae1318689c86c69655268ca2d2afc0f81 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 27 Dec 2022 01:55:54 +0000 Subject: [PATCH] Consolidate two almost duplicated fn info extraction routines --- compiler/rustc_hir_typeck/src/callee.rs | 3 +- .../src/fn_ctxt/suggestions.rs | 103 +------- .../rustc_hir_typeck/src/method/suggest.rs | 6 +- .../src/traits/error_reporting/mod.rs | 1 + .../src/traits/error_reporting/suggestions.rs | 221 +++++++++++------- .../call-on-unimplemented-with-autoderef.rs | 13 ++ ...all-on-unimplemented-with-autoderef.stderr | 21 ++ 7 files changed, 183 insertions(+), 185 deletions(-) create mode 100644 tests/ui/suggestions/call-on-unimplemented-with-autoderef.rs create mode 100644 tests/ui/suggestions/call-on-unimplemented-with-autoderef.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 8d417290407..f1a4f94cd01 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -659,8 +659,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) { - if let Some((maybe_def, output_ty, _)) = - self.extract_callable_info(callee_expr, callee_ty) + if let Some((maybe_def, output_ty, _)) = self.extract_callable_info(callee_ty) && !self.type_is_sized_modulo_regions(self.param_env, output_ty, callee_expr.span) { let descr = match maybe_def { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 005bd164065..76f33be7a38 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -11,7 +11,6 @@ use rustc_hir::{ Expr, ExprKind, GenericBound, Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicate, }; use rustc_hir_analysis::astconv::AstConv; -use rustc_infer::infer; use rustc_infer::traits::{self, StatementAsExpression}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{ @@ -23,9 +22,9 @@ use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Ident}; use rustc_span::{Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt; use rustc_trait_selection::traits::error_reporting::DefIdOrName; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; -use rustc_trait_selection::traits::NormalizeExt; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn body_fn_sig(&self) -> Option> { @@ -94,7 +93,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { found: Ty<'tcx>, can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, ) -> bool { - let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found) + let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(found) else { return false; }; if can_satisfy(output) { let (sugg_call, mut applicability) = match inputs.len() { @@ -163,99 +162,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// because the callable type must also be well-formed to be called. pub(in super::super) fn extract_callable_info( &self, - expr: &Expr<'_>, - found: Ty<'tcx>, + ty: Ty<'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(expr.span, found).silence_errors().find_map(|(found, _)| { - match *found.kind() { - ty::FnPtr(fn_sig) => - Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.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, substs) => { - let fn_sig = substs.as_closure().sig(); - Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) - } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() - && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() - { - Some(( - DefIdOrName::DefId(def_id), - pred.kind().rebind(proj.term.ty().unwrap()), - 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() - && Some(proj.def_id) == self.tcx.lang_items().fn_once_output() - // for existential projection, substs are shifted over by 1 - && let ty::Tuple(args) = proj.substs.type_at(0).kind() - { - Some(( - DefIdOrName::Name("trait object"), - pred.rebind(proj.term.ty().unwrap()), - pred.rebind(args.as_slice()), - )) - } else { - None - } - }) - } - ty::Param(param) => { - let def_id = self.tcx.generics_of(self.body_id.owner).type_param(¶m, self.tcx).def_id; - self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() - && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() - && proj.projection_ty.self_ty() == found - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() - { - Some(( - DefIdOrName::DefId(def_id), - pred.kind().rebind(proj.term.ty().unwrap()), - pred.kind().rebind(args.as_slice()), - )) - } else { - None - } - }) - } - _ => None, - } - }) else { return None; }; - - let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); - let inputs = inputs - .skip_binder() - .iter() - .map(|ty| { - self.replace_bound_vars_with_fresh_vars( - expr.span, - infer::FnCall, - inputs.rebind(*ty), - ) - }) - .collect(); - - // We don't want to register any extra obligations, which should be - // implied by wf, but also because that would possibly result in - // erroneous errors later on. - let infer::InferOk { value: output, obligations: _ } = - self.at(&self.misc(expr.span), self.param_env).normalize(output); - - if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } + self.err_ctxt().extract_callable_info(self.body_id, self.param_env, ty) } pub fn suggest_two_fn_call( @@ -267,9 +176,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rhs_ty: Ty<'tcx>, can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool, ) -> bool { - let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty) + let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_ty) else { return false; }; - let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty) + let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_ty) else { return false; }; if can_satisfy(lhs_output_ty, rhs_output_ty) { diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 8166eb82990..7e258103372 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2662,8 +2662,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { found: Ty<'tcx>, expected: Ty<'tcx>, ) -> bool { - let Some((_def_id_or_name, output, _inputs)) = self.extract_callable_info(expr, found) - else { return false; }; + let Some((_def_id_or_name, output, _inputs)) = + self.extract_callable_info(found) else { + return false; + }; if !self.can_coerce(output, expected) { return false; diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 0c7ffb056cc..940fc29841b 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -2912,6 +2912,7 @@ impl<'tcx> ty::TypeVisitor<'tcx> for HasNumericInferVisitor { } } +#[derive(Copy, Clone)] pub enum DefIdOrName { DefId(DefId), Name(&'static str), diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 1b98ead29f8..48c32a446c5 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -212,6 +212,13 @@ pub trait TypeErrCtxtExt<'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, ) -> bool; + fn extract_callable_info( + &self, + hir_id: HirId, + param_env: ty::ParamEnv<'tcx>, + found: Ty<'tcx>, + ) -> Option<(DefIdOrName, Ty<'tcx>, Vec>)>; + fn suggest_add_reference_to_arg( &self, obligation: &PredicateObligation<'tcx>, @@ -885,92 +892,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { return false; } - // This is duplicated from `extract_callable_info` in typeck, which - // relies on autoderef, so we can't use it here. - let found = trait_pred.self_ty().skip_binder().peel_refs(); - let Some((def_id_or_name, output, inputs)) = (match *found.kind() - { - ty::FnPtr(fn_sig) => { - Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.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, substs) => { - let fn_sig = substs.as_closure().sig(); - Some(( - DefIdOrName::DefId(def_id), - fn_sig.output(), - fn_sig.inputs().map_bound(|inputs| &inputs[1..]), - )) - } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() - && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() - { - Some(( - DefIdOrName::DefId(def_id), - pred.kind().rebind(proj.term.ty().unwrap()), - 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() - && Some(proj.def_id) == self.tcx.lang_items().fn_once_output() - // for existential projection, substs are shifted over by 1 - && let ty::Tuple(args) = proj.substs.type_at(0).kind() - { - Some(( - DefIdOrName::Name("trait object"), - pred.rebind(proj.term.ty().unwrap()), - pred.rebind(args.as_slice()), - )) - } else { - None - } - }) - } - ty::Param(_) => { - obligation.param_env.caller_bounds().iter().find_map(|pred| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() - && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() - && proj.projection_ty.self_ty() == found - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() - { - Some(( - DefIdOrName::Name("type parameter"), - pred.kind().rebind(proj.term.ty().unwrap()), - pred.kind().rebind(args.as_slice()), - )) - } else { - None - } - }) - } - _ => None, - }) else { return false; }; - let output = self.replace_bound_vars_with_fresh_vars( - obligation.cause.span, + let self_ty = self.replace_bound_vars_with_fresh_vars( + DUMMY_SP, LateBoundRegionConversionTime::FnCall, - output, + trait_pred.self_ty(), ); - let inputs = inputs.skip_binder().iter().map(|ty| { - self.replace_bound_vars_with_fresh_vars( - obligation.cause.span, - LateBoundRegionConversionTime::FnCall, - inputs.rebind(*ty), - ) - }); + + let Some((def_id_or_name, output, inputs)) = self.extract_callable_info( + obligation.cause.body_id, + obligation.param_env, + self_ty, + ) else { return false; }; // Remapping bound vars here let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, output)); @@ -998,6 +930,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { }; let args = inputs + .into_iter() .map(|ty| { if ty.is_suggestable(self.tcx, false) { format!("/* {ty} */") @@ -1161,6 +1094,126 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { false } + /// Extracts information about a callable type for diagnostics. This is a + /// heuristic -- it doesn't necessarily mean that a type is always callable, + /// because the callable type must also be well-formed to be called. + fn extract_callable_info( + &self, + hir_id: HirId, + param_env: ty::ParamEnv<'tcx>, + found: Ty<'tcx>, + ) -> Option<(DefIdOrName, Ty<'tcx>, Vec>)> { + // Autoderef is useful here because sometimes we box callables, etc. + let Some((def_id_or_name, output, inputs)) = Autoderef::new( + self, + param_env, + hir_id, + DUMMY_SP, + found, + ).silence_errors().find_map(|(found, _)| { + match *found.kind() { + ty::FnPtr(fn_sig) => + Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.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, substs) => { + let fn_sig = substs.as_closure().sig(); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) + } + ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { + if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() + && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.ty().unwrap()), + 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() + && Some(proj.def_id) == self.tcx.lang_items().fn_once_output() + // for existential projection, substs are shifted over by 1 + && let ty::Tuple(args) = proj.substs.type_at(0).kind() + { + Some(( + DefIdOrName::Name("trait object"), + pred.rebind(proj.term.ty().unwrap()), + pred.rebind(args.as_slice()), + )) + } else { + None + } + }) + } + ty::Param(param) => { + let generics = self.tcx.generics_of(hir_id.owner.to_def_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::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() + && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() + && proj.projection_ty.self_ty() == found + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + name, + pred.kind().rebind(proj.term.ty().unwrap()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }) + } + _ => None, + } + }) else { return None; }; + + let output = self.replace_bound_vars_with_fresh_vars( + DUMMY_SP, + LateBoundRegionConversionTime::FnCall, + output, + ); + let inputs = inputs + .skip_binder() + .iter() + .map(|ty| { + self.replace_bound_vars_with_fresh_vars( + DUMMY_SP, + LateBoundRegionConversionTime::FnCall, + inputs.rebind(*ty), + ) + }) + .collect(); + + // We don't want to register any extra obligations, which should be + // implied by wf, but also because that would possibly result in + // erroneous errors later on. + let InferOk { value: output, obligations: _ } = + self.at(&ObligationCause::dummy(), param_env).normalize(output); + + if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } + } + fn suggest_add_reference_to_arg( &self, obligation: &PredicateObligation<'tcx>, diff --git a/tests/ui/suggestions/call-on-unimplemented-with-autoderef.rs b/tests/ui/suggestions/call-on-unimplemented-with-autoderef.rs new file mode 100644 index 00000000000..9021dd752e7 --- /dev/null +++ b/tests/ui/suggestions/call-on-unimplemented-with-autoderef.rs @@ -0,0 +1,13 @@ +trait Foo {} + +impl Foo for i32 {} + +fn needs_foo(_: impl Foo) {} + +fn test(x: &Box i32>) { + needs_foo(x); + //~^ ERROR the trait bound + //~| HELP use parentheses to call this trait object +} + +fn main() {} diff --git a/tests/ui/suggestions/call-on-unimplemented-with-autoderef.stderr b/tests/ui/suggestions/call-on-unimplemented-with-autoderef.stderr new file mode 100644 index 00000000000..90f44cce06e --- /dev/null +++ b/tests/ui/suggestions/call-on-unimplemented-with-autoderef.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `&Box i32>: Foo` is not satisfied + --> $DIR/call-on-unimplemented-with-autoderef.rs:8:15 + | +LL | needs_foo(x); + | --------- ^ the trait `Foo` is not implemented for `&Box i32>` + | | + | required by a bound introduced by this call + | +note: required by a bound in `needs_foo` + --> $DIR/call-on-unimplemented-with-autoderef.rs:5:22 + | +LL | fn needs_foo(_: impl Foo) {} + | ^^^ required by this bound in `needs_foo` +help: use parentheses to call this trait object + | +LL | needs_foo(x()); + | ++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`.