Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions compiler/rustc_trait_selection/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,18 @@ pub(crate) enum ConsiderAddingAwait {
#[suggestion_part(code = ".await")]
spans: Vec<Span>,
},
#[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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// ```
///
/// This routine checks if the found type `T` implements `Future<Output=U>` 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>,
Expand All @@ -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() {
Expand Down Expand Up @@ -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<Ty<'tcx>>,
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>,
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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() };

@estebank estebank Aug 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if having a help saying "if the function was async, you could await" would make sense for some subset of cases that we are less certain of.

View changes since the review

| ^^^^^^^^ 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`.
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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`.
Loading