diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index b98c3fab5bcb5..e8ad998f4cccf 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -522,6 +522,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); } + self.suggest_await_on_future( + &obligation, + &mut err, + leaf_trait_predicate, + span, + ); + if self.suggest_add_clone_to_arg( &obligation, &mut err, 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 2a1f0e7935ea0..8e160fe677103 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4574,41 +4574,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ) { let future_trait = self.tcx.require_lang_item(LangItem::Future, span); - let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty()); - let impls_future = self.type_implements_trait( - future_trait, - [self.tcx.instantiate_bound_regions_with_erased(self_ty)], - obligation.param_env, - ); - if !impls_future.must_apply_modulo_regions() { + // Don't suggest `.await` if the `trait_pred.self_ty()` doesn't implement `Future`. + if !self.check_self_ty(obligation, trait_pred, future_trait) { return; } - let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; - // `::Output` - let projection_ty = trait_pred.map_bound(|trait_pred| { - Ty::new_projection( - self.tcx, - ty::IsRigid::No, - item_def_id, - // Future::Output has no args - [trait_pred.self_ty()], - ) - }); - let InferOk { value: projection_ty, .. } = self - .at(&obligation.cause, obligation.param_env) - .normalize(Unnormalized::new_wip(projection_ty)); + // Don't suggest `.await` if `::Output` doesn't implement the target trait (`Try`). + if !self.check_future_output(obligation, trait_pred, future_trait) { + return; + } - debug!( - normalized_projection_type = ?self.resolve_vars_if_possible(projection_ty) - ); - let try_obligation = self.mk_trait_obligation_with_new_self_ty( - obligation.param_env, - trait_pred.map_bound(|trait_pred| (trait_pred, projection_ty.skip_binder())), - ); - debug!(try_trait_obligation = ?try_obligation); - if self.predicate_may_hold(&try_obligation) - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) && snippet.ends_with('?') { match self.tcx.coroutine_kind(obligation.cause.body_def_id) { @@ -4636,6 +4612,161 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } + /// When a future is used where a non-`Future` trait is expected (e.g. `Display`, + /// `Copy`), suggest `.await` if it would satisfy the trait bound. + pub(super) fn suggest_await_on_future( + &self, + obligation: &PredicateObligation<'tcx>, + err: &mut Diag<'_>, + trait_pred: ty::PolyTraitPredicate<'tcx>, + span: Span, + ) { + let Some(future_trait) = self.tcx.lang_items().get(LangItem::Future) else { + return; + }; + + // Don't suggest `.await` if the target trait is `Future` (that's a different error). + if trait_pred.def_id() == future_trait { + return; + } + + // Don't suggest `.await` if the target trait is `Try` - `suggest_await_before_try` handles this. + if self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Try) { + return; + } + + // Non-lifetime binders (e.g. `for`) produce bound types that + // `instantiate_bound_regions_with_erased` in `self.check_self_ty` cannot handle. + if trait_pred.bound_vars().len() > 0 { + return; + } + + // Don't suggest `.await` if the `trait_pred.self_ty()` doesn't implement `Future`. + if !self.check_self_ty(obligation, trait_pred, future_trait) { + return; + } + + // Don't suggest `.await` if `::Output` doesn't implement the target trait. + if !self.check_future_output(obligation, trait_pred, future_trait) { + return; + } + + let Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) = + self.tcx.coroutine_kind(obligation.cause.body_def_id) + else { + return; + }; + + let body = self.tcx.hir_body_owned_by(obligation.cause.body_def_id); + + // Don't suggest `.await` if the span is outside the function body + // (e.g. in a parameter type like `f: dyn Future`). + if !body.value.span.contains(span) { + return; + } + + let sm = self.tcx.sess.source_map(); + + // Check if span is inside a format macro (e.g. `{n}` in `println!("{n}")`). + if let Ok(snippet) = sm.span_to_snippet(span) + && snippet.starts_with('{') + && snippet.ends_with('}') + && let Some(var_name) = + snippet.trim_start_matches('{').trim_end_matches('}').split(':').next() + && !var_name.is_empty() + && let Some(let_stmt) = self.find_let_binding_by_name(&body.value, var_name) + && let hir::PatKind::Binding(_, _, ident, _) = let_stmt.pat.kind + { + // Format macro - add explicit let binding with `.await`. + let ident_name = ident.name; + let padding = sm.indentation_before(let_stmt.span).unwrap_or_default(); + err.multipart_suggestion( + "consider `await`ing on the `Future`", + vec![( + let_stmt.span.shrink_to_hi(), + format!("\n{padding}let {ident_name} = {ident_name}.await;"), + )], + Applicability::MaybeIncorrect, + ); + } else { + // Other - add `.await` at the usage site. + err.span_suggestion_verbose( + span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + } + + /// Check if `trait_pred.self_ty()` implements `Future`. + fn check_self_ty( + &self, + obligation: &PredicateObligation<'tcx>, + trait_pred: ty::PolyTraitPredicate<'tcx>, + future_trait: DefId, + ) -> bool { + let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty()); + let impls_future = self.type_implements_trait( + future_trait, + [self.tcx.instantiate_bound_regions_with_erased(self_ty)], + obligation.param_env, + ); + impls_future.must_apply_modulo_regions() + } + + /// Check if `::Output` implements a given trait. + fn check_future_output( + &self, + obligation: &PredicateObligation<'tcx>, + trait_pred: ty::PolyTraitPredicate<'tcx>, + future_trait: DefId, + ) -> bool { + let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; + let projection_ty = trait_pred.map_bound(|trait_pred| { + Ty::new_projection(self.tcx, ty::IsRigid::No, item_def_id, [trait_pred.self_ty()]) + }); + let InferOk { value: projection_ty, .. } = self + .at(&obligation.cause, obligation.param_env) + .normalize(Unnormalized::new_wip(projection_ty)); + let output_obligation = self.mk_trait_obligation_with_new_self_ty( + obligation.param_env, + trait_pred.map_bound(|trait_pred| (trait_pred, projection_ty.skip_binder())), + ); + self.predicate_may_hold(&output_obligation) + } + + /// Find a `let` binding in the body by variable name. + fn find_let_binding_by_name<'hir>( + &self, + body_expr: &'hir hir::Expr<'hir>, + name: &str, + ) -> Option<&'hir hir::LetStmt<'hir>> { + use hir::intravisit::Visitor; + + struct FindLetVisitor<'a, 'hir> { + name: &'a str, + found: Option<&'hir hir::LetStmt<'hir>>, + } + + impl<'a, 'hir> Visitor<'hir> for FindLetVisitor<'a, 'hir> { + fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) { + if let hir::StmtKind::Let(let_stmt) = &stmt.kind + && let hir::PatKind::Binding(_, _, ident, _) = let_stmt.pat.kind + && ident.name.as_str() == self.name + { + self.found = Some(let_stmt); + return; + } + hir::intravisit::walk_stmt(self, stmt); + } + } + + let mut visitor = FindLetVisitor { name, found: None }; + visitor.visit_expr(body_expr); + visitor.found + } + pub(super) fn suggest_floating_point_literal( &self, obligation: &PredicateObligation<'tcx>, diff --git a/tests/ui/async-await/suggest-await-on-future-for-trait-bound.rs b/tests/ui/async-await/suggest-await-on-future-for-trait-bound.rs new file mode 100644 index 0000000000000..a1d5897730deb --- /dev/null +++ b/tests/ui/async-await/suggest-await-on-future-for-trait-bound.rs @@ -0,0 +1,30 @@ +//! Regression test for . +//! When a future is used directly where a trait is expected (e.g. `take_copy(n)`), +//! suggest adding `.await` at the usage site: `take_copy(n.await)`. +//! Also checks that no suggestion is given when `Future::Output` doesn't implement the trait. +//@ edition:2021 + +#![crate_type = "lib"] + +fn take_copy(_: impl Copy) {} + +async fn make_number() -> i32 { + 42 +} + +async fn use_number() { + let number = make_number(); + take_copy(number); + //~^ ERROR the trait bound `impl Future: Copy` is not satisfied +} + +async fn make_string() -> String { + String::new() +} + +// String doesn't implement Copy, so no suggestion. +async fn use_string() { + let string = make_string(); + take_copy(string); + //~^ ERROR the trait bound `impl Future: Copy` is not satisfied +} diff --git a/tests/ui/async-await/suggest-await-on-future-for-trait-bound.stderr b/tests/ui/async-await/suggest-await-on-future-for-trait-bound.stderr new file mode 100644 index 0000000000000..1364bb19503ef --- /dev/null +++ b/tests/ui/async-await/suggest-await-on-future-for-trait-bound.stderr @@ -0,0 +1,35 @@ +error[E0277]: the trait bound `impl Future: Copy` is not satisfied + --> $DIR/suggest-await-on-future-for-trait-bound.rs:17:15 + | +LL | take_copy(number); + | --------- ^^^^^^ the trait `Copy` is not implemented for `impl Future` + | | + | required by a bound introduced by this call + | +note: required by a bound in `take_copy` + --> $DIR/suggest-await-on-future-for-trait-bound.rs:9:22 + | +LL | fn take_copy(_: impl Copy) {} + | ^^^^ required by this bound in `take_copy` +help: consider `await`ing on the `Future` + | +LL | take_copy(number.await); + | ++++++ + +error[E0277]: the trait bound `impl Future: Copy` is not satisfied + --> $DIR/suggest-await-on-future-for-trait-bound.rs:28:15 + | +LL | take_copy(string); + | --------- ^^^^^^ the trait `Copy` is not implemented for `impl Future` + | | + | required by a bound introduced by this call + | +note: required by a bound in `take_copy` + --> $DIR/suggest-await-on-future-for-trait-bound.rs:9:22 + | +LL | fn take_copy(_: impl Copy) {} + | ^^^^ required by this bound in `take_copy` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/async-await/suggest-await-on-future-in-format-macro.fixed b/tests/ui/async-await/suggest-await-on-future-in-format-macro.fixed new file mode 100644 index 0000000000000..1d2c8c57ad402 --- /dev/null +++ b/tests/ui/async-await/suggest-await-on-future-in-format-macro.fixed @@ -0,0 +1,33 @@ +//! Regression test for . +//! When a future is used inside a format macro like `println!("{number}")`, the +//! span is `{number}` (inside the string literal), so `.await` cannot be added +//! directly. Instead, suggest adding an intermediate `let number = number.await;`. +//@ edition:2021 +//@ run-rustfix + +#![crate_type = "lib"] + +async fn make_number() -> i32 { + 42 +} + +pub async fn println_number_display() { + let number = make_number(); + let number = number.await; + println!("{number}"); + //~^ ERROR `impl Future` doesn't implement `std::fmt::Display` +} + +pub async fn println_number_debug() { + let number = make_number(); + let number = number.await; + println!("{number:?}"); + //~^ ERROR `impl Future` doesn't implement `Debug` +} + +pub async fn println_number_display_format_spec() { + let number = make_number(); + let number = number.await; + println!("{number:.2}"); + //~^ ERROR `impl Future` doesn't implement `std::fmt::Display` +} diff --git a/tests/ui/async-await/suggest-await-on-future-in-format-macro.rs b/tests/ui/async-await/suggest-await-on-future-in-format-macro.rs new file mode 100644 index 0000000000000..3dc2bb8a45ea9 --- /dev/null +++ b/tests/ui/async-await/suggest-await-on-future-in-format-macro.rs @@ -0,0 +1,30 @@ +//! Regression test for . +//! When a future is used inside a format macro like `println!("{number}")`, the +//! span is `{number}` (inside the string literal), so `.await` cannot be added +//! directly. Instead, suggest adding an intermediate `let number = number.await;`. +//@ edition:2021 +//@ run-rustfix + +#![crate_type = "lib"] + +async fn make_number() -> i32 { + 42 +} + +pub async fn println_number_display() { + let number = make_number(); + println!("{number}"); + //~^ ERROR `impl Future` doesn't implement `std::fmt::Display` +} + +pub async fn println_number_debug() { + let number = make_number(); + println!("{number:?}"); + //~^ ERROR `impl Future` doesn't implement `Debug` +} + +pub async fn println_number_display_format_spec() { + let number = make_number(); + println!("{number:.2}"); + //~^ ERROR `impl Future` doesn't implement `std::fmt::Display` +} diff --git a/tests/ui/async-await/suggest-await-on-future-in-format-macro.stderr b/tests/ui/async-await/suggest-await-on-future-in-format-macro.stderr new file mode 100644 index 0000000000000..e8aaa20204a79 --- /dev/null +++ b/tests/ui/async-await/suggest-await-on-future-in-format-macro.stderr @@ -0,0 +1,44 @@ +error[E0277]: `impl Future` doesn't implement `std::fmt::Display` + --> $DIR/suggest-await-on-future-in-format-macro.rs:16:15 + | +LL | println!("{number}"); + | ^^^^^^^^ `impl Future` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `impl Future` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead +help: consider `await`ing on the `Future` + | +LL ~ let number = make_number(); +LL + let number = number.await; + | + +error[E0277]: `impl Future` doesn't implement `Debug` + --> $DIR/suggest-await-on-future-in-format-macro.rs:22:15 + | +LL | println!("{number:?}"); + | ^^^^^^^^^^ `impl Future` cannot be formatted using `{:?}` because it doesn't implement `Debug` + | + = help: the trait `Debug` is not implemented for `impl Future` +help: consider `await`ing on the `Future` + | +LL ~ let number = make_number(); +LL + let number = number.await; + | + +error[E0277]: `impl Future` doesn't implement `std::fmt::Display` + --> $DIR/suggest-await-on-future-in-format-macro.rs:28:15 + | +LL | println!("{number:.2}"); + | ^^^^^^^^^^^ `impl Future` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `impl Future` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead +help: consider `await`ing on the `Future` + | +LL ~ let number = make_number(); +LL + let number = number.await; + | + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`.