diff --git a/README.md b/README.md index 27e30b6..ce4c767 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,17 @@ trait IntFactory: Send { Implementers can choose to implement either `LocalIntFactory` or `IntFactory` as appropriate. +If a non-`Send` variant of the trait is not needed, the name of the new variant can simply be omitted. E.g., this generates a *single* (rather than an additional) trait whose definition matches that in the expansion above: + +```rust +#[trait_variant::make(Send)] +trait IntFactory { + async fn make(&self) -> i32; + fn stream(&self) -> impl Iterator; + fn call(&self) -> u32; +} +``` + For more details, see the docs for [`trait_variant::make`]. [`trait_variant::make`]: https://docs.rs/trait-variant/latest/trait_variant/attr.make.html diff --git a/trait-variant/examples/variant.rs b/trait-variant/examples/variant.rs index 04bcc6a..7241ba6 100644 --- a/trait-variant/examples/variant.rs +++ b/trait-variant/examples/variant.rs @@ -29,6 +29,11 @@ fn spawn_task(factory: impl IntFactory + 'static) { }); } +#[trait_variant::make(Send)] +pub trait TupleFactory { + async fn new() -> Self; +} + #[trait_variant::make(GenericTrait: Send)] pub trait LocalGenericTrait<'x, S: Sync, Y, const X: usize> where diff --git a/trait-variant/src/lib.rs b/trait-variant/src/lib.rs index d3286d9..892e90a 100644 --- a/trait-variant/src/lib.rs +++ b/trait-variant/src/lib.rs @@ -38,6 +38,19 @@ mod variant; /// Implementers of the trait can choose to implement the variant instead of the /// original trait. The macro creates a blanket impl which ensures that any type /// which implements the variant also implements the original trait. +/// +/// If a non-`Send` variant of the trait is not needed, the name of +/// new variant can simply be omitted. E.g., this generates a +/// *single* (rather than an additional) trait whose definition +/// matches that in the expansion above: +/// +/// #[trait_variant::make(Send)] +/// trait IntFactory { +/// async fn make(&self) -> i32; +/// fn stream(&self) -> impl Iterator; +/// fn call(&self) -> u32; +/// } +/// ``` #[proc_macro_attribute] pub fn make( attr: proc_macro::TokenStream, diff --git a/trait-variant/src/variant.rs b/trait-variant/src/variant.rs index f7f0d27..b3a04c1 100644 --- a/trait-variant/src/variant.rs +++ b/trait-variant/src/variant.rs @@ -1,4 +1,5 @@ // Copyright (c) 2023 Google LLC +// Copyright (c) 2023 Various contributors (see git history) // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -11,7 +12,7 @@ use std::iter; use proc_macro2::TokenStream; use quote::quote; use syn::{ - parse::{Parse, ParseStream}, + parse::{discouraged::Speculative as _, Parse, ParseStream}, parse_macro_input, parse_quote, punctuated::Punctuated, token::Plus, @@ -20,32 +21,46 @@ use syn::{ TypeImplTrait, TypeParam, TypeParamBound, }; -struct Attrs { - variant: MakeVariant, +#[derive(Clone)] +struct Variant { + name: Option, + _colon: Option, + bounds: Punctuated, } -impl Parse for Attrs { - fn parse(input: ParseStream) -> Result { - Ok(Self { - variant: MakeVariant::parse(input)?, - }) - } +fn parse_bounds_only(input: ParseStream) -> Result> { + let fork = input.fork(); + let colon: Option = fork.parse()?; + let bounds = match fork.parse_terminated(TraitBound::parse, Token![+]) { + Ok(x) => Ok(x), + Err(e) if colon.is_some() => Err(e), + Err(_) => return Ok(None), + }; + input.advance_to(&fork); + Ok(Some(Variant { + name: None, + _colon: colon, + bounds: bounds?, + })) } -struct MakeVariant { - name: Ident, - #[allow(unused)] - colon: Token![:], - bounds: Punctuated, +fn parse_fallback(input: ParseStream) -> Result { + let name: Ident = input.parse()?; + let colon: Token![:] = input.parse()?; + let bounds = input.parse_terminated(TraitBound::parse, Token![+])?; + Ok(Variant { + name: Some(name), + _colon: Some(colon), + bounds, + }) } -impl Parse for MakeVariant { +impl Parse for Variant { fn parse(input: ParseStream) -> Result { - Ok(Self { - name: input.parse()?, - colon: input.parse()?, - bounds: input.parse_terminated(TraitBound::parse, Token![+])?, - }) + match parse_bounds_only(input)? { + Some(x) => Ok(x), + None => parse_fallback(input), + } } } @@ -53,11 +68,10 @@ pub fn make( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let attrs = parse_macro_input!(attr as Attrs); + let variant = parse_macro_input!(attr as Variant); let item = parse_macro_input!(item as ItemTrait); - let maybe_allow_async_lint = if attrs - .variant + let maybe_allow_async_lint = if variant .bounds .iter() .any(|b| b.path.segments.last().unwrap().ident == "Send") @@ -67,26 +81,24 @@ pub fn make( quote! {} }; - let variant = mk_variant(&attrs, &item); - let blanket_impl = mk_blanket_impl(&attrs, &item); - + let variant_name = variant.clone().name.unwrap_or(item.clone().ident); + let variant_def = mk_variant(&variant_name, &variant.bounds, &item); + if variant_name == item.ident { + return variant_def.into(); + } + let blanket_impl = Some(mk_blanket_impl(&variant_name, &item)); quote! { #maybe_allow_async_lint #item - #variant + #variant_def #blanket_impl } .into() } -fn mk_variant(attrs: &Attrs, tr: &ItemTrait) -> TokenStream { - let MakeVariant { - ref name, - colon: _, - ref bounds, - } = attrs.variant; +fn mk_variant(name: &Ident, bounds: &Punctuated, tr: &ItemTrait) -> TokenStream { let bounds: Vec<_> = bounds .into_iter() .map(|b| TypeParamBound::Trait(b.clone())) @@ -160,9 +172,8 @@ fn transform_item(item: &TraitItem, bounds: &Vec) -> TraitItem { }) } -fn mk_blanket_impl(attrs: &Attrs, tr: &ItemTrait) -> TokenStream { +fn mk_blanket_impl(variant: &Ident, tr: &ItemTrait) -> TokenStream { let orig = &tr.ident; - let variant = &attrs.variant.name; let (_impl, orig_ty_generics, _where) = &tr.generics.split_for_impl(); let items = tr .items diff --git a/trait-variant/tests/bounds.rs b/trait-variant/tests/bounds.rs new file mode 100644 index 0000000..3a42ae4 --- /dev/null +++ b/trait-variant/tests/bounds.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Google LLC +// Copyright (c) 2023 Various contributors (see git history) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[trait_variant::make(Send + Sync)] +pub trait Trait { + const CONST: &'static (); + type Gat<'a> + where + Self: 'a; + async fn assoc_async_fn_no_ret(a: (), b: ()); + async fn assoc_async_method_no_ret(&self, a: (), b: ()); + async fn assoc_async_fn(a: (), b: ()) -> (); + async fn assoc_async_method(&self, a: (), b: ()) -> (); + fn assoc_sync_fn_no_ret(a: (), b: ()); + fn assoc_sync_method_no_ret(&self, a: (), b: ()); + fn assoc_sync_fn(a: (), b: ()) -> (); + fn assoc_sync_method(&self, a: (), b: ()) -> (); + // FIXME: See #17. + //async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {} + //async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + //async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {} + //async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {} + fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +impl Trait for () { + const CONST: &'static () = &(); + type Gat<'a> = (); + async fn assoc_async_fn_no_ret(_a: (), _b: ()) {} + async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + async fn assoc_async_fn(_a: (), _b: ()) -> () {} + async fn assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn assoc_sync_fn(_a: (), _b: ()) -> () {} + fn assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +fn is_bounded(_: T) {} + +#[test] +fn test() { + fn inner(x: T) { + let (a, b) = ((), ()); + is_bounded(::assoc_async_fn_no_ret(a, b)); + is_bounded(::assoc_async_method_no_ret(&x, a, b)); + is_bounded(::assoc_async_fn(a, b)); + is_bounded(::assoc_async_method(&x, a, b)); + // FIXME: See #17. + //is_bounded(::dft_assoc_async_fn_no_ret(a, b)); + //is_bounded(::dft_assoc_async_method_no_ret(&x, a, b)); + //is_bounded(::dft_assoc_async_fn(a, b)); + //is_bounded(::dft_assoc_async_method(&x, a, b)); + } + inner(()); +} diff --git a/trait-variant/tests/colon-bounds.rs b/trait-variant/tests/colon-bounds.rs new file mode 100644 index 0000000..00462ca --- /dev/null +++ b/trait-variant/tests/colon-bounds.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Google LLC +// Copyright (c) 2023 Various contributors (see git history) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[trait_variant::make(: Send + Sync)] +pub trait Trait { + const CONST: &'static (); + type Gat<'a> + where + Self: 'a; + async fn assoc_async_fn_no_ret(a: (), b: ()); + async fn assoc_async_method_no_ret(&self, a: (), b: ()); + async fn assoc_async_fn(a: (), b: ()) -> (); + async fn assoc_async_method(&self, a: (), b: ()) -> (); + fn assoc_sync_fn_no_ret(a: (), b: ()); + fn assoc_sync_method_no_ret(&self, a: (), b: ()); + fn assoc_sync_fn(a: (), b: ()) -> (); + fn assoc_sync_method(&self, a: (), b: ()) -> (); + // FIXME: See #17. + //async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {} + //async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + //async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {} + //async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {} + fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +impl Trait for () { + const CONST: &'static () = &(); + type Gat<'a> = (); + async fn assoc_async_fn_no_ret(_a: (), _b: ()) {} + async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + async fn assoc_async_fn(_a: (), _b: ()) -> () {} + async fn assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn assoc_sync_fn(_a: (), _b: ()) -> () {} + fn assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +fn is_bounded(_: T) {} + +#[test] +fn test() { + fn inner(x: T) { + let (a, b) = ((), ()); + is_bounded(::assoc_async_fn_no_ret(a, b)); + is_bounded(::assoc_async_method_no_ret(&x, a, b)); + is_bounded(::assoc_async_fn(a, b)); + is_bounded(::assoc_async_method(&x, a, b)); + // FIXME: See #17. + //is_bounded(::dft_assoc_async_fn_no_ret(a, b)); + //is_bounded(::dft_assoc_async_method_no_ret(&x, a, b)); + //is_bounded(::dft_assoc_async_fn(a, b)); + //is_bounded(::dft_assoc_async_method(&x, a, b)); + } + inner(()); +} diff --git a/trait-variant/tests/name-colon.rs b/trait-variant/tests/name-colon.rs new file mode 100644 index 0000000..8cb413c --- /dev/null +++ b/trait-variant/tests/name-colon.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2023 Google LLC +// Copyright (c) 2023 Various contributors (see git history) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[trait_variant::make(Trait:)] +#[allow(async_fn_in_trait)] +pub trait LocalTrait { + const CONST: &'static (); + type Gat<'a> + where + Self: 'a; + async fn assoc_async_fn_no_ret(a: (), b: ()); + async fn assoc_async_method_no_ret(&self, a: (), b: ()); + async fn assoc_async_fn(a: (), b: ()) -> (); + async fn assoc_async_method(&self, a: (), b: ()) -> (); + fn assoc_sync_fn_no_ret(a: (), b: ()); + fn assoc_sync_method_no_ret(&self, a: (), b: ()); + fn assoc_sync_fn(a: (), b: ()) -> (); + fn assoc_sync_method(&self, a: (), b: ()) -> (); + // FIXME: See #17. + //async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {} + //async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + //async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {} + //async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {} + fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +impl Trait for () { + const CONST: &'static () = &(); + type Gat<'a> = (); + async fn assoc_async_fn_no_ret(_a: (), _b: ()) {} + async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + async fn assoc_async_fn(_a: (), _b: ()) -> () {} + async fn assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn assoc_sync_fn(_a: (), _b: ()) -> () {} + fn assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +fn is_bounded(_: T) {} + +#[test] +fn test() { + fn inner(x: T) { + let (a, b) = ((), ()); + is_bounded(::assoc_async_fn_no_ret(a, b)); + is_bounded(::assoc_async_method_no_ret(&x, a, b)); + is_bounded(::assoc_async_fn(a, b)); + is_bounded(::assoc_async_method(&x, a, b)); + // FIXME: See #17. + //is_bounded(::dft_assoc_async_fn_no_ret(a, b)); + //is_bounded(::dft_assoc_async_method_no_ret(&x, a, b)); + //is_bounded(::dft_assoc_async_fn(a, b)); + //is_bounded(::dft_assoc_async_method(&x, a, b)); + } + inner(()); +} diff --git a/trait-variant/tests/same_name-colon-bounds.rs b/trait-variant/tests/same_name-colon-bounds.rs new file mode 100644 index 0000000..7fa1f05 --- /dev/null +++ b/trait-variant/tests/same_name-colon-bounds.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Google LLC +// Copyright (c) 2023 Various contributors (see git history) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[trait_variant::make(Trait: Send + Sync)] +pub trait Trait { + const CONST: &'static (); + type Gat<'a> + where + Self: 'a; + async fn assoc_async_fn_no_ret(a: (), b: ()); + async fn assoc_async_method_no_ret(&self, a: (), b: ()); + async fn assoc_async_fn(a: (), b: ()) -> (); + async fn assoc_async_method(&self, a: (), b: ()) -> (); + fn assoc_sync_fn_no_ret(a: (), b: ()); + fn assoc_sync_method_no_ret(&self, a: (), b: ()); + fn assoc_sync_fn(a: (), b: ()) -> (); + fn assoc_sync_method(&self, a: (), b: ()) -> (); + // FIXME: See #17. + //async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {} + //async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + //async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {} + //async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {} + fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +impl Trait for () { + const CONST: &'static () = &(); + type Gat<'a> = (); + async fn assoc_async_fn_no_ret(_a: (), _b: ()) {} + async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {} + async fn assoc_async_fn(_a: (), _b: ()) -> () {} + async fn assoc_async_method(&self, _a: (), _b: ()) -> () {} + fn assoc_sync_fn_no_ret(_a: (), _b: ()) {} + fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {} + fn assoc_sync_fn(_a: (), _b: ()) -> () {} + fn assoc_sync_method(&self, _a: (), _b: ()) -> () {} +} + +fn is_bounded(_: T) {} + +#[test] +fn test() { + fn inner(x: T) { + let (a, b) = ((), ()); + is_bounded(::assoc_async_fn_no_ret(a, b)); + is_bounded(::assoc_async_method_no_ret(&x, a, b)); + is_bounded(::assoc_async_fn(a, b)); + is_bounded(::assoc_async_method(&x, a, b)); + // FIXME: See #17. + //is_bounded(::dft_assoc_async_fn_no_ret(a, b)); + //is_bounded(::dft_assoc_async_method_no_ret(&x, a, b)); + //is_bounded(::dft_assoc_async_fn(a, b)); + //is_bounded(::dft_assoc_async_method(&x, a, b)); + } + inner(()); +}