maybe_impl/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use quote::ToTokens;
5use syn::{
6    parse2, punctuated::Punctuated, spanned::Spanned, token::Comma, Error, ItemTrait, TraitBound,
7    TraitBoundModifier, TypeParamBound,
8};
9
10use syn::{
11    parse::{Parse, ParseStream},
12    Path, Result,
13};
14
15struct TraitsAttribute {
16    traits: Punctuated<Path, Comma>,
17}
18
19impl Parse for TraitsAttribute {
20    fn parse(input: ParseStream) -> Result<Self> {
21        Ok(Self {
22            traits: input
23                .parse_terminated(Path::parse, Comma)
24                .expect("At least one type must be specified."),
25        })
26    }
27}
28
29/// Represents the metadata used to conditionally implement one or more traits.
30/// 
31/// # Arguments
32/// 
33/// * `traits` - one or more traits to add to a super trait.
34/// 
35/// # Remarks
36/// 
37/// This attribute is intended to be combined with the `cfg_attr` attribute to
38/// conditionally implement the specified traits. The primary use case is to
39/// conditionally add `Send` and `Sync`, but any user-specified traits are supported.
40/// 
41/// # Examples
42/// 
43/// The following trait only implements `Send` and `Sync` when the **async** feature
44/// is activated.
45/// 
46/// ```
47/// #[cfg_attr(feature = "async", maybe_impl::traits(Send,Sync))]
48/// trait Foo {
49///    fn bar(&self);
50/// }
51/// ```
52#[proc_macro_attribute]
53pub fn traits(
54    metadata: proc_macro::TokenStream,
55    input: proc_macro::TokenStream,
56) -> proc_macro::TokenStream {
57    proc_macro::TokenStream::from(_traits(
58        TokenStream::from(metadata),
59        TokenStream::from(input),
60    ))
61}
62
63fn _traits(metadata: TokenStream, input: TokenStream) -> TokenStream {
64    let result = match parse2::<TraitsAttribute>(metadata) {
65        Ok(attribute) => {
66            if let Ok(mut trait_) = parse2::<ItemTrait>(TokenStream::from(input.clone())) {
67                let traits = attribute.traits.iter().map(|path| {
68                    TypeParamBound::Trait(TraitBound {
69                        paren_token: None,
70                        modifier: TraitBoundModifier::None,
71                        lifetimes: None,
72                        path: path.clone(),
73                    })
74                });
75                trait_.supertraits.extend(traits);
76                Ok(trait_.into_token_stream())
77            } else {
78                Err(Error::new(
79                    input.span(),
80                    "Attribute can only be applied to a trait.",
81                ))
82            }
83        }
84        Err(error) => Err(error),
85    };
86
87    match result {
88        Ok(output) => output,
89        Err(error) => error.to_compile_error().into(),
90    }
91}
92
93#[cfg(test)]
94mod test {
95    use super::*;
96    use std::str::FromStr;
97
98    #[test]
99    fn attribute_should_add_single_trait() {
100        // arrange
101        let metadata = TokenStream::from_str("Send").unwrap();
102        let input = TokenStream::from_str("trait Foo { }").unwrap();
103        let expected = "trait Foo : Send { }";
104
105        // act
106        let result = _traits(metadata, input);
107
108        // assert
109        assert_eq!(expected, result.to_string());
110    }
111
112    #[test]
113    fn attribute_should_add_multiple_traits() {
114        // arrange
115        let metadata = TokenStream::from_str("Send, Sync").unwrap();
116        let input = TokenStream::from_str(
117            r#"
118            trait IPityTheFoo {
119                fn bar(&self);
120            }
121        "#,
122        )
123        .unwrap();
124        let expected = concat!(
125            "trait IPityTheFoo : Send + Sync { ",
126            "fn bar (& self) ; ",
127            "}",
128        );
129
130        // act
131        let result = _traits(metadata, input);
132
133        // assert
134        assert_eq!(expected, result.to_string());
135    }
136}