Skip to content

Commit c8a15fe

Browse files
committed
Support omitting the variant name
Let's say that we want to create a `Send` variant of a trait but we don't need a non-`Send` variant of that trait at all. We could of course just write that trait, but adding the `Send` bounds in all the right places could be annoying. In this commit, we allow simply omitting the new variant name from the call to `make`. When called that way, we use the name from the item to emit only one variant with the bounds applied. We don't emit the original item. For completeness and explicit disambiguation, we support prefixing the bounds with a colon (but giving no variant name). Similarly, for completeness, we support giving the same name as the trait item. In both of these cases, we just emit the one variant. Since these are for completeness, we don't advertise these syntaxes in the documentation. That is, we now support: - `make(NAME: BOUNDS)` - `make(NAME:)` - `make(:BOUNDS)` - `make(BOUNDS)` This resolves #18.
1 parent f81bb09 commit c8a15fe

File tree

8 files changed

+336
-35
lines changed

8 files changed

+336
-35
lines changed

Diff for: README.md

+11
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ trait IntFactory: Send {
3131

3232
Implementers can choose to implement either `LocalIntFactory` or `IntFactory` as appropriate.
3333

34+
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:
35+
36+
```rust
37+
#[trait_variant::make(Send)]
38+
trait IntFactory {
39+
async fn make(&self) -> i32;
40+
fn stream(&self) -> impl Iterator<Item = i32>;
41+
fn call(&self) -> u32;
42+
}
43+
```
44+
3445
For more details, see the docs for [`trait_variant::make`].
3546

3647
[`trait_variant::make`]: https://docs.rs/trait-variant/latest/trait_variant/attr.make.html

Diff for: trait-variant/examples/variant.rs

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ fn spawn_task(factory: impl IntFactory + 'static) {
2929
});
3030
}
3131

32+
#[trait_variant::make(Send)]
33+
pub trait TupleFactory {
34+
async fn new() -> Self;
35+
}
36+
3237
#[trait_variant::make(GenericTrait: Send)]
3338
pub trait LocalGenericTrait<'x, S: Sync, Y, const X: usize>
3439
where

