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#[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 let metadata = TokenStream::from_str("Send").unwrap();
102 let input = TokenStream::from_str("trait Foo { }").unwrap();
103 let expected = "trait Foo : Send { }";
104
105 let result = _traits(metadata, input);
107
108 assert_eq!(expected, result.to_string());
110 }
111
112 #[test]
113 fn attribute_should_add_multiple_traits() {
114 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 let result = _traits(metadata, input);
132
133 assert_eq!(expected, result.to_string());
135 }
136}