Skip to content

Commit 448097d

Browse files
committed
Suggest async block instead of async closure when possible
1 parent 1d113d2 commit 448097d

6 files changed

Lines changed: 121 additions & 7 deletions

compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,34 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
798798
return false;
799799
}
800800

801+
// If this is a zero-argument async closure directly passed as an argument
802+
// and the expected type is `Future`, suggest using `async {}` block instead
803+
// of `async || {}`.
804+
if let ty::CoroutineClosure(def_id, args) = *self_ty.kind()
805+
&& let sig = args.as_coroutine_closure().coroutine_closure_sig().skip_binder()
806+
&& let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind()
807+
&& inputs.is_empty()
808+
&& self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Future)
809+
&& let Some(hir::Node::Expr(hir::Expr {
810+
kind:
811+
hir::ExprKind::Closure(hir::Closure {
812+
kind: hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async),
813+
fn_arg_span: Some(arg_span),
814+
..
815+
}),
816+
..
817+
})) = self.tcx.hir_get_if_local(def_id)
818+
&& obligation.cause.span.contains(*arg_span)
819+
{
820+
err.span_suggestion_verbose(
821+
arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)),
822+
"use `async {}` instead of `async || {}` to introduce an async block",
823+
"",
824+
Applicability::MachineApplicable,
825+
);
826+
return true;
827+
}
828+
801829
// Get the name of the callable and the arguments to be used in the suggestion.
802830
let msg = match def_id_or_name {
803831
DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ edition:2024
2+
// Test that we suggest using `async {}` block instead of `async || {}` closure if possible
3+
4+
use std::future::Future;
5+
6+
fn takes_future(_fut: impl Future<Output = ()>) {}
7+
8+
fn main() {
9+
// Basic case: suggest using async block
10+
takes_future(async || {
11+
//~^ ERROR is not a future
12+
println!("hi!");
13+
});
14+
15+
// With arguments: should suggest calling the closure, not using async block
16+
takes_future(async |x: i32| {
17+
//~^ ERROR is not a future
18+
println!("{x}");
19+
});
20+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future
2+
--> $DIR/suggest-async-block-issue-140265.rs:10:18
3+
|
4+
LL | takes_future(async || {
5+
| _____------------_^
6+
| | |
7+
| | required by a bound introduced by this call
8+
LL | |
9+
LL | | println!("hi!");
10+
LL | | });
11+
| |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future
12+
|
13+
= help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}`
14+
note: required by a bound in `takes_future`
15+
--> $DIR/suggest-async-block-issue-140265.rs:6:28
16+
|
17+
LL | fn takes_future(_fut: impl Future<Output = ()>) {}
18+
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future`
19+
help: use `async {}` instead of `async || {}` to introduce an async block
20+
|
21+
LL - takes_future(async || {
22+
LL + takes_future(async {
23+
|
24+
25+
error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future
26+
--> $DIR/suggest-async-block-issue-140265.rs:16:18
27+
|
28+
LL | takes_future(async |x: i32| {
29+
| _____------------_^
30+
| | |
31+
| | required by a bound introduced by this call
32+
LL | |
33+
LL | | println!("{x}");
34+
LL | | });
35+
| |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future
36+
|
37+
= help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}`
38+
note: required by a bound in `takes_future`
39+
--> $DIR/suggest-async-block-issue-140265.rs:6:28
40+
|
41+
LL | fn takes_future(_fut: impl Future<Output = ()>) {}
42+
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future`
43+
help: use parentheses to call this closure
44+
|
45+
LL | }(/* i32 */));
46+
| +++++++++++
47+
48+
error: aborting due to 2 previous errors
49+
50+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ edition:2018
2+
//@ run-rustfix
3+
#![allow(unused_variables)]
4+
use std::future::Future;
5+
6+
async fn foo() {}
7+
8+
fn bar(f: impl Future<Output=()>) {}
9+
10+
fn main() {
11+
bar(foo()); //~ERROR E0277
12+
let async_closure = async || ();
13+
bar(async_closure()); //~ERROR E0277
14+
}

tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//@ edition:2018
2+
//@ run-rustfix
3+
#![allow(unused_variables)]
24
use std::future::Future;
35

46
async fn foo() {}

tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: `fn() -> impl Future<Output = ()> {foo}` is not a future
2-
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:9:9
2+
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9
33
|
44
LL | bar(foo);
55
| --- ^^^ `fn() -> impl Future<Output = ()> {foo}` is not a future
@@ -8,7 +8,7 @@ LL | bar(foo);
88
|
99
= help: the trait `Future` is not implemented for fn item `fn() -> impl Future<Output = ()> {foo}`
1010
note: required by a bound in `bar`
11-
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16
11+
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16
1212
|
1313
LL | fn bar(f: impl Future<Output=()>) {}
1414
| ^^^^^^^^^^^^^^^^^ required by this bound in `bar`
@@ -17,17 +17,17 @@ help: use parentheses to call this function
1717
LL | bar(foo());
1818
| ++
1919

20-
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
21-
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9
20+
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
21+
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:13:9
2222
|
2323
LL | bar(async_closure);
24-
| --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` is not a future
24+
| --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` is not a future
2525
| |
2626
| required by a bound introduced by this call
2727
|
28-
= 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}`
28+
= 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}`
2929
note: required by a bound in `bar`
30-
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16
30+
--> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16
3131
|
3232
LL | fn bar(f: impl Future<Output=()>) {}
3333
| ^^^^^^^^^^^^^^^^^ required by this bound in `bar`

0 commit comments

Comments
 (0)