From da98ef9a5d0b8a4fb90e1d845506880fae9e7352 Mon Sep 17 00:00:00 2001 From: Dan Johnson Date: Wed, 2 Nov 2022 17:45:08 -0700 Subject: [PATCH] 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` 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 --- .../locales/en-US/hir_analysis.ftl | 4 ++ .../src/check/compare_method.rs | 32 ++++++++++++++++ compiler/rustc_hir_analysis/src/errors.rs | 11 ++++++ .../in-trait/async-example-desugared-boxed.rs | 7 +--- .../async-example-desugared-boxed.stderr | 11 ++++++ .../in-trait/async-example-desugared-extra.rs | 37 +++++++++++++++++++ .../async-example-desugared-manual.rs | 29 +++++++++++++++ .../async-example-desugared-manual.stderr | 11 ++++++ .../in-trait/async-example-desugared.rs | 5 +-- .../async-await/in-trait/fn-not-async-err.rs | 2 +- .../in-trait/fn-not-async-err.stderr | 18 +++------ .../async-await/in-trait/fn-not-async-err2.rs | 4 +- 12 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 src/test/ui/async-await/in-trait/async-example-desugared-boxed.stderr create mode 100644 src/test/ui/async-await/in-trait/async-example-desugared-extra.rs create mode 100644 src/test/ui/async-await/in-trait/async-example-desugared-manual.rs create mode 100644 src/test/ui/async-await/in-trait/async-example-desugared-manual.stderr diff --git a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl index e33323a779536..26cdf8a58f3fb 100644 --- a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl +++ b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl @@ -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 diff --git a/compiler/rustc_hir_analysis/src/check/compare_method.rs b/compiler/rustc_hir_analysis/src/check/compare_method.rs index ba7d31cea2e2f..bfa37c05a194f 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_method.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_method.rs @@ -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; @@ -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, +) -> 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>, diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index d9697c63c56e1..d383fcacb3a9c 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -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, + pub method_name: Symbol, +} + #[derive(Diagnostic)] #[diag(hir_analysis_drop_impl_on_wrong_item, code = "E0120")] pub struct DropImplOnWrongItem { diff --git a/src/test/ui/async-await/in-trait/async-example-desugared-boxed.rs b/src/test/ui/async-await/in-trait/async-example-desugared-boxed.rs index 61d7e2520eab7..1b1b3cffd58f3 100644 --- a/src/test/ui/async-await/in-trait/async-example-desugared-boxed.rs +++ b/src/test/ui/async-await/in-trait/async-example-desugared-boxed.rs @@ -1,4 +1,3 @@ -// check-pass // edition: 2021 #![feature(async_fn_in_trait)] @@ -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::pin(async { - *self - }) + //~^ ERROR method `foo` should be async + Box::pin(async { *self }) } } diff --git a/src/test/ui/async-await/in-trait/async-example-desugared-boxed.stderr b/src/test/ui/async-await/in-trait/async-example-desugared-boxed.stderr new file mode 100644 index 0000000000000..60fa534a64f02 --- /dev/null +++ b/src/test/ui/async-await/in-trait/async-example-desugared-boxed.stderr @@ -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 + '_>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/test/ui/async-await/in-trait/async-example-desugared-extra.rs b/src/test/ui/async-await/in-trait/async-example-desugared-extra.rs new file mode 100644 index 0000000000000..81e1e59a36249 --- /dev/null +++ b/src/test/ui/async-await/in-trait/async-example-desugared-extra.rs @@ -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<::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 + Clone { + MyFuture(*self) + } +} + +fn main() {} diff --git a/src/test/ui/async-await/in-trait/async-example-desugared-manual.rs b/src/test/ui/async-await/in-trait/async-example-desugared-manual.rs new file mode 100644 index 0000000000000..71473e7455fd6 --- /dev/null +++ b/src/test/ui/async-await/in-trait/async-example-desugared-manual.rs @@ -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 { + Poll::Ready(0) + } +} + +impl MyTrait for u32 { + fn foo(&self) -> MyFuture { + //~^ ERROR method `foo` should be async + MyFuture + } +} + +fn main() {} diff --git a/src/test/ui/async-await/in-trait/async-example-desugared-manual.stderr b/src/test/ui/async-await/in-trait/async-example-desugared-manual.stderr new file mode 100644 index 0000000000000..567a36a86d191 --- /dev/null +++ b/src/test/ui/async-await/in-trait/async-example-desugared-manual.stderr @@ -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 + diff --git a/src/test/ui/async-await/in-trait/async-example-desugared.rs b/src/test/ui/async-await/in-trait/async-example-desugared.rs index 1313c9edd861c..fb92ec786746f 100644 --- a/src/test/ui/async-await/in-trait/async-example-desugared.rs +++ b/src/test/ui/async-await/in-trait/async-example-desugared.rs @@ -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 + '_ { - async { - *self - } + async { *self } } } diff --git a/src/test/ui/async-await/in-trait/fn-not-async-err.rs b/src/test/ui/async-await/in-trait/fn-not-async-err.rs index f94d32145a290..9598d53bce8b2 100644 --- a/src/test/ui/async-await/in-trait/fn-not-async-err.rs +++ b/src/test/ui/async-await/in-trait/fn-not-async-err.rs @@ -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 } } diff --git a/src/test/ui/async-await/in-trait/fn-not-async-err.stderr b/src/test/ui/async-await/in-trait/fn-not-async-err.stderr index 03321dc5b5af1..579801d0f3975 100644 --- a/src/test/ui/async-await/in-trait/fn-not-async-err.stderr +++ b/src/test/ui/async-await/in-trait/fn-not-async-err.stderr @@ -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`. diff --git a/src/test/ui/async-await/in-trait/fn-not-async-err2.rs b/src/test/ui/async-await/in-trait/fn-not-async-err2.rs index 594baa91ad8ba..2c4ed5535801e 100644 --- a/src/test/ui/async-await/in-trait/fn-not-async-err2.rs +++ b/src/test/ui/async-await/in-trait/fn-not-async-err2.rs @@ -12,9 +12,7 @@ trait MyTrait { impl MyTrait for i32 { fn foo(&self) -> impl Future { //~^ ERROR `impl Trait` only allowed in function and inherent method return types, not in `impl` method return [E0562] - async { - *self - } + async { *self } } }