Diff for: trait-variant/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ mod variant;
3838
/// Implementers of the trait can choose to implement the variant instead of the
3939
/// original trait. The macro creates a blanket impl which ensures that any type
4040
/// which implements the variant also implements the original trait.
41+
///
42+
/// If a non-`Send` variant of the trait is not needed, the name of
43+
/// new variant can simply be omitted. E.g., this generates a
44+
/// *single* (rather than an additional) trait whose definition
45+
/// matches that in the expansion above:
46+
///
47+
/// #[trait_variant::make(Send)]
48+
/// trait IntFactory {
49+
/// async fn make(&self) -> i32;
50+
/// fn stream(&self) -> impl Iterator<Item = i32>;
51+
/// fn call(&self) -> u32;
52+
/// }
53+
/// ```
4154
#[proc_macro_attribute]
4255
pub fn make(
4356
attr: proc_macro::TokenStream,

Diff for: trait-variant/src/variant.rs

+46-35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2023 Google LLC
2+
// Copyright (c) 2023 Various contributors (see git history)
23
//
34
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
45
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@@ -11,7 +12,7 @@ use std::iter;
1112
use proc_macro2::TokenStream;
1213
use quote::quote;
1314
use syn::{
14-
parse::{Parse, ParseStream},
15+
parse::{discouraged::Speculative as _, Parse, ParseStream},
1516
parse_macro_input, parse_quote,
1617
punctuated::Punctuated,
1718
token::Plus,
@@ -20,44 +21,57 @@ use syn::{
2021
TypeImplTrait, TypeParam, TypeParamBound,
2122
};
2223

23-
struct Attrs {
24-
variant: MakeVariant,
24+
#[derive(Clone)]
25+
struct Variant {
26+
name: Option<Ident>,
27+
_colon: Option<Token![:]>,
28+
bounds: Punctuated<TraitBound, Plus>,
2529
}
2630

27-
impl Parse for Attrs {
28-
fn parse(input: ParseStream) -> Result<Self> {
29-
Ok(Self {
30-
variant: MakeVariant::parse(input)?,
31-
})
32-
}
31+
fn parse_bounds_only(input: ParseStream) -> Result<Option<Variant>> {
32+
let fork = input.fork();
33+
let colon: Option<Token![:]> = fork.parse()?;
34+
let bounds = match fork.parse_terminated(TraitBound::parse, Token![+]) {
35+
Ok(x) => Ok(x),
36+
Err(e) if colon.is_some() => Err(e),
37+
Err(_) => return Ok(None),
38+
};
39+
input.advance_to(&fork);
40+
Ok(Some(Variant {
41+
name: None,
42+
_colon: colon,
43+
bounds: bounds?,
44+
}))
3345
}
3446

35-
struct MakeVariant {
36-
name: Ident,
37-
#[allow(unused)]
38-
colon: Token![:],
39-
bounds: Punctuated<TraitBound, Plus>,
47+
fn parse_fallback(input: ParseStream) -> Result<Variant> {
48+
let name: Ident = input.parse()?;
49+
let colon: Token![:] = input.parse()?;
50+
let bounds = input.parse_terminated(TraitBound::parse, Token![+])?;
51+
Ok(Variant {
52+
name: Some(name),
53+
_colon: Some(colon),
54+
bounds,
55+
})
4056
}
4157

42-
impl Parse for MakeVariant {
58+
impl Parse for Variant {
4359
fn parse(input: ParseStream) -> Result<Self> {
44-
Ok(Self {
45-
name: input.parse()?,
46-
colon: input.parse()?,
47-
bounds: input.parse_terminated(TraitBound::parse, Token![+])?,
48-
})
60+
match parse_bounds_only(input)? {
61+
Some(x) => Ok(x),
62+
None => parse_fallback(input),
63+
}
4964
}
5065
}
5166

5267
pub fn make(
5368
attr: proc_macro::TokenStream,
5469
item: proc_macro::TokenStream,
5570
) -> proc_macro::TokenStream {
56-
let attrs = parse_macro_input!(attr as Attrs);
71+
let variant = parse_macro_input!(attr as Variant);
5772
let item = parse_macro_input!(item as ItemTrait);
5873

59-
let maybe_allow_async_lint = if attrs
60-
.variant
74+
let maybe_allow_async_lint = if variant
6175
.bounds
6276
.iter()
6377
.any(|b| b.path.segments.last().unwrap().ident == "Send")
@@ -67,26 +81,24 @@ pub fn make(
6781
quote! {}
6882
};
6983

70-
let variant = mk_variant(&attrs, &item);
71-
let blanket_impl = mk_blanket_impl(&attrs, &item);
72-
84+
let variant_name = variant.clone().name.unwrap_or(item.clone().ident);
85+
let variant_def = mk_variant(&variant_name, &variant.bounds, &item);
86+
if variant_name == item.ident {
87+
return variant_def.into();
88+
}
89+
let blanket_impl = Some(mk_blanket_impl(&variant_name, &item));
7390
quote! {
7491
#maybe_allow_async_lint
7592
#item
7693

77-
#variant
94+
#variant_def
7895

7996
#blanket_impl
8097
}
8198
.into()
8299
}
83100

84-
fn mk_variant(attrs: &Attrs, tr: &ItemTrait) -> TokenStream {
85-
let MakeVariant {
86-
ref name,
87-
colon: _,
88-
ref bounds,
89-
} = attrs.variant;
101+
fn mk_variant(name: &Ident, bounds: &Punctuated<TraitBound, Plus>, tr: &ItemTrait) -> TokenStream {
90102
let bounds: Vec<_> = bounds
91103
.into_iter()
92104
.map(|b| TypeParamBound::Trait(b.clone()))
@@ -160,9 +172,8 @@ fn transform_item(item: &TraitItem, bounds: &Vec<TypeParamBound>) -> TraitItem {
160172
})
161173
}
162174

163-
fn mk_blanket_impl(attrs: &Attrs, tr: &ItemTrait) -> TokenStream {
175+
fn mk_blanket_impl(variant: &Ident, tr: &ItemTrait) -> TokenStream {
164176
let orig = &tr.ident;
165-
let variant = &attrs.variant.name;
166177
let (_impl, orig_ty_generics, _where) = &tr.generics.split_for_impl();
167178
let items = tr
168179
.items

Diff for: trait-variant/tests/bounds.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2023 Google LLC
2+
// Copyright (c) 2023 Various contributors (see git history)
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
#[trait_variant::make(Send + Sync)]
11+
pub trait Trait {
12+
const CONST: &'static ();
13+
type Gat<'a>
14+
where
15+
Self: 'a;
16+
async fn assoc_async_fn_no_ret(a: (), b: ());
17+
async fn assoc_async_method_no_ret(&self, a: (), b: ());
18+
async fn assoc_async_fn(a: (), b: ()) -> ();
19+
async fn assoc_async_method(&self, a: (), b: ()) -> ();
20+
fn assoc_sync_fn_no_ret(a: (), b: ());
21+
fn assoc_sync_method_no_ret(&self, a: (), b: ());
22+
fn assoc_sync_fn(a: (), b: ()) -> ();
23+
fn assoc_sync_method(&self, a: (), b: ()) -> ();
24+
// FIXME: See #17.
25+
//async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {}
26+
//async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
27+
//async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {}
28+
//async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {}
29+
fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {}
30+
fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
31+
fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {}
32+
fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {}
33+
}
34+
35+
impl Trait for () {
36+
const CONST: &'static () = &();
37+
type Gat<'a> = ();
38+
async fn assoc_async_fn_no_ret(_a: (), _b: ()) {}
39+
async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
40+
async fn assoc_async_fn(_a: (), _b: ()) -> () {}
41+
async fn assoc_async_method(&self, _a: (), _b: ()) -> () {}
42+
fn assoc_sync_fn_no_ret(_a: (), _b: ()) {}
43+
fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
44+
fn assoc_sync_fn(_a: (), _b: ()) -> () {}
45+
fn assoc_sync_method(&self, _a: (), _b: ()) -> () {}
46+
}
47+
48+
fn is_bounded<T: Send + Sync>(_: T) {}
49+
50+
#[test]
51+
fn test() {
52+
fn inner<T: Trait>(x: T) {
53+
let (a, b) = ((), ());
54+
is_bounded(<T as Trait>::assoc_async_fn_no_ret(a, b));
55+
is_bounded(<T as Trait>::assoc_async_method_no_ret(&x, a, b));
56+
is_bounded(<T as Trait>::assoc_async_fn(a, b));
57+
is_bounded(<T as Trait>::assoc_async_method(&x, a, b));
58+
// FIXME: See #17.
59+
//is_bounded(<T as Trait>::dft_assoc_async_fn_no_ret(a, b));
60+
//is_bounded(<T as Trait>::dft_assoc_async_method_no_ret(&x, a, b));
61+
//is_bounded(<T as Trait>::dft_assoc_async_fn(a, b));
62+
//is_bounded(<T as Trait>::dft_assoc_async_method(&x, a, b));
63+
}
64+
inner(());
65+
}

Diff for: trait-variant/tests/colon-bounds.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2023 Google LLC
2+
// Copyright (c) 2023 Various contributors (see git history)
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
#[trait_variant::make(: Send + Sync)]
11+
pub trait Trait {
12+
const CONST: &'static ();
13+
type Gat<'a>
14+
where
15+
Self: 'a;
16+
async fn assoc_async_fn_no_ret(a: (), b: ());
17+
async fn assoc_async_method_no_ret(&self, a: (), b: ());
18+
async fn assoc_async_fn(a: (), b: ()) -> ();
19+
async fn assoc_async_method(&self, a: (), b: ()) -> ();
20+
fn assoc_sync_fn_no_ret(a: (), b: ());
21+
fn assoc_sync_method_no_ret(&self, a: (), b: ());
22+
fn assoc_sync_fn(a: (), b: ()) -> ();
23+
fn assoc_sync_method(&self, a: (), b: ()) -> ();
24+
// FIXME: See #17.
25+
//async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {}
26+
//async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
27+
//async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {}
28+
//async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {}
29+
fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {}
30+
fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
31+
fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {}
32+
fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {}
33+
}
34+
35+
impl Trait for () {
36+
const CONST: &'static () = &();
37+
type Gat<'a> = ();
38+
async fn assoc_async_fn_no_ret(_a: (), _b: ()) {}
39+
async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
40+
async fn assoc_async_fn(_a: (), _b: ()) -> () {}
41+
async fn assoc_async_method(&self, _a: (), _b: ()) -> () {}
42+
fn assoc_sync_fn_no_ret(_a: (), _b: ()) {}
43+
fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
44+
fn assoc_sync_fn(_a: (), _b: ()) -> () {}
45+
fn assoc_sync_method(&self, _a: (), _b: ()) -> () {}
46+
}
47+
48+
fn is_bounded<T: Send + Sync>(_: T) {}
49+
50+
#[test]
51+
fn test() {
52+
fn inner<T: Trait>(x: T) {
53+
let (a, b) = ((), ());
54+
is_bounded(<T as Trait>::assoc_async_fn_no_ret(a, b));
55+
is_bounded(<T as Trait>::assoc_async_method_no_ret(&x, a, b));
56+
is_bounded(<T as Trait>::assoc_async_fn(a, b));
57+
is_bounded(<T as Trait>::assoc_async_method(&x, a, b));
58+
// FIXME: See #17.
59+
//is_bounded(<T as Trait>::dft_assoc_async_fn_no_ret(a, b));
60+
//is_bounded(<T as Trait>::dft_assoc_async_method_no_ret(&x, a, b));
61+
//is_bounded(<T as Trait>::dft_assoc_async_fn(a, b));
62+
//is_bounded(<T as Trait>::dft_assoc_async_method(&x, a, b));
63+
}
64+
inner(());
65+
}

Diff for: trait-variant/tests/name-colon.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2023 Google LLC
2+
// Copyright (c) 2023 Various contributors (see git history)
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
#[trait_variant::make(Trait:)]
11+
#[allow(async_fn_in_trait)]
12+
pub trait LocalTrait {
13+
const CONST: &'static ();
14+
type Gat<'a>
15+
where
16+
Self: 'a;
17+
async fn assoc_async_fn_no_ret(a: (), b: ());
18+
async fn assoc_async_method_no_ret(&self, a: (), b: ());
19+
async fn assoc_async_fn(a: (), b: ()) -> ();
20+
async fn assoc_async_method(&self, a: (), b: ()) -> ();
21+
fn assoc_sync_fn_no_ret(a: (), b: ());
22+
fn assoc_sync_method_no_ret(&self, a: (), b: ());
23+
fn assoc_sync_fn(a: (), b: ()) -> ();
24+
fn assoc_sync_method(&self, a: (), b: ()) -> ();
25+
// FIXME: See #17.
26+
//async fn dft_assoc_async_fn_no_ret(_a: (), _b: ()) {}
27+
//async fn dft_assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
28+
//async fn dft_assoc_async_fn(_a: (), _b: ()) -> () {}
29+
//async fn dft_assoc_async_method(&self, _a: (), _b: ()) -> () {}
30+
fn dft_assoc_sync_fn_no_ret(_a: (), _b: ()) {}
31+
fn dft_assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
32+
fn dft_assoc_sync_fn(_a: (), _b: ()) -> () {}
33+
fn dft_assoc_sync_method(&self, _a: (), _b: ()) -> () {}
34+
}
35+
36+
impl Trait for () {
37+
const CONST: &'static () = &();
38+
type Gat<'a> = ();
39+
async fn assoc_async_fn_no_ret(_a: (), _b: ()) {}
40+
async fn assoc_async_method_no_ret(&self, _a: (), _b: ()) {}
41+
async fn assoc_async_fn(_a: (), _b: ()) -> () {}
42+
async fn assoc_async_method(&self, _a: (), _b: ()) -> () {}
43+
fn assoc_sync_fn_no_ret(_a: (), _b: ()) {}
44+
fn assoc_sync_method_no_ret(&self, _a: (), _b: ()) {}
45+
fn assoc_sync_fn(_a: (), _b: ()) -> () {}
46+
fn assoc_sync_method(&self, _a: (), _b: ()) -> () {}
47+
}
48+
49+
fn is_bounded<T>(_: T) {}
50+
51+
#[test]
52+
fn test() {
53+
fn inner<T: Trait>(x: T) {
54+
let (a, b) = ((), ());
55+
is_bounded(<T as Trait>::assoc_async_fn_no_ret(a, b));
56+
is_bounded(<T as Trait>::assoc_async_method_no_ret(&x, a, b));
57+
is_bounded(<T as Trait>::assoc_async_fn(a, b));
58+
is_bounded(<T as Trait>::assoc_async_method(&x, a, b));
59+
// FIXME: See #17.
60+
//is_bounded(<T as Trait>::dft_assoc_async_fn_no_ret(a, b));
61+
//is_bounded(<T as Trait>::dft_assoc_async_method_no_ret(&x, a, b));
62+
//is_bounded(<T as Trait>::dft_assoc_async_fn(a, b));
63+
//is_bounded(<T as Trait>::dft_assoc_async_method(&x, a, b));
64+
}
65+
inner(());
66+
}

0 commit comments

Comments
 (0)