From 448097dd2386957a36880efbb78cb83021774540 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 31 Jan 2026 13:38:34 +0000 Subject: [PATCH 1/2] Suggest async block instead of async closure when possible --- .../src/error_reporting/traits/suggestions.rs | 28 +++++++++++ .../suggest-async-block-issue-140265.rs | 20 ++++++++ .../suggest-async-block-issue-140265.stderr | 50 +++++++++++++++++++ ...arg-where-it-should-have-been-called.fixed | 14 ++++++ ...as-arg-where-it-should-have-been-called.rs | 2 + ...rg-where-it-should-have-been-called.stderr | 14 +++--- 6 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs create mode 100644 tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr create mode 100644 tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed 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 176cc8c4c2cab..0e460763d9dcb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -798,6 +798,34 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return false; } + // If this is a zero-argument async closure directly passed as an argument + // and the expected type is `Future`, suggest using `async {}` block instead + // of `async || {}`. + if let ty::CoroutineClosure(def_id, args) = *self_ty.kind() + && let sig = args.as_coroutine_closure().coroutine_closure_sig().skip_binder() + && let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind() + && inputs.is_empty() + && self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Future) + && let Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::Closure(hir::Closure { + kind: hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async), + fn_arg_span: Some(arg_span), + .. + }), + .. + })) = self.tcx.hir_get_if_local(def_id) + && obligation.cause.span.contains(*arg_span) + { + err.span_suggestion_verbose( + arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)), + "use `async {}` instead of `async || {}` to introduce an async block", + "", + Applicability::MachineApplicable, + ); + return true; + } + // Get the name of the callable and the arguments to be used in the suggestion. let msg = match def_id_or_name { DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs new file mode 100644 index 0000000000000..9dc6153be2578 --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs @@ -0,0 +1,20 @@ +//@ edition:2024 +// Test that we suggest using `async {}` block instead of `async || {}` closure if possible + +use std::future::Future; + +fn takes_future(_fut: impl Future) {} + +fn main() { + // Basic case: suggest using async block + takes_future(async || { + //~^ ERROR is not a future + println!("hi!"); + }); + + // With arguments: should suggest calling the closure, not using async block + takes_future(async |x: i32| { + //~^ ERROR is not a future + println!("{x}"); + }); +} diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr new file mode 100644 index 0000000000000..a742a358c0a3f --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr @@ -0,0 +1,50 @@ +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:10:18 + | +LL | takes_future(async || { + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("hi!"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use `async {}` instead of `async || {}` to introduce an async block + | +LL - takes_future(async || { +LL + takes_future(async { + | + +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:16:18 + | +LL | takes_future(async |x: i32| { + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("{x}"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use parentheses to call this closure + | +LL | }(/* i32 */)); + | +++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed new file mode 100644 index 0000000000000..88a94058bd54a --- /dev/null +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed @@ -0,0 +1,14 @@ +//@ edition:2018 +//@ run-rustfix +#![allow(unused_variables)] +use std::future::Future; + +async fn foo() {} + +fn bar(f: impl Future) {} + +fn main() { + bar(foo()); //~ERROR E0277 + let async_closure = async || (); + bar(async_closure()); //~ERROR E0277 +} diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs index 8e67f4e7398cd..a05773497707c 100644 --- a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs @@ -1,4 +1,6 @@ //@ edition:2018 +//@ run-rustfix +#![allow(unused_variables)] use std::future::Future; async fn foo() {} diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr index 696b156d5a5f0..761a8d529f509 100644 --- a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr @@ -1,5 +1,5 @@ error[E0277]: `fn() -> impl Future {foo}` is not a future - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:9:9 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9 | LL | bar(foo); | --- ^^^ `fn() -> impl Future {foo}` is not a future @@ -8,7 +8,7 @@ LL | bar(foo); | = help: the trait `Future` is not implemented for fn item `fn() -> impl Future {foo}` note: required by a bound in `bar` - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16 | LL | fn bar(f: impl Future) {} | ^^^^^^^^^^^^^^^^^ required by this bound in `bar` @@ -17,17 +17,17 @@ help: use parentheses to call this function LL | bar(foo()); | ++ -error[E0277]: `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` is not a future - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9 +error[E0277]: `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` is not a future + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:13:9 | LL | bar(async_closure); - | --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` is not a future + | --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` is not a future | | | required by a bound introduced by this call | - = help: the trait `Future` is not implemented for `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` + = help: the trait `Future` is not implemented for `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` note: required by a bound in `bar` - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16 | LL | fn bar(f: impl Future) {} | ^^^^^^^^^^^^^^^^^ required by this bound in `bar` From 37364585a10e368918e4a79466be24354ff5cf87 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 5 Feb 2026 10:09:46 +0000 Subject: [PATCH 2/2] Fix async closure suggestion when no space between || and { --- .../src/error_reporting/traits/suggestions.rs | 14 ++++++-- .../suggest-async-block-issue-140265.rs | 6 ++++ .../suggest-async-block-issue-140265.stderr | 32 ++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) 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 0e460763d9dcb..386c75dad9dd3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -800,7 +800,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // If this is a zero-argument async closure directly passed as an argument // and the expected type is `Future`, suggest using `async {}` block instead - // of `async || {}`. + // of `async || {}` if let ty::CoroutineClosure(def_id, args) = *self_ty.kind() && let sig = args.as_coroutine_closure().coroutine_closure_sig().skip_binder() && let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind() @@ -817,8 +817,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { })) = self.tcx.hir_get_if_local(def_id) && obligation.cause.span.contains(*arg_span) { + let sm = self.tcx.sess.source_map(); + let removal_span = if let Ok(snippet) = + sm.span_to_snippet(arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1))) + && snippet.ends_with(' ') + { + // There's a space after `||`, include it in the removal + arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)) + } else { + *arg_span + }; err.span_suggestion_verbose( - arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)), + removal_span, "use `async {}` instead of `async || {}` to introduce an async block", "", Applicability::MachineApplicable, diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs index 9dc6153be2578..b5120d196f4ee 100644 --- a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs @@ -12,6 +12,12 @@ fn main() { println!("hi!"); }); + // Without space between `||` and `{`: should also suggest using async block + takes_future(async||{ + //~^ ERROR is not a future + println!("no space!"); + }); + // With arguments: should suggest calling the closure, not using async block takes_future(async |x: i32| { //~^ ERROR is not a future diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr index a742a358c0a3f..d81cfaac7f7d1 100644 --- a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr @@ -22,9 +22,33 @@ LL - takes_future(async || { LL + takes_future(async { | -error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` is not a future --> $DIR/suggest-async-block-issue-140265.rs:16:18 | +LL | takes_future(async||{ + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("no space!"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use `async {}` instead of `async || {}` to introduce an async block + | +LL - takes_future(async||{ +LL + takes_future(async{ + | + +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:22:18 + | LL | takes_future(async |x: i32| { | _____------------_^ | | | @@ -32,9 +56,9 @@ LL | takes_future(async |x: i32| { LL | | LL | | println!("{x}"); LL | | }); - | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` is not a future | - = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` note: required by a bound in `takes_future` --> $DIR/suggest-async-block-issue-140265.rs:6:28 | @@ -45,6 +69,6 @@ help: use parentheses to call this closure LL | }(/* i32 */)); | +++++++++++ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0277`.