Skip to content

Commit 9072415

Browse files
Suggest desugaring to RPITIT when AFIT is required to be an auto trait
1 parent 087a571 commit 9072415

9 files changed

+270
-0
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
987987
}
988988

989989
self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause);
990+
self.suggest_desugaring_async_fn_in_trait(&mut err, trait_ref);
990991

991992
// Return early if the trait is Debug or Display and the invocation
992993
// originates within a standard library macro, because the output

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

+136
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ pub trait TypeErrCtxtExt<'tcx> {
414414
param_env: ty::ParamEnv<'tcx>,
415415
cause: &ObligationCause<'tcx>,
416416
);
417+
418+
fn suggest_desugaring_async_fn_in_trait(
419+
&self,
420+
err: &mut Diagnostic,
421+
trait_ref: ty::PolyTraitRef<'tcx>,
422+
);
417423
}
418424

419425
fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
@@ -4100,6 +4106,136 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
41004106
});
41014107
}
41024108
}
4109+
4110+
fn suggest_desugaring_async_fn_in_trait(
4111+
&self,
4112+
err: &mut Diagnostic,
4113+
trait_ref: ty::PolyTraitRef<'tcx>,
4114+
) {
4115+
// Don't suggest if RTN is active -- we should prefer a where-clause bound instead.
4116+
if self.tcx.features().return_type_notation {
4117+
return;
4118+
}
4119+
4120+
let trait_def_id = trait_ref.def_id();
4121+
4122+
// Only suggest specifying auto traits
4123+
if !self.tcx.trait_is_auto(trait_def_id) {
4124+
return;
4125+
}
4126+
4127+
// Look for an RPITIT
4128+
let ty::Alias(ty::Projection, alias_ty) = trait_ref.self_ty().skip_binder().kind() else {
4129+
return;
4130+
};
4131+
let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id }) =
4132+
self.tcx.opt_rpitit_info(alias_ty.def_id)
4133+
else {
4134+
return;
4135+
};
4136+
4137+
let auto_trait = self.tcx.def_path_str(trait_def_id);
4138+
// ... which is a local function
4139+
let Some(fn_def_id) = fn_def_id.as_local() else {
4140+
// If it's not local, we can at least mention that the method is async, if it is.
4141+
if self.tcx.asyncness(fn_def_id).is_async() {
4142+
err.span_note(
4143+
self.tcx.def_span(fn_def_id),
4144+
format!(
4145+
"`{}::{}` is an `async fn` in trait, which does not \
4146+
automatically imply that its future is `{auto_trait}`",
4147+
alias_ty.trait_ref(self.tcx),
4148+
self.tcx.item_name(fn_def_id)
4149+
),
4150+
);
4151+
}
4152+
return;
4153+
};
4154+
let Some(hir::Node::TraitItem(item)) = self.tcx.hir().find_by_def_id(fn_def_id) else {
4155+
return;
4156+
};
4157+
4158+
// ... whose signature is `async` (i.e. this is an AFIT)
4159+
let (sig, body) = item.expect_fn();
4160+
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4161+
return;
4162+
};
4163+
let Ok(async_span) =
4164+
self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4165+
else {
4166+
return;
4167+
};
4168+
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
4169+
sig.decl.output
4170+
else {
4171+
// This should never happen, but let's not ICE.
4172+
return;
4173+
};
4174+
4175+
// Check that this is *not* a nested `impl Future` RPIT in an async fn
4176+
// (i.e. `async fn foo() -> impl Future`)
4177+
if def.owner_id.to_def_id() != opaque_def_id {
4178+
return;
4179+
}
4180+
4181+
let future = self.tcx.hir().item(*def).expect_opaque_ty();
4182+
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4183+
// `async fn` should always lower to a lang item bound... but don't ICE.
4184+
return;
4185+
};
4186+
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4187+
generics.bindings.get(0).map(|binding| binding.kind)
4188+
else {
4189+
// Also should never happen.
4190+
return;
4191+
};
4192+
4193+
let function_name = self.tcx.def_path_str(fn_def_id);
4194+
4195+
let mut sugg = if future_output_ty.span.is_empty() {
4196+
vec![
4197+
(async_span, String::new()),
4198+
(
4199+
future_output_ty.span,
4200+
format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
4201+
),
4202+
]
4203+
} else {
4204+
vec![
4205+
(
4206+
future_output_ty.span.shrink_to_lo(),
4207+
"impl std::future::Future<Output = ".to_owned(),
4208+
),
4209+
(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
4210+
(async_span, String::new()),
4211+
]
4212+
};
4213+
4214+
// If there's a body, we also need to wrap it in `async {}`
4215+
if let hir::TraitFn::Provided(body) = body {
4216+
let body = self.tcx.hir().body(*body);
4217+
let body_span = body.value.span;
4218+
let body_span_without_braces =
4219+
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4220+
if body_span_without_braces.is_empty() {
4221+
sugg.push((body_span_without_braces, " async {} ".to_owned()));
4222+
} else {
4223+
sugg.extend([
4224+
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4225+
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4226+
]);
4227+
}
4228+
}
4229+
4230+
err.multipart_suggestion(
4231+
format!(
4232+
"`{auto_trait}` can be made part of the associated future's \
4233+
guarantees for all implementations of `{function_name}`"
4234+
),
4235+
sugg,
4236+
Applicability::MachineApplicable,
4237+
);
4238+
}
41034239
}
41044240

41054241
/// Add a hint to add a missing borrow or remove an unnecessary one.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// edition:2021
2+
3+
#![feature(async_fn_in_trait)]
4+
5+
pub trait Foo {
6+
async fn test();
7+
}

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`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// aux-build:foreign-async-fn.rs
2+
// edition:2021
3+
4+
#![feature(async_fn_in_trait)]
5+
6+
extern crate foreign_async_fn;
7+
use foreign_async_fn::Foo;
8+
9+
fn bar<T: Foo>() {
10+
fn needs_send(_: impl Send) {}
11+
needs_send(T::test());
12+
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
13+
}
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
2+
--> $DIR/send-on-foreign-async-fn-in-trait.rs:11: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: `<T as Foo>::test` is an `async fn` in trait, which does not automatically imply that its future is `Send`
11+
--> $DIR/auxiliary/foreign-async-fn.rs:6:5
12+
|
13+
LL | async fn test();
14+
| ^^^^^^^^^^^^^^^^
15+
note: required by a bound in `needs_send`
16+
--> $DIR/send-on-foreign-async-fn-in-trait.rs:10:27
17+
|
18+
LL | fn needs_send(_: impl Send) {}
19+
| ^^^^ required by this bound in `needs_send`
20+
21+
error: aborting due to previous error
22+
23+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)