diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 57771e0969ba..45872a0b07ae 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -61,55 +61,64 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pointing_at_return_type } - /// When encountering an fn-like ctor that needs to unify with a value, check whether calling - /// the ctor would successfully solve the type mismatch and if so, suggest it: + /// When encountering an fn-like type, try accessing the output of the type + /// // and suggesting calling it if it satisfies a predicate (i.e. if the + /// output has a method or a field): /// ```compile_fail,E0308 /// fn foo(x: usize) -> usize { x } /// let x: usize = foo; // suggest calling the `foo` function: `foo(42)` /// ``` - fn suggest_fn_call( + pub(crate) fn suggest_fn_call( &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, - expected: Ty<'tcx>, found: Ty<'tcx>, + can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, ) -> bool { - let (def_id, output, inputs) = match *found.kind() { - ty::FnDef(def_id, _) => { - let fn_sig = found.fn_sig(self.tcx); - (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len()) - } - ty::Closure(def_id, substs) => { - let fn_sig = substs.as_closure().sig(); - (def_id, fn_sig.output(), fn_sig.inputs().skip_binder().len() - 1) - } - ty::Opaque(def_id, substs) => { - let sig = self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { - if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() - && Some(proj.projection_ty.item_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(( - pred.kind().rebind(proj.term.ty().unwrap()), - args.len(), - )) + // Autoderef is useful here because sometimes we box callables, etc. + let Some((def_id, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| { + match *found.kind() { + ty::FnPtr(fn_sig) => Some((None, fn_sig.output(), fn_sig.inputs().skip_binder().len())), + ty::FnDef(def_id, _) => { + let fn_sig = found.fn_sig(self.tcx); + Some((Some(def_id), fn_sig.output(), fn_sig.inputs().skip_binder().len())) + } + ty::Closure(def_id, substs) => { + let fn_sig = substs.as_closure().sig(); + Some((Some(def_id), fn_sig.output(), fn_sig.inputs().skip_binder().len() - 1)) + } + ty::Opaque(def_id, substs) => { + let sig = self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { + if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() + && Some(proj.projection_ty.item_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(( + pred.kind().rebind(proj.term.ty().unwrap()), + args.len(), + )) + } else { + None + } + }); + if let Some((output, inputs)) = sig { + Some((Some(def_id), output, inputs)) } else { None } - }); - if let Some((output, inputs)) = sig { - (def_id, output, inputs) - } else { - return false; } + _ => None, } - _ => return false, - }; + }) else { return false; }; let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); - let output = self.normalize_associated_types_in(expr.span, output); - if !output.is_ty_var() && self.can_coerce(output, expected) { + // 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.normalize_associated_types_in_as_infer_ok(expr.span, output); + if !output.is_ty_var() && can_satisfy(output) { let (sugg_call, mut applicability) = match inputs { 0 => ("".to_string(), Applicability::MachineApplicable), 1..=4 => ( @@ -119,11 +128,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => ("...".to_string(), Applicability::HasPlaceholders), }; - let msg = match self.tcx.def_kind(def_id) { - DefKind::Fn => "call this function", - DefKind::Closure | DefKind::OpaqueTy => "call this closure", - DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct", - DefKind::Ctor(CtorOf::Variant, _) => "instantiate this tuple variant", + let msg = match def_id.map(|def_id| self.tcx.def_kind(def_id)) { + Some(DefKind::Fn) => "call this function", + Some(DefKind::Closure | DefKind::OpaqueTy) => "call this closure", + Some(DefKind::Ctor(CtorOf::Struct, _)) => "instantiate this tuple struct", + Some(DefKind::Ctor(CtorOf::Variant, _)) => "instantiate this tuple variant", _ => "call this function", }; @@ -178,12 +187,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { err.span_suggestion(sp, &msg, suggestion, applicability); } - } else if let (ty::FnDef(def_id, ..), true) = - (&found.kind(), self.suggest_fn_call(err, expr, expected, found)) + } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) + && let ty::FnDef(def_id, ..) = &found.kind() + && let Some(sp) = self.tcx.hir().span_if_local(*def_id) { - if let Some(sp) = self.tcx.hir().span_if_local(*def_id) { - err.span_label(sp, format!("{found} defined here")); - } + err.span_label(sp, format!("{found} defined here")); } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) { let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); if !methods.is_empty() { diff --git a/src/test/ui/suggestions/call-boxed.rs b/src/test/ui/suggestions/call-boxed.rs new file mode 100644 index 000000000000..d19e4596a0cc --- /dev/null +++ b/src/test/ui/suggestions/call-boxed.rs @@ -0,0 +1,7 @@ +fn main() { + let mut x = 1i32; + let y = Box::new(|| 1); + x = y; + //~^ ERROR mismatched types + //~| HELP use parentheses to call this closure +} diff --git a/src/test/ui/suggestions/call-boxed.stderr b/src/test/ui/suggestions/call-boxed.stderr new file mode 100644 index 000000000000..9b619ac9a3f5 --- /dev/null +++ b/src/test/ui/suggestions/call-boxed.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/call-boxed.rs:4:9 + | +LL | let mut x = 1i32; + | ---- expected due to this value +LL | let y = Box::new(|| 1); + | -- the found closure +LL | x = y; + | ^ expected `i32`, found struct `Box` + | + = note: expected type `i32` + found struct `Box<[closure@$DIR/call-boxed.rs:3:22: 3:24]>` +help: use parentheses to call this closure + | +LL | x = y(); + | ++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.