Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion compiler/rustc_hir_typeck/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
39 changes: 37 additions & 2 deletions compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
53 changes: 53 additions & 0 deletions tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/159481>.
//! 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
}
198 changes: 198 additions & 0 deletions tests/ui/closures/suggest-impl-fn-return-for-capturing-closure.stderr
Original file line number Diff line number Diff line change
@@ -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 {
|
Comment on lines +148 to +171

@estebank estebank Jul 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think it would make sense to suggest impl for<'a> Fn(&'a i32) -> &'_ i32.

View changes since the review


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`.
Loading