From a5bc8d4f3c00b438bee7e51fb1d2fde71fe2b4ff Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 13 Jan 2026 20:41:41 +0800 Subject: [PATCH] Fix wrong suggestion for returning async closure --- .../src/error_reporting/traits/suggestions.rs | 92 ++++++++++++++----- .../suggest-async-block-issue-140265.stderr | 7 +- .../const_param_ty_bad.stderr | 5 +- .../suggest-create-closure-issue-150701.fixed | 15 +++ .../suggest-create-closure-issue-150701.rs | 15 +++ ...suggest-create-closure-issue-150701.stderr | 52 +++++++++++ ...theses-to-call-closure-issue-145404.stderr | 5 +- 7 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 tests/ui/suggestions/suggest-create-closure-issue-150701.fixed create mode 100644 tests/ui/suggestions/suggest-create-closure-issue-150701.rs create mode 100644 tests/ui/suggestions/suggest-create-closure-issue-150701.stderr 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 4a6d5eb48f8f1..83ee6d4cfe87e 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -805,17 +805,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { && let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind() && inputs.is_empty() && self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Future) + && let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code() + && let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(..), .. }) = + self.tcx.hir_node(*arg_hir_id) && 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), - .. - }), - .. + kind: hir::ExprKind::Closure(closure), .. })) = self.tcx.hir_get_if_local(def_id) - && obligation.cause.span.contains(*arg_span) + && let hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async) = closure.kind + && let Some(arg_span) = closure.fn_arg_span + && obligation.cause.span.contains(arg_span) { + let mut body = self.tcx.hir_body(closure.body).value; + let peeled = body.peel_blocks().peel_drop_temps(); + if let hir::ExprKind::Closure(inner) = peeled.kind { + body = self.tcx.hir_body(inner.body).value; + } + if !matches!(body.peel_blocks().peel_drop_temps().kind, hir::ExprKind::Block(..)) { + return false; + } + 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))) @@ -824,7 +832,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // There's a space after `||`, include it in the removal arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)) } else { - *arg_span + arg_span }; err.span_suggestion_verbose( removal_span, @@ -864,23 +872,63 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .collect::>() .join(", "); - if matches!(obligation.cause.code(), ObligationCauseCode::FunctionArg { .. }) + if let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code() && obligation.cause.span.can_be_used_for_suggestions() { - let (span, sugg) = if let Some(snippet) = - self.tcx.sess.source_map().span_to_snippet(obligation.cause.span).ok() - && snippet.starts_with("|") - { - (obligation.cause.span, format!("({snippet})({args})")) - } else { - (obligation.cause.span.shrink_to_hi(), format!("({args})")) + let span = obligation.cause.span; + + let arg_expr = match self.tcx.hir_node(*arg_hir_id) { + hir::Node::Expr(expr) => Some(expr), + _ => None, }; - // When the obligation error has been ensured to have been caused by - // an argument, the `obligation.cause.span` points at the expression - // of the argument, so we can provide a suggestion. Otherwise, we give - // a more general note. - err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders); + let is_closure_expr = + arg_expr.is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Closure(..))); + + // If the user wrote `|| {}()`, suggesting to call the closure would produce `(|| {}())()`, + // which doesn't help and is often outright wrong. + if args.is_empty() + && let Some(expr) = arg_expr + && let hir::ExprKind::Closure(closure) = expr.kind + { + let mut body = self.tcx.hir_body(closure.body).value; + + // Async closures desugar to a closure returning a coroutine + if let hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async) = + closure.kind + { + let peeled = body.peel_blocks().peel_drop_temps(); + if let hir::ExprKind::Closure(inner) = peeled.kind { + body = self.tcx.hir_body(inner.body).value; + } + } + + let peeled_body = body.peel_blocks().peel_drop_temps(); + if let hir::ExprKind::Call(callee, call_args) = peeled_body.kind + && call_args.is_empty() + && let hir::ExprKind::Block(..) = callee.peel_blocks().peel_drop_temps().kind + { + return false; + } + } + + if is_closure_expr { + err.multipart_suggestions( + msg, + vec![vec![ + (span.shrink_to_lo(), "(".to_string()), + (span.shrink_to_hi(), format!(")({args})")), + ]], + Applicability::HasPlaceholders, + ); + } else { + err.span_suggestion_verbose( + span.shrink_to_hi(), + msg, + format!("({args})"), + Applicability::HasPlaceholders, + ); + } } else if let DefIdOrName::DefId(def_id) = def_id_or_name { let name = match self.tcx.hir_get_if_local(def_id) { Some(hir::Node::Expr(hir::Expr { 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 d81cfaac7f7d1..8e9e8ce1e7303 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 @@ -66,8 +66,11 @@ LL | fn takes_future(_fut: impl Future) {} | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` help: use parentheses to call this closure | -LL | }(/* i32 */)); - | +++++++++++ +LL ~ takes_future((async |x: i32| { +LL | +LL | println!("{x}"); +LL ~ })(/* i32 */)); + | error: aborting due to 3 previous errors diff --git a/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr b/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr index be63c9e5c046b..0237e7bb5eeab 100644 --- a/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr +++ b/tests/ui/const-generics/adt_const_params/const_param_ty_bad.stderr @@ -32,9 +32,8 @@ LL | fn check(_: impl std::marker::ConstParamTy_) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `check` help: use parentheses to call this closure | -LL - check(|| {}); -LL + check((|| {})()); - | +LL | check((|| {})()); + | + +++ error[E0277]: `fn()` can't be used as a const parameter type --> $DIR/const_param_ty_bad.rs:9:11 diff --git a/tests/ui/suggestions/suggest-create-closure-issue-150701.fixed b/tests/ui/suggestions/suggest-create-closure-issue-150701.fixed new file mode 100644 index 0000000000000..f3e226c9e5681 --- /dev/null +++ b/tests/ui/suggestions/suggest-create-closure-issue-150701.fixed @@ -0,0 +1,15 @@ +// Regression test for #150701 + +//@ run-rustfix +//@ edition: 2024 + +use std::future::Future; + +fn f(_c: impl Future) {} + +fn main() { + f((async || {})()); //~ ERROR: expected function, found `()` + //~^ ERROR: is not a future + f(async {}); + //~^ ERROR: is not a future +} diff --git a/tests/ui/suggestions/suggest-create-closure-issue-150701.rs b/tests/ui/suggestions/suggest-create-closure-issue-150701.rs new file mode 100644 index 0000000000000..e7a5076d8f1d9 --- /dev/null +++ b/tests/ui/suggestions/suggest-create-closure-issue-150701.rs @@ -0,0 +1,15 @@ +// Regression test for #150701 + +//@ run-rustfix +//@ edition: 2024 + +use std::future::Future; + +fn f(_c: impl Future) {} + +fn main() { + f(async || {}()); //~ ERROR: expected function, found `()` + //~^ ERROR: is not a future + f(async || {}); + //~^ ERROR: is not a future +} diff --git a/tests/ui/suggestions/suggest-create-closure-issue-150701.stderr b/tests/ui/suggestions/suggest-create-closure-issue-150701.stderr new file mode 100644 index 0000000000000..0f991169d6772 --- /dev/null +++ b/tests/ui/suggestions/suggest-create-closure-issue-150701.stderr @@ -0,0 +1,52 @@ +error[E0618]: expected function, found `()` + --> $DIR/suggest-create-closure-issue-150701.rs:11:16 + | +LL | f(async || {}()); + | ^^-- + | | + | call expression requires function + | +help: if you meant to create this closure and immediately call it, surround the closure with parentheses + | +LL | f((async || {})()); + | + + + +error[E0277]: `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}` is not a future + --> $DIR/suggest-create-closure-issue-150701.rs:11:7 + | +LL | f(async || {}()); + | - ^^^^^^^^^^^^^ `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}` is not a future + | | + | required by a bound introduced by this call + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-create-closure-issue-150701.rs:11:7: 11:15}` +note: required by a bound in `f` + --> $DIR/suggest-create-closure-issue-150701.rs:8:15 + | +LL | fn f(_c: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `f` + +error[E0277]: `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}` is not a future + --> $DIR/suggest-create-closure-issue-150701.rs:13:7 + | +LL | f(async || {}); + | - ^^^^^^^^^^^ `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}` is not a future + | | + | required by a bound introduced by this call + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-create-closure-issue-150701.rs:13:7: 13:15}` +note: required by a bound in `f` + --> $DIR/suggest-create-closure-issue-150701.rs:8:15 + | +LL | fn f(_c: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `f` +help: use `async {}` instead of `async || {}` to introduce an async block + | +LL - f(async || {}); +LL + f(async {}); + | + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0618. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr b/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr index cb6df5af7fb16..e32b2d4a30c80 100644 --- a/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr +++ b/tests/ui/suggestions/use-parentheses-to-call-closure-issue-145404.stderr @@ -14,9 +14,8 @@ LL | fn call(&self, _: impl Display) {} | ^^^^^^^ required by this bound in `S::call` help: use parentheses to call this closure | -LL - S.call(|| "hello"); -LL + S.call((|| "hello")()); - | +LL | S.call((|| "hello")()); + | + +++ error: aborting due to 1 previous error