Skip to content

Commit ae05a49

Browse files
committed
Fix wrong suggestion for returning async closure
1 parent 6f109d8 commit ae05a49

7 files changed

Lines changed: 161 additions & 30 deletions

File tree

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

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,17 +1034,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
10341034
&& let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind()
10351035
&& inputs.is_empty()
10361036
&& self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Future)
1037+
&& let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code()
1038+
&& let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(..), .. }) =
1039+
self.tcx.hir_node(*arg_hir_id)
10371040
&& let Some(hir::Node::Expr(hir::Expr {
1038-
kind:
1039-
hir::ExprKind::Closure(hir::Closure {
1040-
kind: hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async),
1041-
fn_arg_span: Some(arg_span),
1042-
..
1043-
}),
1044-
..
1041+
kind: hir::ExprKind::Closure(closure), ..
10451042
})) = self.tcx.hir_get_if_local(def_id)
1046-
&& obligation.cause.span.contains(*arg_span)
1043+
&& let hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async) = closure.kind
1044+
&& let Some(arg_span) = closure.fn_arg_span
1045+
&& obligation.cause.span.contains(arg_span)
10471046
{
1047+
let mut body = self.tcx.hir_body(closure.body).value;
1048+
let peeled = body.peel_blocks().peel_drop_temps();
1049+
if let hir::ExprKind::Closure(inner) = peeled.kind {
1050+
body = self.tcx.hir_body(inner.body).value;
1051+
}
1052+
if !matches!(body.peel_blocks().peel_drop_temps().kind, hir::ExprKind::Block(..)) {
1053+
return false;
1054+
}
1055+
10481056
let sm = self.tcx.sess.source_map();
10491057
let removal_span = if let Ok(snippet) =
10501058
sm.span_to_snippet(arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)))
@@ -1053,7 +1061,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
10531061
// There's a space after `||`, include it in the removal
10541062
arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1))
10551063
} else {
1056-
*arg_span
1064+
arg_span
10571065
};
10581066
err.span_suggestion_verbose(
10591067
removal_span,
@@ -1093,23 +1101,63 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
10931101
.collect::<Vec<_>>()
10941102
.join(", ");
10951103

1096-
if matches!(obligation.cause.code(), ObligationCauseCode::FunctionArg { .. })
1104+
if let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code()
10971105
&& obligation.cause.span.can_be_used_for_suggestions()
10981106
{
1099-
let (span, sugg) = if let Some(snippet) =
1100-
self.tcx.sess.source_map().span_to_snippet(obligation.cause.span).ok()
1101-
&& snippet.starts_with("|")
1102-
{
1103-
(obligation.cause.span, format!("({snippet})({args})"))
1104-
} else {
1105-
(obligation.cause.span.shrink_to_hi(), format!("({args})"))
1107+
let span = obligation.cause.span;
1108+
1109+
let arg_expr = match self.tcx.hir_node(*arg_hir_id) {
1110+
hir::Node::Expr(expr) => Some(expr),
1111+
_ => None,
11061112
};
11071113

1108-
// When the obligation error has been ensured to have been caused by
1109-
// an argument, the `obligation.cause.span` points at the expression
1110-
// of the argument, so we can provide a suggestion. Otherwise, we give
1111-
// a more general note.
1112-
err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders);
1114+
let is_closure_expr =
1115+
arg_expr.is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Closure(..)));
1116+
1117+
// If the user wrote `|| {}()`, suggesting to call the closure would produce `(|| {}())()`,
1118+
// which doesn't help and is often outright wrong.
1119+
if args.is_empty()
1120+
&& let Some(expr) = arg_expr
1121+
&& let hir::ExprKind::Closure(closure) = expr.kind
1122+
{
1123+
let mut body = self.tcx.hir_body(closure.body).value;
1124+
1125+
// Async closures desugar to a closure returning a coroutine
1126+
if let hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async) =
1127+
closure.kind
1128+
{
1129+
let peeled = body.peel_blocks().peel_drop_temps();
1130+
if let hir::ExprKind::Closure(inner) = peeled.kind {
1131+
body = self.tcx.hir_body(inner.body).value;
1132+
}
1133+
}
1134+
1135+
let peeled_body = body.peel_blocks().peel_drop_temps();
1136+
if let hir::ExprKind::Call(callee, call_args) = peeled_body.kind
1137+
&& call_args.is_empty()
1138+
&& let hir::ExprKind::Block(..) = callee.peel_blocks().peel_drop_temps().kind
1139+
{
1140+
return false;
1141+
}
1142+
}
1143+
1144+
if is_closure_expr {
1145+
err.multipart_suggestions(
1146+
msg,
1147+
vec![vec![
1148+
(span.shrink_to_lo(), "(".to_string()),
1149+
(span.shrink_to_hi(), format!(")({args})")),
1150+
]],
1151+
Applicability::HasPlaceholders,
1152+
);
1153+
} else {
1154+
err.span_suggestion_verbose(
1155+
span.shrink_to_hi(),
1156+
msg,
1157+
format!("({args})"),
1158+
Applicability::HasPlaceholders,
1159+
);
1160+
}
11131161
} else if let DefIdOrName::DefId(def_id) = def_id_or_name {
11141162
let name = match self.tcx.hir_get_if_local(def_id) {
11151163
Some(hir::Node::Expr(hir::Expr {

tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ LL | fn takes_future(_fut: impl Future<Output = ()>) {}
6666
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future`
6767
help: use parentheses to call this closure
6868
|
69-
LL | }(/* i32 */));
70-
| +++++++++++
69+
LL ~ takes_future((async |x: i32| {
70+
LL |
71+
LL | println!("{x}");
72+
LL ~ })(/* i32 */));
73+
|
7174

7275
error: aborting due to 3 previous errors
7376

tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ LL | fn check(_: impl std::marker::ConstParamTy_) {}
3232
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `check`
3333
help: use parentheses to call this closure
3434
|
35-
LL - check(|| {});
36-
LL + check((|| {})());
37-
|
35+
LL | check((|| {})());
36+
| + +++
3837

3938
error[E0277]: `fn()` can't be used as a const parameter type
4039
--> $DIR/const_param_ty_bad.rs:9:11
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Regression test for #150701
2+
3+
//@ run-rustfix
4+
//@ edition: 2024
5+
6+
use std::future::Future;
7+
8+
fn f(_c: impl Future<Output = ()>) {}
9+
10+
fn main() {
11+
f((async || {})()); //~ ERROR: expected function, found `()`
12+
//~^ ERROR: is not a future
13+
f(async {});
14+
//~^ ERROR: is not a future
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Regression test for #150701
2+
3+
//@ run-rustfix
4+
//@ edition: 2024
5+
6+
use std::future::Future;
7+
8+
fn f(_c: impl Future<Output = ()>) {}
9+
10+
fn main() {
11+
f(async || {}()); //~ ERROR: expected function, found `()`
12+
//~^ ERROR: is not a future
13+
f(async || {});
14+
//~^ ERROR: is not a future
15+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error[E0618]: expected function, found `()`
2+
--> $DIR/suggest-create-closure-issue-150701.rs:11:16
3+
|
4+
LL | f(async || {}());
5+
| ^^--
6+
| |
7+
| call expression requires function
8+
|
9+
help: if you meant to create this closure and immediately call it, surround the closure with parentheses
10+
|
11+
LL | f((async || {})());
12+
| + +
13+
14+
error[E0277]: `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}` is not a future
15+
--> $DIR/suggest-create-closure-issue-150701.rs:11:7
16+
|
17+
LL | f(async || {}());
18+
| - ^^^^^^^^^^^^^ `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}` is not a future
19+
| |
20+
| required by a bound introduced by this call
21+
|
22+
= help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}`
23+
note: required by a bound in `f`
24+
--> $DIR/suggest-create-closure-issue-150701.rs:8:15
25+
|
26+
LL | fn f(_c: impl Future<Output = ()>) {}
27+
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `f`
28+
29+
error[E0277]: `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}` is not a future
30+
--> $DIR/suggest-create-closure-issue-150701.rs:13:7
31+
|
32+
LL | f(async || {});
33+
| - ^^^^^^^^^^^ `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}` is not a future
34+
| |
35+
| required by a bound introduced by this call
36+
|
37+
= help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}`
38+
note: required by a bound in `f`
39+
--> $DIR/suggest-create-closure-issue-150701.rs:8:15
40+
|
41+
LL | fn f(_c: impl Future<Output = ()>) {}
42+
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `f`
43+
help: use `async {}` instead of `async || {}` to introduce an async block
44+
|
45+
LL - f(async || {});
46+
LL + f(async {});
47+
|
48+
49+
error: aborting due to 3 previous errors
50+
51+
Some errors have detailed explanations: E0277, E0618.
52+
For more information about an error, try `rustc --explain E0277`.

tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ LL | fn call(&self, _: impl Display) {}
1414
| ^^^^^^^ required by this bound in `S::call`
1515
help: use parentheses to call this closure
1616
|
17-
LL - S.call(|| "hello");
18-
LL + S.call((|| "hello")());
19-
|
17+
LL | S.call((|| "hello")());
18+
| + +++
2019

2120
error: aborting due to 1 previous error
2221

0 commit comments

Comments
 (0)