diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 2bbcbbdab9a64..4cbeaa6278049 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -591,7 +591,12 @@ impl<'a, 'tcx> CastCheck<'tcx> { err.span_label(self.span, "invalid cast"); } - fcx.suggest_no_capture_closure(&mut err, self.cast_ty, self.expr_ty); + fcx.suggest_closure_to_fn_ptr_coercion( + &mut err, + self.expr, + self.cast_ty, + self.expr_ty, + ); self.try_suggest_collection_to_bool(fcx, &mut err); err.emit(); diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 66c214a1be98a..671bf52205689 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -44,7 +44,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { || self.suggest_compatible_variants(err, expr, expected, expr_ty) || self.suggest_non_zero_new_unwrap(err, expr, expected, expr_ty) || self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty) - || self.suggest_no_capture_closure(err, expected, expr_ty) + || self.suggest_closure_to_fn_ptr_coercion(err, expr, expected, expr_ty) || self.suggest_boxing_when_appropriate( err, expr.peel_blocks().span, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 3aec9d4dae5ca..b28eb8ad940d9 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -3,6 +3,7 @@ use core::cmp::min; use core::iter; use hir::def_id::LocalDefId; +use itertools::Itertools; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::packed::Pu128; use rustc_errors::{Applicability, Diag, MultiSpan, listify, msg}; @@ -670,10 +671,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// When encountering a closure that captures variables, where a FnPtr is expected, - /// suggest a non-capturing closure - pub(in super::super) fn suggest_no_capture_closure( + /// explain why coercion fails and suggest changing the return type to `impl Fn(...)`. + pub(in super::super) fn suggest_closure_to_fn_ptr_coercion( &self, err: &mut Diag<'_>, + expr: &hir::Expr<'_>, expected: Ty<'tcx>, found: Ty<'tcx>, ) -> bool { @@ -701,11 +703,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { multi_span, "closures can only be coerced to `fn` types if they do not capture any variables", ); + + // If the expected fn pointer type comes from the enclosing function's return type, + // suggest changing it to `impl Fn(...)` so that a capturing closure can be returned. + self.suggest_impl_fn_for_fn_ptr_ret(err, expr); + return true; } false } + /// When a capturing closure is returned where a `fn(...)` pointer return type is expected, + /// suggest changing the return type to `impl Fn(...)`. + fn suggest_impl_fn_for_fn_ptr_ret(&self, err: &mut Diag<'_>, expr: &hir::Expr<'_>) { + let Some((_, fn_decl)) = self.get_fn_decl(expr.hir_id) else { return }; + let hir::FnRetTy::Return(ret_ty) = fn_decl.output else { return }; + let hir::TyKind::FnPtr(fn_ptr_ty) = ret_ty.kind else { return }; + + let hir::FnDecl { inputs, output, .. } = fn_ptr_ty.decl; + + let inputs_str = + inputs.iter().map(|ty| rustc_hir_pretty::ty_to_string(&self.tcx, ty)).join(", "); + + let output_str = match output { + hir::FnRetTy::DefaultReturn(_) => String::new(), + hir::FnRetTy::Return(ty) => { + format!(" -> {}", rustc_hir_pretty::ty_to_string(&self.tcx, ty)) + } + }; + + let suggestion = format!("impl Fn({inputs_str}){output_str}"); + err.span_suggestion( + ret_ty.span, + "change the return type to return a type-erased closure instead", + suggestion, + Applicability::MaybeIncorrect, + ); + } + /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. #[instrument(skip(self, err))] pub(in super::super) fn suggest_calling_boxed_future_when_appropriate( diff --git a/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.rs b/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.rs new file mode 100644 index 0000000000000..c8273017f38a3 --- /dev/null +++ b/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.rs @@ -0,0 +1,53 @@ +//! Regression test for . +//! When a capturing closure is returned where a `fn(...)` pointer return type is +//! expected, suggest changing the return type to `impl Fn(...)`. + +#![crate_type = "lib"] + +fn no_args(a: i32) -> fn() -> i32 { + || a + //~^ ERROR mismatched types +} + +fn no_args_no_return(a: i32) -> fn() { + || println!("{}", a) + //~^ ERROR mismatched types +} + +fn one_arg(a: i32) -> fn(i32) -> i32 { + |x| x + a + //~^ ERROR mismatched types +} + +fn one_arg_unsafe(a: i32) -> unsafe fn(i32) -> i32 { + |x| x + a + //~^ ERROR mismatched types +} + +fn one_arg_extern(a: i32) -> extern "C" fn(i32) -> i32 { + |x| x + a + //~^ ERROR mismatched types +} + +fn one_arg_no_return(a: i32) -> fn(i32) { + |x| println!("{}", x + a) + //~^ ERROR mismatched types +} + +fn one_arg_return_unit(a: i32) -> fn(i32) -> () { + |x| println!("{}", x + a) + //~^ ERROR mismatched types +} + +fn one_arg_ref(a: i32) -> fn(&i32) -> &i32 { + |x| { + //~^ ERROR mismatched types + println!("{}", a); + x + } +} + +fn multi_args(a: i32, b: i32) -> fn(i32, i32) -> i32 { + |x, y| x + y + a + b + //~^ ERROR mismatched types +} diff --git a/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.stderr b/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.stderr new file mode 100644 index 0000000000000..1baaf6b32bcd8 --- /dev/null +++ b/tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.stderr @@ -0,0 +1,198 @@ +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:8:5 + | +LL | fn no_args(a: i32) -> fn() -> i32 { + | ----------- expected `fn() -> i32` because of return type +LL | || a + | ^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn() -> i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:8:5: 8:7}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:8:8 + | +LL | || a + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn no_args(a: i32) -> fn() -> i32 { +LL + fn no_args(a: i32) -> impl Fn() -> i32 { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:13:5 + | +LL | fn no_args_no_return(a: i32) -> fn() { + | ---- expected `fn()` because of return type +LL | || println!("{}", a) + | ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn()` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:13:5: 13:7}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:13:23 + | +LL | || println!("{}", a) + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn no_args_no_return(a: i32) -> fn() { +LL + fn no_args_no_return(a: i32) -> impl Fn() { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:18:5 + | +LL | fn one_arg(a: i32) -> fn(i32) -> i32 { + | -------------- expected `fn(i32) -> i32` because of return type +LL | |x| x + a + | ^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn(i32) -> i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:18:5: 18:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:18:13 + | +LL | |x| x + a + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg(a: i32) -> fn(i32) -> i32 { +LL + fn one_arg(a: i32) -> impl Fn(i32) -> i32 { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:23:5 + | +LL | fn one_arg_unsafe(a: i32) -> unsafe fn(i32) -> i32 { + | --------------------- expected `unsafe fn(i32) -> i32` because of return type +LL | |x| x + a + | ^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `unsafe fn(i32) -> i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:23:5: 23:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:23:13 + | +LL | |x| x + a + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg_unsafe(a: i32) -> unsafe fn(i32) -> i32 { +LL + fn one_arg_unsafe(a: i32) -> impl Fn(i32) -> i32 { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:28:5 + | +LL | fn one_arg_extern(a: i32) -> extern "C" fn(i32) -> i32 { + | ------------------------- expected `extern "C" fn(i32) -> i32` because of return type +LL | |x| x + a + | ^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `extern "C" fn(i32) -> i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:28:5: 28:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:28:13 + | +LL | |x| x + a + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg_extern(a: i32) -> extern "C" fn(i32) -> i32 { +LL + fn one_arg_extern(a: i32) -> impl Fn(i32) -> i32 { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:33:5 + | +LL | fn one_arg_no_return(a: i32) -> fn(i32) { + | ------- expected `fn(i32)` because of return type +LL | |x| println!("{}", x + a) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn(i32)` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:33:5: 33:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:33:28 + | +LL | |x| println!("{}", x + a) + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg_no_return(a: i32) -> fn(i32) { +LL + fn one_arg_no_return(a: i32) -> impl Fn(i32) { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:38:5 + | +LL | fn one_arg_return_unit(a: i32) -> fn(i32) -> () { + | ------------- expected `fn(i32)` because of return type +LL | |x| println!("{}", x + a) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn(i32)` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:38:5: 38:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:38:28 + | +LL | |x| println!("{}", x + a) + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg_return_unit(a: i32) -> fn(i32) -> () { +LL + fn one_arg_return_unit(a: i32) -> impl Fn(i32) -> () { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:43:5 + | +LL | fn one_arg_ref(a: i32) -> fn(&i32) -> &i32 { + | ---------------- expected `for<'a> fn(&'a i32) -> &'a i32` because of return type +LL | / |x| { +LL | | +LL | | println!("{}", a); +LL | | x +LL | | } + | |_____^ expected fn pointer, found closure + | + = note: expected fn pointer `for<'a> fn(&'a i32) -> &'a i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:43:5: 43:8}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:45:24 + | +LL | println!("{}", a); + | ^ `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn one_arg_ref(a: i32) -> fn(&i32) -> &i32 { +LL + fn one_arg_ref(a: i32) -> impl Fn(&'_ i32) -> &'_ i32 { + | + +error[E0308]: mismatched types + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:51:5 + | +LL | fn multi_args(a: i32, b: i32) -> fn(i32, i32) -> i32 { + | ------------------- expected `fn(i32, i32) -> i32` because of return type +LL | |x, y| x + y + a + b + | ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure + | + = note: expected fn pointer `fn(i32, i32) -> i32` + found closure `{closure@$DIR/suggest-impl-fn-return-for-capturing-closure.rs:51:5: 51:11}` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/suggest-impl-fn-return-for-capturing-closure.rs:51:20 + | +LL | |x, y| x + y + a + b + | ^ ^ `b` captured here + | | + | `a` captured here +help: change the return type to return a type-erased closure instead + | +LL - fn multi_args(a: i32, b: i32) -> fn(i32, i32) -> i32 { +LL + fn multi_args(a: i32, b: i32) -> impl Fn(i32, i32) -> i32 { + | + +error: aborting due to 9 previous errors + +For more information about this error, try `rustc --explain E0308`.