Skip to content

Commit

Permalink
Ensure async trait impls are async (or otherwise return an opaque type)
Browse files Browse the repository at this point in the history
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 rust-lang#102745
  • Loading branch information
ComputerDruid committed Dec 15, 2022
1 parent b70baa4 commit da98ef9
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 25 deletions.
4 changes: 4 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
.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 =
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
Expand Down
32 changes: 32 additions & 0 deletions compiler/rustc_hir_analysis/src/check/compare_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ pub(crate) fn compare_impl_method<'tcx>(
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)
{
return;
Expand Down Expand Up @@ -323,6 +327,34 @@ fn compare_predicate_entailment<'tcx>(
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)]
pub fn collect_trait_impl_trait_tys<'tcx>(
tcx: TyCtxt<'tcx>,
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ pub struct LifetimesOrBoundsMismatchOnTrait {
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)]
#[diag(hir_analysis_drop_impl_on_wrong_item, code = "E0120")]
pub struct DropImplOnWrongItem {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// check-pass
// edition: 2021

#![feature(async_fn_in_trait)]
Expand All @@ -13,11 +12,9 @@ trait MyTrait {
}

impl MyTrait for i32 {
// This will break once a PR that implements #102745 is merged
fn foo(&self) -> Pin<Box<dyn Future<Output = i32> + '_>> {
Box::pin(async {
*self
})
//~^ ERROR method `foo` should be async
Box::pin(async { *self })
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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

37 changes: 37 additions & 0 deletions src/test/ui/async-await/in-trait/async-example-desugared-extra.rs
Original file line number Diff line number Diff line change
@@ -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() {}
29 changes: 29 additions & 0 deletions src/test/ui/async-await/in-trait/async-example-desugared-manual.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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

5 changes: 1 addition & 4 deletions src/test/ui/async-await/in-trait/async-example-desugared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ trait MyTrait {
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/async-await/in-trait/fn-not-async-err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trait MyTrait {

impl MyTrait for i32 {
fn foo(&self) -> i32 {
//~^ ERROR: `i32` is not a future [E0277]
//~^ ERROR: method `foo` should be async
*self
}
}
Expand Down
18 changes: 6 additions & 12 deletions src/test/ui/async-await/in-trait/fn-not-async-err.stderr
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
error[E0277]: `i32` is not a future
--> $DIR/fn-not-async-err.rs:11:22
|
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
error: method `foo` should be async because the method from the trait is async
--> $DIR/fn-not-async-err.rs:11:5
|
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

For more information about this error, try `rustc --explain E0277`.
4 changes: 1 addition & 3 deletions src/test/ui/async-await/in-trait/fn-not-async-err2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ trait MyTrait {
impl MyTrait for 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]
async {
*self
}
async { *self }
}
}

Expand Down

0 comments on commit da98ef9

Please sign in to comment.