From 4a5667326ff9bd4d70ddea4184887f17c375d125 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 18 Jan 2023 16:08:33 -0800 Subject: [PATCH 1/5] Implement #[async_send] attribute for async fns This gives a potential solution for the spawning from generics issue with async functions in traits. The goal here is to make something that users can experiment with and provide feedback on, but this is not intended to be the final syntax or even final design. --- compiler/rustc_ast_lowering/src/lib.rs | 28 ++++++++++++++++++++- compiler/rustc_feature/src/builtin_attrs.rs | 4 +++ compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_span/src/symbol.rs | 2 ++ library/core/src/marker.rs | 1 + 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index bc6d2cf12c78a..fe432a21ac70c 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1801,6 +1801,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::Async, span, None); let fn_def_id = self.local_def_id(fn_node_id); + let fn_local_id = self.lower_node_id(fn_node_id).local_id; let opaque_ty_def_id = self.create_def(fn_def_id, opaque_ty_node_id, DefPathData::ImplTrait, opaque_ty_span); @@ -1967,6 +1968,31 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { )); debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); + let is_async_send = this.tcx.features().async_fn_in_trait + && (true + || this.attrs.get(&fn_local_id).map_or(false, |attrs| { + attrs.into_iter().any(|attr| attr.has_name(sym::async_send)) + })); + + // Add Send bound if `#[async_send]` attribute is present + let bounds = if is_async_send { + let send_bound = hir::GenericBound::LangItemTrait( + hir::LangItem::Send, + this.lower_span(span), + this.next_id(), + this.arena.alloc(hir::GenericArgs { + args: &[], + bindings: &[], + parenthesized: false, + span_ext: DUMMY_SP, + }), + ); + + arena_vec![this; future_bound, send_bound] + } else { + arena_vec![this; future_bound] + }; + let opaque_ty_item = hir::OpaqueTy { generics: this.arena.alloc(hir::Generics { params: generic_params, @@ -1975,7 +2001,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { where_clause_span: this.lower_span(span), span: this.lower_span(span), }), - bounds: arena_vec![this; future_bound], + bounds, origin: hir::OpaqueTyOrigin::AsyncFn(fn_def_id), in_trait, }; diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index af56a0b245987..df017b095773f 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -490,6 +490,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // RFC 2397 gated!(do_not_recommend, Normal, template!(Word), WarnFollowing, experimental!(do_not_recommend)), + gated!(async_send, Normal, template!(Word), ErrorFollowing, async_fn_in_trait, + "`async_send` is a temporary placeholder for marking async methods as returning a future \ + that is also Send and may be removed or renamed in the future."), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 3474fab34f00b..332c3d2aaf8aa 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -216,6 +216,7 @@ language_item_table! { FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; Future, sym::future_trait, future_trait, Target::Trait, GenericRequirement::Exact(0); + Send, sym::send, send_trait, Target::Trait, GenericRequirement::Exact(0); GeneratorState, sym::generator_state, gen_state, Target::Enum, GenericRequirement::None; Generator, sym::generator, gen_trait, Target::Trait, GenericRequirement::Minimum(1); Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 706002f79b1fb..d266337cd2a1a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -393,6 +393,7 @@ symbols! { async_await, async_closure, async_fn_in_trait, + async_send, atomic, atomic_mod, atomics, @@ -1297,6 +1298,7 @@ symbols! { self_in_typedefs, self_struct_ctor, semitransparent, + send, shadow_call_stack, shl, shl_assign, diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 1326fc9ab096f..8d09136ec3d0f 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -31,6 +31,7 @@ use crate::hash::Hasher; /// [ub]: ../../reference/behavior-considered-undefined.html #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "Send")] +#[cfg_attr(not(bootstrap), lang = "send")] #[rustc_on_unimplemented( message = "`{Self}` cannot be sent between threads safely", label = "`{Self}` cannot be sent between threads safely" From 06851def454f26164893d7ae06083ee13af06ffd Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 18 Jan 2023 16:16:46 -0800 Subject: [PATCH 2/5] Add test for #[async_send] functionality --- tests/ui/async-await/in-trait/send-bound.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/ui/async-await/in-trait/send-bound.rs diff --git a/tests/ui/async-await/in-trait/send-bound.rs b/tests/ui/async-await/in-trait/send-bound.rs new file mode 100644 index 0000000000000..ac473b001bf55 --- /dev/null +++ b/tests/ui/async-await/in-trait/send-bound.rs @@ -0,0 +1,18 @@ +// check-pass +// edition: 2021 + +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +trait MyTrait { + #[async_send] + async fn foo(&self) -> usize; +} + +fn assert_send(_: T) {} + +fn use_trait(x: T) { + assert_send(x.foo()) +} + +fn main() {} From cabd06467d67192e9f5803c753aa31bc7f7bb919 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 18 Jan 2023 16:37:26 -0800 Subject: [PATCH 3/5] Add a test the ensure the #[async_send] attribute is feature gated --- compiler/rustc_ast_lowering/src/lib.rs | 8 ++--- .../async-await/in-trait/send-bound-gate.rs | 14 ++++++++ .../in-trait/send-bound-gate.stderr | 35 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/ui/async-await/in-trait/send-bound-gate.rs create mode 100644 tests/ui/async-await/in-trait/send-bound-gate.stderr diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index fe432a21ac70c..049e113f1413d 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1968,11 +1968,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { )); debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); - let is_async_send = this.tcx.features().async_fn_in_trait - && (true - || this.attrs.get(&fn_local_id).map_or(false, |attrs| { - attrs.into_iter().any(|attr| attr.has_name(sym::async_send)) - })); + let is_async_send = this.attrs.get(&fn_local_id).map_or(false, |attrs| { + attrs.into_iter().any(|attr| attr.has_name(sym::async_send)) + }); // Add Send bound if `#[async_send]` attribute is present let bounds = if is_async_send { diff --git a/tests/ui/async-await/in-trait/send-bound-gate.rs b/tests/ui/async-await/in-trait/send-bound-gate.rs new file mode 100644 index 0000000000000..8fed247cf7480 --- /dev/null +++ b/tests/ui/async-await/in-trait/send-bound-gate.rs @@ -0,0 +1,14 @@ +// Make sure the #[async_send] attribute requires the async_fn_in_trait feature + +// edition: 2021 + +trait MyTrait { + #[async_send] //~ `async_send` is a temporary placeholder + async fn foo(&self) -> usize; + //~^ functions in traits cannot be declared `async` +} + +#[async_send] //~ `async_send` is a temporary placeholder +async fn bar() {} + +fn main() {} diff --git a/tests/ui/async-await/in-trait/send-bound-gate.stderr b/tests/ui/async-await/in-trait/send-bound-gate.stderr new file mode 100644 index 0000000000000..b81956cbaa5e4 --- /dev/null +++ b/tests/ui/async-await/in-trait/send-bound-gate.stderr @@ -0,0 +1,35 @@ +error[E0658]: `async_send` is a temporary placeholder for marking async methods as returning a future that is also Send and may be removed or renamed in the future. + --> $DIR/send-bound-gate.rs:6:5 + | +LL | #[async_send] + | ^^^^^^^^^^^^^ + | + = note: see issue #91611 for more information + = help: add `#![feature(async_fn_in_trait)]` to the crate attributes to enable + +error[E0658]: `async_send` is a temporary placeholder for marking async methods as returning a future that is also Send and may be removed or renamed in the future. + --> $DIR/send-bound-gate.rs:11:1 + | +LL | #[async_send] + | ^^^^^^^^^^^^^ + | + = note: see issue #91611 for more information + = help: add `#![feature(async_fn_in_trait)]` to the crate attributes to enable + +error[E0706]: functions in traits cannot be declared `async` + --> $DIR/send-bound-gate.rs:7:5 + | +LL | async fn foo(&self) -> usize; + | -----^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `async` because of this + | + = note: `async` trait functions are not currently supported + = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait + = note: see issue #91611 for more information + = help: add `#![feature(async_fn_in_trait)]` to the crate attributes to enable + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0658, E0706. +For more information about an error, try `rustc --explain E0658`. From 6ca9bf2c687c5a7b3d67444ed932d5114406b3c2 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 19 Jan 2023 14:08:25 -0800 Subject: [PATCH 4/5] Look in the right attributes for async_send --- compiler/rustc_ast_lowering/src/item.rs | 13 ++++--- compiler/rustc_ast_lowering/src/lib.rs | 45 ++++++++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 5d2589cb2b2f7..9bdf56535ae5a 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1,3 +1,5 @@ +use crate::AsyncReturn; + use super::errors::{InvalidAbi, InvalidAbiSuggestion, MisplacedRelaxTraitBound}; use super::ResolverAstLoweringExt; use super::{AstOwner, ImplTraitContext, ImplTraitPosition}; @@ -261,7 +263,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let itctx = ImplTraitContext::Universal; let (generics, decl) = this.lower_generics(generics, id, &itctx, |this| { - let ret_id = asyncness.opt_return_id(); + let ret_id = + AsyncReturn::new_opt(asyncness.opt_return_id(), attrs.unwrap_or(&[])); this.lower_fn_decl(&decl, id, *fn_sig_span, FnDeclKind::Fn, ret_id) }); let sig = hir::FnSig { @@ -721,7 +724,7 @@ impl<'hir> LoweringContext<'_, 'hir> { sig, i.id, FnDeclKind::Trait, - asyncness.opt_return_id(), + AsyncReturn::new_opt(asyncness.opt_return_id(), &i.attrs), ); (generics, hir::TraitItemKind::Fn(sig, hir::TraitFn::Required(names)), false) } @@ -734,7 +737,7 @@ impl<'hir> LoweringContext<'_, 'hir> { sig, i.id, FnDeclKind::Trait, - asyncness.opt_return_id(), + AsyncReturn::new_opt(asyncness.opt_return_id(), &i.attrs), ); (generics, hir::TraitItemKind::Fn(sig, hir::TraitFn::Provided(body_id)), true) } @@ -827,7 +830,7 @@ impl<'hir> LoweringContext<'_, 'hir> { sig, i.id, if self.is_in_trait_impl { FnDeclKind::Impl } else { FnDeclKind::Inherent }, - asyncness.opt_return_id(), + AsyncReturn::new_opt(asyncness.opt_return_id(), &i.attrs), ); (generics, hir::ImplItemKind::Fn(sig, body_id)) @@ -1181,7 +1184,7 @@ impl<'hir> LoweringContext<'_, 'hir> { sig: &FnSig, id: NodeId, kind: FnDeclKind, - is_async: Option<(NodeId, Span)>, + is_async: Option, ) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) { let header = self.lower_fn_header(sig.header); let itctx = ImplTraitContext::Universal; diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 049e113f1413d..a84664c1956ce 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -462,6 +462,30 @@ enum ParenthesizedGenericArgs { Err, } +/// Specifies options for generating an async return type +#[derive(Debug)] +struct AsyncReturn { + /// The `NodeId` of the return `impl Trait` item + node_id: NodeId, + /// Points to the `async` keyword + span: Span, + /// Whether to add a `+ Send` bound to the `-> impl Future` return type + is_send: bool, +} + +impl AsyncReturn { + /// Creates a new Option from an Option<(NodeId, Span)> that is typically + /// returned from `Async::opt_node_id()` and a list of attributes to determine whether the + /// resulting type should be Send. + fn new_opt(opt_return_id: Option<(NodeId, Span)>, attrs: &[Attribute]) -> Option { + opt_return_id.map(|(node_id, span)| AsyncReturn { + node_id, + span, + is_send: attrs.iter().any(|attr| attr.has_name(sym::async_send)), + }) + } +} + impl<'a, 'hir> LoweringContext<'a, 'hir> { fn create_def( &mut self, @@ -1666,7 +1690,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { fn_node_id: NodeId, fn_span: Span, kind: FnDeclKind, - make_ret_async: Option<(NodeId, Span)>, + make_ret_async: Option, ) -> &'hir hir::FnDecl<'hir> { let c_variadic = decl.c_variadic(); @@ -1695,20 +1719,20 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.lower_ty_direct(¶m.ty, &itctx) })); - let output = if let Some((ret_id, span)) = make_ret_async { + let output = if let Some(async_return) = make_ret_async { if !kind.async_fn_allowed(self.tcx) { match kind { FnDeclKind::Trait | FnDeclKind::Impl => { self.tcx .sess .create_feature_err( - TraitFnAsync { fn_span, span }, + TraitFnAsync { fn_span, span: async_return.span }, sym::async_fn_in_trait, ) .emit(); } _ => { - self.tcx.sess.emit_err(TraitFnAsync { fn_span, span }); + self.tcx.sess.emit_err(TraitFnAsync { fn_span, span: async_return.span }); } } } @@ -1716,7 +1740,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.lower_async_fn_ret_ty( &decl.output, fn_node_id, - ret_id, + async_return, matches!(kind, FnDeclKind::Trait), ) } else { @@ -1793,15 +1817,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { &mut self, output: &FnRetTy, fn_node_id: NodeId, - opaque_ty_node_id: NodeId, + async_return: AsyncReturn, in_trait: bool, ) -> hir::FnRetTy<'hir> { let span = output.span(); + let opaque_ty_node_id = async_return.node_id; + let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::Async, span, None); let fn_def_id = self.local_def_id(fn_node_id); - let fn_local_id = self.lower_node_id(fn_node_id).local_id; let opaque_ty_def_id = self.create_def(fn_def_id, opaque_ty_node_id, DefPathData::ImplTrait, opaque_ty_span); @@ -1968,12 +1993,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { )); debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); - let is_async_send = this.attrs.get(&fn_local_id).map_or(false, |attrs| { - attrs.into_iter().any(|attr| attr.has_name(sym::async_send)) - }); - // Add Send bound if `#[async_send]` attribute is present - let bounds = if is_async_send { + let bounds = if async_return.is_send { let send_bound = hir::GenericBound::LangItemTrait( hir::LangItem::Send, this.lower_span(span), From 01816de35839ec6c8989ae0228d33969027acb6e Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 19 Jan 2023 14:29:44 -0800 Subject: [PATCH 5/5] Fix RustDoc --- compiler/rustc_ast_lowering/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a84664c1956ce..dfb5e239dfe67 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -474,7 +474,7 @@ struct AsyncReturn { } impl AsyncReturn { - /// Creates a new Option from an Option<(NodeId, Span)> that is typically + /// Creates a new `Option` from an `Option<(NodeId, Span)>` that is typically /// returned from `Async::opt_node_id()` and a list of attributes to determine whether the /// resulting type should be Send. fn new_opt(opt_return_id: Option<(NodeId, Span)>, attrs: &[Attribute]) -> Option {