Skip to content

Commit 733a447

Browse files
Suggest desugaring to RPITIT when AFIT is required to be an auto trait
1 parent 0254fba commit 733a447

File tree

6 files changed

+214
-0
lines changed

6 files changed

+214
-0
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
986986
}
987987
}
988988

989+
self.suggest_desugaring_async_fn_in_trait(&mut err, trait_ref);
990+
989991
// Return early if the trait is Debug or Display and the invocation
990992
// originates within a standard library macro, because the output
991993
// is otherwise overwhelming and unhelpful (see #85844 for an

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

+124
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,12 @@ pub trait TypeErrCtxtExt<'tcx> {
406406
candidate_impls: &[ImplCandidate<'tcx>],
407407
span: Span,
408408
);
409+
410+
fn suggest_desugaring_async_fn_in_trait(
411+
&self,
412+
err: &mut Diagnostic,
413+
trait_ref: ty::PolyTraitRef<'tcx>,
414+
);
409415
}
410416

411417
fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
@@ -4027,6 +4033,124 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
40274033
}
40284034
}
40294035
}
4036+
4037+
fn suggest_desugaring_async_fn_in_trait(
4038+
&self,
4039+
err: &mut Diagnostic,
4040+
trait_ref: ty::PolyTraitRef<'tcx>,
4041+
) {
4042+
// Don't suggest if RTN is active -- we should prefer a where-clause bound instead.
4043+
if self.tcx.features().return_type_notation {
4044+
return;
4045+
}
4046+
4047+
let trait_def_id = trait_ref.def_id();
4048+
4049+
// Only suggest specifying auto traits
4050+
if !self.tcx.trait_is_auto(trait_def_id) {
4051+
return;
4052+
}
4053+
4054+
// Look for an RPITIT
4055+
let ty::Alias(ty::Projection, alias_ty) = trait_ref.self_ty().skip_binder().kind() else {
4056+
return;
4057+
};
4058+
let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id }) =
4059+
self.tcx.opt_rpitit_info(alias_ty.def_id)
4060+
else {
4061+
return;
4062+
};
4063+
4064+
// ... which is a local function
4065+
let Some(fn_def_id) = fn_def_id.as_local() else {
4066+
return;
4067+
};
4068+
let Some(hir::Node::TraitItem(item)) = self.tcx.hir().find_by_def_id(fn_def_id) else {
4069+
return;
4070+
};
4071+
4072+
// ... whose signature is `async` (i.e. this is an AFIT)
4073+
let (sig, body) = item.expect_fn();
4074+
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4075+
return;
4076+
};
4077+
let Ok(async_span) =
4078+
self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4079+
else {
4080+
return;
4081+
};
4082+
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
4083+
sig.decl.output
4084+
else {
4085+
// This should never happen, but let's not ICE.
4086+
return;
4087+
};
4088+
4089+
// Check that this is *not* a nested `impl Future` RPIT in an async fn
4090+
// (i.e. `async fn foo() -> impl Future`)
4091+
if def.owner_id.to_def_id() != opaque_def_id {
4092+
return;
4093+
}
4094+
4095+
let future = self.tcx.hir().item(*def).expect_opaque_ty();
4096+
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4097+
// `async fn` should always lower to a lang item bound... but don't ICE.
4098+
return;
4099+
};
4100+
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4101+
generics.bindings.get(0).map(|binding| binding.kind)
4102+
else {
4103+
// Also should never happen.
4104+
return;
4105+
};
4106+
4107+
let function_name = self.tcx.def_path_str(fn_def_id);
4108+
let auto_trait = self.tcx.def_path_str(trait_def_id);
4109+
4110+
let mut sugg = if future_output_ty.span.is_empty() {
4111+
vec![
4112+
(async_span, String::new()),
4113+
(
4114+
future_output_ty.span,
4115+
format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
4116+
),
4117+
]
4118+
} else {
4119+
vec![
4120+
(
4121+
future_output_ty.span.shrink_to_lo(),
4122+
"impl std::future::Future<Output = ".to_owned(),
4123+
),
4124+
(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
4125+
(async_span, String::new()),
4126+
]
4127+
};
4128+
4129+
// If there's a body, we also need to wrap it in `async {}`
4130+
if let hir::TraitFn::Provided(body) = body {
4131+
let body = self.tcx.hir().body(*body);
4132+
let body_span = body.value.span;
4133+
let body_span_without_braces =
4134+
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4135+
if body_span_without_braces.is_empty() {
4136+
sugg.push((body_span_without_braces, " async {} ".to_owned()));
4137+
} else {
4138+
sugg.extend([
4139+
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4140+
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4141+
]);
4142+
}
4143+
}
4144+
4145+
err.multipart_suggestion(
4146+
format!(
4147+
"`{auto_trait}` can be made part of the associated future's \
4148+
guarantees for all implementations of `{function_name}`"
4149+
),
4150+
sugg,
4151+
Applicability::MachineApplicable,
4152+
);
4153+
}
40304154
}
40314155

