Ensure async trait impls are async (or otherwise return an opaque type)

As a workaround for the full `#[refine]` semantics not being implemented
yet, forbit returning a concrete future type like `Box<dyn Future>` or a
manually implemented Future.

`-> impl Future` is still permitted; while that can also cause
accidental refinement, that's behind a different feature gate
(`return_position_impl_trait_in_trait`) and that problem exists
regardless of whether the trait method is async, so will have to be
solved more generally.

Fixes #102745
This commit is contained in:
Dan Johnson 2022-11-02 17:45:08 -07:00
parent b70baa4f92
commit da98ef9a5d
12 changed files with 146 additions and 25 deletions

View File

@ -20,6 +20,10 @@ hir_analysis_lifetimes_or_bounds_mismatch_on_trait =
.where_label = this `where` clause might not match the one in the trait .where_label = this `where` clause might not match the one in the trait
.bounds_label = this bound might be missing in the impl .bounds_label = this bound might be missing in the impl
hir_analysis_async_trait_impl_should_be_async =
method `{$method_name}` should be async because the method from the trait is async
.trait_item_label = required because the trait method is async
hir_analysis_drop_impl_on_wrong_item = hir_analysis_drop_impl_on_wrong_item =
the `Drop` trait may only be implemented for local structs, enums, and unions the `Drop` trait may only be implemented for local structs, enums, and unions
.label = must be a struct, enum, or union in the current crate .label = must be a struct, enum, or union in the current crate

View File

@ -67,6 +67,10 @@ pub(crate) fn compare_impl_method<'tcx>(
return; return;
} }
if let Err(_) = compare_asyncness(tcx, impl_m, impl_m_span, trait_m, trait_item_span) {
return;
}
if let Err(_) = compare_predicate_entailment(tcx, impl_m, impl_m_span, trait_m, impl_trait_ref) if let Err(_) = compare_predicate_entailment(tcx, impl_m, impl_m_span, trait_m, impl_trait_ref)
{ {
return; return;
@ -323,6 +327,34 @@ fn compare_predicate_entailment<'tcx>(
Ok(()) Ok(())
} }
fn compare_asyncness<'tcx>(
tcx: TyCtxt<'tcx>,
impl_m: &ty::AssocItem,
impl_m_span: Span,
trait_m: &ty::AssocItem,
trait_item_span: Option<Span>,
) -> Result<(), ErrorGuaranteed> {
if tcx.asyncness(trait_m.def_id) == hir::IsAsync::Async {
match tcx.fn_sig(impl_m.def_id).skip_binder().output().kind() {
ty::Alias(ty::Opaque, ..) => {
// allow both `async fn foo()` and `fn foo() -> impl Future`
}
ty::Error(rustc_errors::ErrorGuaranteed { .. }) => {
// We don't know if it's ok, but at least it's already an error.
}
_ => {
return Err(tcx.sess.emit_err(crate::errors::AsyncTraitImplShouldBeAsync {
span: impl_m_span,
method_name: trait_m.name,
trait_item_span,
}));
}
};
}
Ok(())
}
#[instrument(skip(tcx), level = "debug", ret)] #[instrument(skip(tcx), level = "debug", ret)]
pub fn collect_trait_impl_trait_tys<'tcx>( pub fn collect_trait_impl_trait_tys<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,

View File

@ -51,6 +51,17 @@ pub struct LifetimesOrBoundsMismatchOnTrait {
pub ident: Ident, pub ident: Ident,
} }
#[derive(Diagnostic)]
#[diag(hir_analysis_async_trait_impl_should_be_async)]
pub struct AsyncTraitImplShouldBeAsync {
#[primary_span]
// #[label]
pub span: Span,
#[label(trait_item_label)]
pub trait_item_span: Option<Span>,
pub method_name: Symbol,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(hir_analysis_drop_impl_on_wrong_item, code = "E0120")] #[diag(hir_analysis_drop_impl_on_wrong_item, code = "E0120")]
pub struct DropImplOnWrongItem { pub struct DropImplOnWrongItem {

View File

@ -1,4 +1,3 @@
// check-pass
// edition: 2021 // edition: 2021
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
@ -13,11 +12,9 @@ trait MyTrait {
} }
impl MyTrait for i32 { impl MyTrait for i32 {
// This will break once a PR that implements #102745 is merged
fn foo(&self) -> Pin<Box<dyn Future<Output = i32> + '_>> { fn foo(&self) -> Pin<Box<dyn Future<Output = i32> + '_>> {
Box::pin(async { //~^ ERROR method `foo` should be async
*self Box::pin(async { *self })
})
} }
} }

