From aed866a14209b8a2262b6333d3a3c74be6e6de87 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 7 Aug 2026 09:24:14 +0800 Subject: [PATCH] Suggest add async for function sig with return expr in body --- .../rustc_trait_selection/src/diagnostics.rs | 12 ++++ .../src/error_reporting/infer/suggest.rs | 70 +++++++++++++++++-- ...returned-future-ineligible-issue-159495.rs | 29 ++++++++ ...rned-future-ineligible-issue-159495.stderr | 17 +++++ ...-fn-for-returned-future-issue-159495.fixed | 36 ++++++++++ ...ync-fn-for-returned-future-issue-159495.rs | 36 ++++++++++ ...fn-for-returned-future-issue-159495.stderr | 55 +++++++++++++++ 7 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs create mode 100644 tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.stderr create mode 100644 tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.fixed create mode 100644 tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.rs create mode 100644 tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.stderr diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index 44b5e669d5251..eea574a70cb3f 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -1426,6 +1426,18 @@ pub(crate) enum ConsiderAddingAwait { #[suggestion_part(code = ".await")] spans: Vec, }, + #[multipart_suggestion( + "consider making the function `async` and `await`ing on the `Future`", + style = "verbose", + applicability = "maybe-incorrect" + )] + MakeFunctionAsync { + #[suggestion_part(code = "{async_prefix}")] + async_span: Span, + async_prefix: String, + #[suggestion_part(code = ".await")] + await_span: Span, + }, } #[derive(Diagnostic)] diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs index 5bd9a9c52de7f..db852701051cf 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs @@ -159,8 +159,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { /// ``` /// /// This routine checks if the found type `T` implements `Future` where `U` is the - /// expected type. If this is the case, and we are inside of an async body, it suggests adding - /// `.await` to the tail of the expression. + /// expected type. In an async body, it suggests adding `.await` to the expression. For a + /// return expression in a synchronous function, it suggests making the function async and + /// awaiting the expression together. pub(super) fn suggest_await_on_expect_found( &self, cause: &ObligationCause<'tcx>, @@ -178,11 +179,14 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::AsyncGen, _, )) => (), - None - | Some( + Some( hir::CoroutineKind::Coroutine(_) | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _), ) => return, + None => { + self.suggest_add_async_for_tail_return_expr(cause, exp_span, exp_found, diag); + return; + } } if let ObligationCauseCode::CompareImplItem { .. } = cause.code() { @@ -268,6 +272,64 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } + fn suggest_add_async_for_tail_return_expr( + &self, + cause: &ObligationCause<'tcx>, + exp_span: Span, + exp_found: &ty::error::ExpectedFound>, + diag: &mut Diag<'_>, + ) { + let (ObligationCauseCode::BlockTailExpression(return_hir_id, ..) + | ObligationCauseCode::ReturnValue(return_hir_id)) = cause.code() + else { + return; + }; + + let body_def_id = cause.body_def_id; + if !self.tcx.sess.at_least_rust_2018() || self.tcx.is_entrypoint(body_def_id.to_def_id()) { + return; + } + + let node = self.tcx.hir_node_by_def_id(body_def_id); + let (item_span, vis_span) = match node { + Node::Item(item) if matches!(item.kind, hir::ItemKind::Fn { .. }) => { + (item.span, item.vis_span) + } + Node::ImplItem(item) if matches!(item.kind, hir::ImplItemKind::Fn(..)) => { + let Some(vis_span) = item.vis_span() else { return }; + (item.span, vis_span) + } + _ => return, + }; + let Some(sig) = node.fn_sig() else { + return; + }; + if sig.header.asyncness.is_async() + || sig.header.constness != hir::Constness::NotConst + || item_span.from_expansion() + { + return; + } + + let (async_span, async_prefix) = if vis_span.is_empty() { + (item_span.shrink_to_lo(), "async ".to_string()) + } else { + (vis_span.shrink_to_hi(), " async".to_string()) + }; + let body_hir_id = self.tcx.local_def_id_to_hir_id(body_def_id); + if self.tcx.hir_get_fn_id_for_return_block(*return_hir_id) == Some(body_hir_id) + && let Some(found) = self.tcx.get_impl_future_output_ty(exp_found.found) + && self.same_type_modulo_infer(exp_found.expected, found) + && exp_span.can_be_used_for_suggestions() + { + diag.subdiagnostic(ConsiderAddingAwait::MakeFunctionAsync { + async_span, + async_prefix, + await_span: exp_span.shrink_to_hi(), + }); + } + } + pub(super) fn suggest_accessing_field_where_appropriate( &self, cause: &ObligationCause<'tcx>, diff --git a/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs b/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs new file mode 100644 index 0000000000000..fa65f7ec77b4f --- /dev/null +++ b/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs @@ -0,0 +1,29 @@ +//@ edition: 2018 + +// Do not suggest changing a function when the future is not its return value or when doing so +// would make a trait method incompatible with its declaration. + +async fn number() -> i32 { + 42 +} + +// A local block tail does not contribute to the function's return value. +fn local_block() { + let _: i32 = { number() }; + //~^ ERROR mismatched types +} + +struct Wrapper; + +trait Trait { + fn trait_method() -> i32; +} + +impl Trait for Wrapper { + fn trait_method() -> i32 { + number() + //~^ ERROR mismatched types + } +} + +fn main() {} diff --git a/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.stderr b/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.stderr new file mode 100644 index 0000000000000..9efba3a55df66 --- /dev/null +++ b/tests/ui/async-await/suggest-async-fn-for-returned-future-ineligible-issue-159495.stderr @@ -0,0 +1,17 @@ +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs:12:20 + | +LL | let _: i32 = { number() }; + | ^^^^^^^^ expected `i32`, found future + +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-ineligible-issue-159495.rs:24:9 + | +LL | fn trait_method() -> i32 { + | --- expected `i32` because of return type +LL | number() + | ^^^^^^^^ expected `i32`, found future + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.fixed b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.fixed new file mode 100644 index 0000000000000..d0312ce7d726a --- /dev/null +++ b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.fixed @@ -0,0 +1,36 @@ +//@ edition: 2021 +//@ run-rustfix + +#![allow(dead_code, unused_must_use)] + +// Suggest making eligible enclosing functions async when a return expression produces a future. + +async fn number() -> i32 { + 42 +} + +async fn unit() {} + +async fn wrapped_number() -> i32 { + number().await + //~^ ERROR mismatched types +} + +async fn explicit_return() -> i32 { + return number().await; + //~^ ERROR mismatched types +} + +struct Wrapper; + +impl Wrapper { + pub async unsafe fn inherent_method() -> i32 { + number().await + //~^ ERROR mismatched types + } +} + +fn main() { + unit(); + //~^ ERROR mismatched types +} diff --git a/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.rs b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.rs new file mode 100644 index 0000000000000..ab1e05d95a8b8 --- /dev/null +++ b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.rs @@ -0,0 +1,36 @@ +//@ edition: 2021 +//@ run-rustfix + +#![allow(dead_code, unused_must_use)] + +// Suggest making eligible enclosing functions async when a return expression produces a future. + +async fn number() -> i32 { + 42 +} + +async fn unit() {} + +fn wrapped_number() -> i32 { + number() + //~^ ERROR mismatched types +} + +fn explicit_return() -> i32 { + return number(); + //~^ ERROR mismatched types +} + +struct Wrapper; + +impl Wrapper { + pub unsafe fn inherent_method() -> i32 { + number() + //~^ ERROR mismatched types + } +} + +fn main() { + unit() + //~^ ERROR mismatched types +} diff --git a/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.stderr b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.stderr new file mode 100644 index 0000000000000..dd0abc84e6a18 --- /dev/null +++ b/tests/ui/async-await/suggest-async-fn-for-returned-future-issue-159495.stderr @@ -0,0 +1,55 @@ +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-issue-159495.rs:15:5 + | +LL | fn wrapped_number() -> i32 { + | --- expected `i32` because of return type +LL | number() + | ^^^^^^^^ expected `i32`, found future + | +help: consider making the function `async` and `await`ing on the `Future` + | +LL ~ async fn wrapped_number() -> i32 { +LL ~ number().await + | + +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-issue-159495.rs:20:12 + | +LL | fn explicit_return() -> i32 { + | --- expected `i32` because of return type +LL | return number(); + | ^^^^^^^^ expected `i32`, found future + | +help: consider making the function `async` and `await`ing on the `Future` + | +LL ~ async fn explicit_return() -> i32 { +LL ~ return number().await; + | + +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-issue-159495.rs:28:9 + | +LL | pub unsafe fn inherent_method() -> i32 { + | --- expected `i32` because of return type +LL | number() + | ^^^^^^^^ expected `i32`, found future + | +help: consider making the function `async` and `await`ing on the `Future` + | +LL ~ pub async unsafe fn inherent_method() -> i32 { +LL ~ number().await + | + +error[E0308]: mismatched types + --> $DIR/suggest-async-fn-for-returned-future-issue-159495.rs:34:5 + | +LL | fn main() { + | - expected `()` because of default return type +LL | unit() + | ^^^^^^- help: consider using a semicolon here: `;` + | | + | expected `()`, found future + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0308`.