40324156
/// Add a hint to add a missing borrow or remove an unnecessary one.

tests/ui/async-await/in-trait/missing-send-bound.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ note: required by a bound in `assert_is_send`
1515
|
1616
LL | fn assert_is_send(_: impl Send) {}
1717
| ^^^^ required by this bound in `assert_is_send`
18+
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::bar`
19+
|
20+
LL - async fn bar();
21+
LL + fn bar() -> impl std::future::Future<Output = ()> + Send;
22+
|
1823

1924
error: aborting due to previous error
2025

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// run-rustfix
2+
// edition: 2021
3+
4+
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
5+
#![allow(unused)]
6+
7+
trait Foo {
8+
fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
9+
fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
10+
}
11+
12+
fn bar<T: Foo>() {
13+
fn needs_send(_: impl Send) {}
14+
needs_send(T::test());
15+
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
16+
needs_send(T::test2());
17+
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
18+
}
19+
20+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// run-rustfix
2+
// edition: 2021
3+
4+
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
5+
#![allow(unused)]
6+
7+
trait Foo {
8+
async fn test() -> () {}
9+
async fn test2() -> i32 { 1 + 2 }
10+
}
11+
12+
fn bar<T: Foo>() {
13+
fn needs_send(_: impl Send) {}
14+
needs_send(T::test());
15+
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
16+
needs_send(T::test2());
17+
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
18+
}
19+
20+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
2+
--> $DIR/send-on-async-fn-in-trait.rs:14:16
3+
|
4+
LL | needs_send(T::test());
5+
| ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
6+
| |
7+
| required by a bound introduced by this call
8+
|
9+
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
10+
note: required by a bound in `needs_send`
11+
--> $DIR/send-on-async-fn-in-trait.rs:13:27
12+
|
13+
LL | fn needs_send(_: impl Send) {}
14+
| ^^^^ required by this bound in `needs_send`
15+
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test`
16+
|
17+
LL - async fn test() -> () {}
18+
LL + fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
19+
|
20+
21+
error[E0277]: `impl Future<Output = i32>` cannot be sent between threads safely
22+
--> $DIR/send-on-async-fn-in-trait.rs:16:16
23+
|
24+
LL | needs_send(T::test2());
25+
| ---------- ^^^^^^^^^^ `impl Future<Output = i32>` cannot be sent between threads safely
26+
| |
27+
| required by a bound introduced by this call
28+
|
29+
= help: the trait `Send` is not implemented for `impl Future<Output = i32>`
30+
note: required by a bound in `needs_send`
31+
--> $DIR/send-on-async-fn-in-trait.rs:13:27
32+
|
33+
LL | fn needs_send(_: impl Send) {}
34+
| ^^^^ required by this bound in `needs_send`
35+
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test2`
36+
|
37+
LL - async fn test2() -> i32 { 1 + 2 }
38+
LL + fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
39+
|
40+
41+
error: aborting due to 2 previous errors
42+
43+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)