Suggest desugaring to RPITIT when AFIT is required to be an auto trait
This commit is contained in:
parent
087a571e70
commit
9072415252
@ -987,6 +987,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||
}
|
||||
|
||||
self.explain_hrtb_projection(&mut err, trait_predicate, obligation.param_env, &obligation.cause);
|
||||
self.suggest_desugaring_async_fn_in_trait(&mut err, trait_ref);
|
||||
|
||||
// Return early if the trait is Debug or Display and the invocation
|
||||
// originates within a standard library macro, because the output
|
||||
|
@ -414,6 +414,12 @@ pub trait TypeErrCtxtExt<'tcx> {
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
);
|
||||
|
||||
fn suggest_desugaring_async_fn_in_trait(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
);
|
||||
}
|
||||
|
||||
fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
|
||||
@ -4100,6 +4106,136 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_desugaring_async_fn_in_trait(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) {
|
||||
// Don't suggest if RTN is active -- we should prefer a where-clause bound instead.
|
||||
if self.tcx.features().return_type_notation {
|
||||
return;
|
||||
}
|
||||
|
||||
let trait_def_id = trait_ref.def_id();
|
||||
|
||||
// Only suggest specifying auto traits
|
||||
if !self.tcx.trait_is_auto(trait_def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for an RPITIT
|
||||
let ty::Alias(ty::Projection, alias_ty) = trait_ref.self_ty().skip_binder().kind() else {
|
||||
return;
|
||||
};
|
||||
let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, opaque_def_id }) =
|
||||
self.tcx.opt_rpitit_info(alias_ty.def_id)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let auto_trait = self.tcx.def_path_str(trait_def_id);
|
||||
// ... which is a local function
|
||||
let Some(fn_def_id) = fn_def_id.as_local() else {
|
||||
// If it's not local, we can at least mention that the method is async, if it is.
|
||||
if self.tcx.asyncness(fn_def_id).is_async() {
|
||||
err.span_note(
|
||||
self.tcx.def_span(fn_def_id),
|
||||
format!(
|
||||
"`{}::{}` is an `async fn` in trait, which does not \
|
||||
automatically imply that its future is `{auto_trait}`",
|
||||
alias_ty.trait_ref(self.tcx),
|
||||
self.tcx.item_name(fn_def_id)
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let Some(hir::Node::TraitItem(item)) = self.tcx.hir().find_by_def_id(fn_def_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// ... whose signature is `async` (i.e. this is an AFIT)
|
||||
let (sig, body) = item.expect_fn();
|
||||
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
|
||||
return;
|
||||
};
|
||||
let Ok(async_span) =
|
||||
self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
|
||||
sig.decl.output
|
||||
else {
|
||||
// This should never happen, but let's not ICE.
|
||||
return;
|
||||
};
|
||||
|
||||
// Check that this is *not* a nested `impl Future` RPIT in an async fn
|
||||
// (i.e. `async fn foo() -> impl Future`)
|
||||
if def.owner_id.to_def_id() != opaque_def_id {
|
||||
return;
|
||||
}
|
||||
|
||||
let future = self.tcx.hir().item(*def).expect_opaque_ty();
|
||||
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
|
||||
// `async fn` should always lower to a lang item bound... but don't ICE.
|
||||
return;
|
||||
};
|
||||
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
|
||||
generics.bindings.get(0).map(|binding| binding.kind)
|
||||
else {
|
||||
// Also should never happen.
|
||||
return;
|
||||
};
|
||||
|
||||
let function_name = self.tcx.def_path_str(fn_def_id);
|
||||
|
||||
let mut sugg = if future_output_ty.span.is_empty() {
|
||||
vec![
|
||||
(async_span, String::new()),
|
||||
(
|
||||
future_output_ty.span,
|
||||
format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
|
||||
),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
(
|
||||
future_output_ty.span.shrink_to_lo(),
|
||||
"impl std::future::Future<Output = ".to_owned(),
|
||||
),
|
||||
(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
|
||||
(async_span, String::new()),
|
||||
]
|
||||
};
|
||||
|
||||
// If there's a body, we also need to wrap it in `async {}`
|
||||
if let hir::TraitFn::Provided(body) = body {
|
||||
let body = self.tcx.hir().body(*body);
|
||||
let body_span = body.value.span;
|
||||
let body_span_without_braces =
|
||||
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
|
||||
if body_span_without_braces.is_empty() {
|
||||
sugg.push((body_span_without_braces, " async {} ".to_owned()));
|
||||
} else {
|
||||
sugg.extend([
|
||||
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
|
||||
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
err.multipart_suggestion(
|
||||
format!(
|
||||
"`{auto_trait}` can be made part of the associated future's \
|
||||
guarantees for all implementations of `{function_name}`"
|
||||
),
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a hint to add a missing borrow or remove an unnecessary one.
|
||||
|
@ -0,0 +1,7 @@
|
||||
// edition:2021
|
||||
|
||||
#![feature(async_fn_in_trait)]
|
||||
|
||||
pub trait Foo {
|
||||
async fn test();
|
||||
}
|
@ -15,6 +15,11 @@ note: required by a bound in `assert_is_send`
|
||||
|
|
||||
LL | fn assert_is_send(_: impl Send) {}
|
||||
| ^^^^ required by this bound in `assert_is_send`
|
||||
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::bar`
|
||||
|
|
||||
LL - async fn bar();
|
||||
LL + fn bar() -> impl std::future::Future<Output = ()> + Send;
|
||||
|
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
// run-rustfix
|
||||
// edition: 2021
|
||||
|
||||
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
|
||||
#![allow(unused)]
|
||||
|
||||
trait Foo {
|
||||
fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
|
||||
fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
|
||||
}
|
||||
|
||||
fn bar<T: Foo>() {
|
||||
fn needs_send(_: impl Send) {}
|
||||
needs_send(T::test());
|
||||
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
needs_send(T::test2());
|
||||
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
|
||||
}
|
||||
|
||||
fn main() {}
|
20
tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs
Normal file
20
tests/ui/async-await/in-trait/send-on-async-fn-in-trait.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// run-rustfix
|
||||
// edition: 2021
|
||||
|
||||
#![feature(async_fn_in_trait, return_position_impl_trait_in_trait)]
|
||||
#![allow(unused)]
|
||||
|
||||
trait Foo {
|
||||
async fn test() -> () {}
|
||||
async fn test2() -> i32 { 1 + 2 }
|
||||
}
|
||||
|
||||
fn bar<T: Foo>() {
|
||||
fn needs_send(_: impl Send) {}
|
||||
needs_send(T::test());
|
||||
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
needs_send(T::test2());
|
||||
//~^ ERROR `impl Future<Output = i32>` cannot be sent between threads safely
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,43 @@
|
||||
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
--> $DIR/send-on-async-fn-in-trait.rs:14:16
|
||||
|
|
||||
LL | needs_send(T::test());
|
||||
| ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
|
||||
note: required by a bound in `needs_send`
|
||||
--> $DIR/send-on-async-fn-in-trait.rs:13:27
|
||||
|
|
||||
LL | fn needs_send(_: impl Send) {}
|
||||
| ^^^^ required by this bound in `needs_send`
|
||||
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test`
|
||||
|
|
||||
LL - async fn test() -> () {}
|
||||
LL + fn test() -> impl std::future::Future<Output = ()> + Send { async {} }
|
||||
|
|
||||
|
||||
error[E0277]: `impl Future<Output = i32>` cannot be sent between threads safely
|
||||
--> $DIR/send-on-async-fn-in-trait.rs:16:16
|
||||
|
|
||||
LL | needs_send(T::test2());
|
||||
| ---------- ^^^^^^^^^^ `impl Future<Output = i32>` cannot be sent between threads safely
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Send` is not implemented for `impl Future<Output = i32>`
|
||||
note: required by a bound in `needs_send`
|
||||
--> $DIR/send-on-async-fn-in-trait.rs:13:27
|
||||
|
|
||||
LL | fn needs_send(_: impl Send) {}
|
||||
| ^^^^ required by this bound in `needs_send`
|
||||
help: `Send` can be made part of the associated future's guarantees for all implementations of `Foo::test2`
|
||||
|
|
||||
LL - async fn test2() -> i32 { 1 + 2 }
|
||||
LL + fn test2() -> impl std::future::Future<Output = i32> + Send {async { 1 + 2 } }
|
||||
|
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
@ -0,0 +1,15 @@
|
||||
// aux-build:foreign-async-fn.rs
|
||||
// edition:2021
|
||||
|
||||
#![feature(async_fn_in_trait)]
|
||||
|
||||
extern crate foreign_async_fn;
|
||||
use foreign_async_fn::Foo;
|
||||
|
||||
fn bar<T: Foo>() {
|
||||
fn needs_send(_: impl Send) {}
|
||||
needs_send(T::test());
|
||||
//~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,23 @@
|
||||
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
--> $DIR/send-on-foreign-async-fn-in-trait.rs:11:16
|
||||
|
|
||||
LL | needs_send(T::test());
|
||||
| ---------- ^^^^^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
|
||||
note: `<T as Foo>::test` is an `async fn` in trait, which does not automatically imply that its future is `Send`
|
||||
--> $DIR/auxiliary/foreign-async-fn.rs:6:5
|
||||
|
|
||||
LL | async fn test();
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
note: required by a bound in `needs_send`
|
||||
--> $DIR/send-on-foreign-async-fn-in-trait.rs:10:27
|
||||
|
|
||||
LL | fn needs_send(_: impl Send) {}
|
||||
| ^^^^ required by this bound in `needs_send`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
Loading…
x
Reference in New Issue
Block a user