diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 48bb45de53eb4..3e9e734149e5a 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -25,6 +25,7 @@ use tracing::{debug, instrument}; use super::method::MethodCallee; use super::method::probe::ProbeScope; use super::{Expectation, FnCtxt, TupleArgumentsFlag}; +use crate::expr_use_visitor::TypeInformationCtxt; use crate::{errors, fluent_generated}; /// Checks that it is legal to call methods of the trait corresponding @@ -122,7 +123,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - let guar = self.report_invalid_callee(call_expr, callee_expr, expr_ty, arg_exprs); + let guar = self.report_invalid_callee( + call_expr, + callee_expr, + expr_ty, + arg_exprs, + expected, + ); Ty::new_error(self.tcx, guar) } @@ -677,6 +684,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { callee_expr: &'tcx hir::Expr<'tcx>, callee_ty: Ty<'tcx>, arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, ) -> ErrorGuaranteed { // Callee probe fails when APIT references errors, so suppress those // errors here. @@ -806,6 +814,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(def_span, "the callable type is defined here"); } } else { + // Suggest adding : Fn(...) [-> RetType] + if callee_ty.has_non_region_param() { + let arg_types: Vec> = arg_exprs + .iter() + .map(|arg| self.typeck_results.borrow().expr_ty(arg)) + .collect(); + let args_tuple = Ty::new_tup(self.tcx(), &arg_types); + + let fn_def_id = self.tcx().require_lang_item(LangItem::Fn, callee_expr.span); + let trait_ref = + ty::TraitRef::new(self.tcx(), fn_def_id, [callee_ty, args_tuple]); + + let trait_pred = + ty::TraitPredicate { trait_ref, polarity: ty::PredicatePolarity::Positive }; + + let associated_ty = expected + .to_option(self) + // We do not want to suggest e.g. `-> _` + .filter(|ty| !ty.has_infer()) + .map(|ty| ("Output", ty)); + self.err_ctxt().suggest_restricting_param_bound( + &mut err, + ty::Binder::dummy(trait_pred), + associated_ty, + self.body_id, + ); + } err.span_label(call_expr.span, "call expression requires function"); } } 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 bf7d4257b6269..9923a8179b2f0 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -388,12 +388,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); if let Some((name, term)) = associated_ty { - // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err. - // That should be extracted into a helper function. - if let Some(stripped) = constraint.strip_suffix('>') { - constraint = format!("{stripped}, {name} = {term}>"); + if self.tcx.is_fn_trait(trait_pred.skip_binder().trait_ref.def_id) { + constraint.push_str(&format!(" -> {term}")); } else { - constraint.push_str(&format!("<{name} = {term}>")); + // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err. + // That should be extracted into a helper function. + if let Some(stripped) = constraint.strip_suffix('>') { + constraint = format!("{stripped}, {name} = {term}>"); + } else { + constraint.push_str(&format!("<{name} = {term}>")); + } } } diff --git a/tests/ui/issues/issue-21701.stderr b/tests/ui/issues/issue-21701.stderr index 9f1fe7dde735a..ee491da2e1ced 100644 --- a/tests/ui/issues/issue-21701.stderr +++ b/tests/ui/issues/issue-21701.stderr @@ -7,6 +7,11 @@ LL | let y = t(); | ^-- | | | call expression requires function + | +help: consider restricting type parameter `U` with trait `Fn` + | +LL | fn foo(t: U) { + | ++++++ error[E0618]: expected function, found struct `Bar` --> $DIR/issue-21701.rs:9:13 diff --git a/tests/ui/suggestions/suggest-call-on-generic-param.rs b/tests/ui/suggestions/suggest-call-on-generic-param.rs new file mode 100644 index 0000000000000..5f4939c604547 --- /dev/null +++ b/tests/ui/suggestions/suggest-call-on-generic-param.rs @@ -0,0 +1,26 @@ +fn return_type(t: T) { + let x: u32 = t(1); + //~^ ERROR: expected function, found `T` [E0618] +} + +fn unknown_return_type(t: T) { + let x = t(); + //~^ ERROR: expected function, found `T` [E0618] +} + +fn nested_return_type(t: Vec) { + t(); + //~^ ERROR: expected function, found `Vec` [E0618] +} + +fn no_return_type(t: T) { + t(1, 2, true); + //~^ ERROR: expected function, found `T` [E0618] +} + +fn existing_bound(t: T) { + t(false); + //~^ ERROR: expected function, found `T` [E0618] +} + +fn main() {} diff --git a/tests/ui/suggestions/suggest-call-on-generic-param.stderr b/tests/ui/suggestions/suggest-call-on-generic-param.stderr new file mode 100644 index 0000000000000..63578845d5ea7 --- /dev/null +++ b/tests/ui/suggestions/suggest-call-on-generic-param.stderr @@ -0,0 +1,78 @@ +error[E0618]: expected function, found `T` + --> $DIR/suggest-call-on-generic-param.rs:2:18 + | +LL | fn return_type(t: T) { + | - `t` has type `T` +LL | let x: u32 = t(1); + | ^--- + | | + | call expression requires function + | +help: consider restricting type parameter `T` with trait `Fn` + | +LL | fn return_type u32>(t: T) { + | ++++++++++++++++ + +error[E0618]: expected function, found `T` + --> $DIR/suggest-call-on-generic-param.rs:7:13 + | +LL | fn unknown_return_type(t: T) { + | - `t` has type `T` +LL | let x = t(); + | ^-- + | | + | call expression requires function + | +help: consider restricting type parameter `T` with trait `Fn` + | +LL | fn unknown_return_type(t: T) { + | ++++++ + +error[E0618]: expected function, found `Vec` + --> $DIR/suggest-call-on-generic-param.rs:12:5 + | +LL | fn nested_return_type(t: Vec) { + | - `t` has type `Vec` +LL | t(); + | ^-- + | | + | call expression requires function + | +help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement + | +LL | fn nested_return_type(t: Vec) where Vec: Fn() { + | ++++++++++++++++++ + +error[E0618]: expected function, found `T` + --> $DIR/suggest-call-on-generic-param.rs:17:5 + | +LL | fn no_return_type(t: T) { + | - `t` has type `T` +LL | t(1, 2, true); + | ^------------ + | | + | call expression requires function + | +help: consider restricting type parameter `T` with trait `Fn` + | +LL | fn no_return_type(t: T) { + | ++++++++++++++++++++ + +error[E0618]: expected function, found `T` + --> $DIR/suggest-call-on-generic-param.rs:22:5 + | +LL | fn existing_bound(t: T) { + | - `t` has type `T` +LL | t(false); + | ^------- + | | + | call expression requires function + | +help: consider further restricting type parameter `T` with trait `Fn` + | +LL | fn existing_bound(t: T) { + | ++++++++++ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0618`.