Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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