View File

@ -0,0 +1,11 @@
error: method `foo` should be async because the method from the trait is async
--> $DIR/async-example-desugared-boxed.rs:15:5
|
LL | async fn foo(&self) -> i32;
| --------------------------- required because the trait method is async
...
LL | fn foo(&self) -> Pin<Box<dyn Future<Output = i32> + '_>> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,37 @@
// check-pass
// edition: 2021
#![feature(async_fn_in_trait)]
#![feature(return_position_impl_trait_in_trait)]
#![allow(incomplete_features)]
use std::future::Future;
use std::pin::Pin;
use std::task::Poll;
trait MyTrait {
async fn foo(&self) -> i32;
}
#[derive(Clone)]
struct MyFuture(i32);
impl Future for MyFuture {
type Output = i32;
fn poll(
self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> Poll<<Self as Future>::Output> {
Poll::Ready(self.0)
}
}
impl MyTrait for i32 {
// FIXME: this should eventually require `#[refine]` to compile, because it also provides
// `Clone`.
fn foo(&self) -> impl Future<Output = i32> + Clone {
MyFuture(*self)
}
}
fn main() {}

View File

@ -0,0 +1,29 @@
// edition: 2021
#![feature(async_fn_in_trait)]
#![feature(return_position_impl_trait_in_trait)]
#![allow(incomplete_features)]
use std::future::Future;
use std::task::Poll;
trait MyTrait {
async fn foo(&self) -> i32;
}
struct MyFuture;
impl Future for MyFuture {
type Output = i32;
fn poll(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll<Self::Output> {
Poll::Ready(0)
}
}
impl MyTrait for u32 {
fn foo(&self) -> MyFuture {
//~^ ERROR method `foo` should be async
MyFuture
}
}
fn main() {}

View File

@ -0,0 +1,11 @@
error: method `foo` should be async because the method from the trait is async
--> $DIR/async-example-desugared-manual.rs:23:5
|
LL | async fn foo(&self) -> i32;
| --------------------------- required because the trait method is async
...
LL | fn foo(&self) -> MyFuture {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -12,11 +12,8 @@ trait MyTrait {
} }
impl MyTrait for i32 { impl MyTrait for i32 {
// This will break once a PR that implements #102745 is merged
fn foo(&self) -> impl Future<Output = i32> + '_ { fn foo(&self) -> impl Future<Output = i32> + '_ {
async { async { *self }
*self
}
} }
} }

View File

@ -9,7 +9,7 @@ trait MyTrait {
impl MyTrait for i32 { impl MyTrait for i32 {
fn foo(&self) -> i32 { fn foo(&self) -> i32 {
//~^ ERROR: `i32` is not a future [E0277] //~^ ERROR: method `foo` should be async
*self *self
} }
} }

View File

@ -1,17 +1,11 @@
error[E0277]: `i32` is not a future error: method `foo` should be async because the method from the trait is async
--> $DIR/fn-not-async-err.rs:11:22 --> $DIR/fn-not-async-err.rs:11:5
|
LL | fn foo(&self) -> i32 {
| ^^^ `i32` is not a future
|
= help: the trait `Future` is not implemented for `i32`
= note: i32 must be a future or must implement `IntoFuture` to be awaited
note: required by a bound in `MyTrait::foo::{opaque#0}`
--> $DIR/fn-not-async-err.rs:7:28
| |
LL | async fn foo(&self) -> i32; LL | async fn foo(&self) -> i32;
| ^^^ required by this bound in `MyTrait::foo::{opaque#0}` | --------------------------- required because the trait method is async
...
LL | fn foo(&self) -> i32 {
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -12,9 +12,7 @@ trait MyTrait {
impl MyTrait for i32 { impl MyTrait for i32 {
fn foo(&self) -> impl Future<Output = i32> { fn foo(&self) -> impl Future<Output = i32> {
//~^ ERROR `impl Trait` only allowed in function and inherent method return types, not in `impl` method return [E0562] //~^ ERROR `impl Trait` only allowed in function and inherent method return types, not in `impl` method return [E0562]
async { async { *self }
*self
}
} }
} }