tamasfe_macro_utils/
attr.rs

1use std::collections::HashSet;
2
3use proc_macro2::{TokenStream, TokenTree};
4use proc_macro_error::{abort, emit_error};
5use quote::{ToTokens, TokenStreamExt};
6use syn::{
7    ext::IdentExt, parenthesized, parse::{discouraged::Speculative, Parser}, parse::Parse,
8    punctuated::IntoIter, punctuated::Punctuated, spanned::Spanned, token, Ident, Token,
9};
10
11#[derive(Debug, Clone)]
12pub enum AttrNamedValue {
13    Eq {
14        eq: Token!(=),
15        content: TokenStream,
16    },
17    Parens {
18        parens: token::Paren,
19        content: TokenStream,
20    },
21}
22
23impl AttrNamedValue {
24    pub fn parse<T: Parse>(self) -> syn::Result<T> {
25        match self {
26            AttrNamedValue::Eq { content, .. } => syn::parse2(content),
27            AttrNamedValue::Parens { content, .. } => syn::parse2(content),
28        }
29    }
30
31    pub fn parse_with<P: Parser>(self, parser: P) -> syn::Result<P::Output> {
32        match self {
33            AttrNamedValue::Eq { content, .. } => parser.parse2(content),
34            AttrNamedValue::Parens { content, .. } => parser.parse2(content),
35        }
36    }
37}
38
39impl ToTokens for AttrNamedValue {
40    fn to_tokens(&self, tokens: &mut TokenStream) {
41        match self {
42            AttrNamedValue::Eq { content, eq } => {
43                eq.to_tokens(tokens);
44                content.to_tokens(tokens);
45            }
46            AttrNamedValue::Parens { content, parens } => {
47                parens.surround(tokens, |tokens| {
48                    content.to_tokens(tokens);
49                });
50            }
51        }
52    }
53}
54
55/// An attribute parameter such as `foo = "bar"`, or `foo`, or `foo(bar)`.
56#[derive(Debug, Clone)]
57pub enum AttrParam {
58    Named { name: Ident, value: AttrNamedValue },
59    Unnamed(TokenStream),
60}
61
62impl AttrParam {
63    pub fn parse<T: Parse>(self) -> syn::Result<T> {
64        match self {
65            AttrParam::Named { value, .. } => value.parse(),
66            AttrParam::Unnamed(ts) => syn::parse2(ts),
67        }
68    }
69}
70
71impl ToTokens for AttrParam {
72    fn to_tokens(&self, tokens: &mut TokenStream) {
73        match self {
74            AttrParam::Named { name, value } => {
75                name.to_tokens(tokens);
76                value.to_tokens(tokens);
77            }
78            AttrParam::Unnamed(ts) => ts.to_tokens(tokens),
79        }
80    }
81}
82
83impl Parse for AttrParam {
84    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
85        let named_input = input.fork();
86
87        if let Ok(name) = named_input.call(Ident::parse_any) {
88            if named_input.peek(token::Paren) {
89                let inner;
90                let parens = parenthesized!(inner in named_input);
91                let content = inner.parse()?;
92                input.advance_to(&named_input);
93                return Ok(AttrParam::Named {
94                    name,
95                    value: AttrNamedValue::Parens { parens, content },
96                });
97            } else if named_input.peek(Token!(=)) {
98                let eq = named_input.parse::<Token!(=)>()?;
99                input.advance_to(&named_input);
100                return Ok(AttrParam::Named {
101                    name,
102                    value: AttrNamedValue::Eq {
103                        eq,
104                        content: parse_until_comma(input),
105                    },
106                });
107            }
108        }
109
110        Ok(AttrParam::Unnamed(parse_until_comma(input)))
111    }
112}
113
114#[derive(Debug, Clone)]
115pub struct AttrParams(Punctuated<AttrParam, Token!(,)>);
116
117impl ToTokens for AttrParams {
118    fn to_tokens(&self, tokens: &mut TokenStream) {
119        for attr in &self.0 {
120            attr.to_tokens(tokens);
121        }
122    }
123}
124
125impl Parse for AttrParams {
126    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
127        Ok(Self(Punctuated::<AttrParam, Token!(,)>::parse_terminated(
128            input,
129        )?))
130    }
131}
132
133#[allow(dead_code)]
134impl AttrParams {
135    pub fn is_empty(&self) -> bool {
136        self.0.is_empty()
137    }
138
139    pub fn len(&self) -> usize {
140        self.0.len()
141    }
142
143    pub fn iter(&self) -> impl Iterator<Item = &AttrParam> {
144        self.0.iter()
145    }
146
147    /// Shows compile errors for duplicate names.
148    pub fn no_duplicates(&self) {
149        self.no_duplicates_by(|_| true)
150    }
151
152    /// f should return true if the ident is not allowed to be a duplicate.
153    pub fn no_duplicates_by<F: Fn(&Ident) -> bool>(&self, f: F) {
154        let mut names: HashSet<&Ident> = HashSet::new();
155        for attr in &self.0 {
156            if let AttrParam::Named { name, .. } = attr {
157                if let Some(existing) = names.get(name) {
158                    if f(name) {
159                        emit_error!(existing.span(), r#"parameter "{}" is given here"#, name);
160                        abort!(name.span(), r#"parameter "{}" already exists"#, name);
161                    }
162                }
163                names.insert(name);
164            }
165        }
166    }
167
168    pub fn retain_known<F: Fn(&Ident) -> bool>(&self, f: F) {
169        for attr in &self.0 {
170            if let AttrParam::Named { name, .. } = attr {
171                if !f(name) {
172                    abort!(name.span(), r#"unknown parameter "{}""#, name);
173                }
174            }
175        }
176    }
177
178    pub fn has_named(&self) -> bool {
179        for attr in &self.0 {
180            if let AttrParam::Named { .. } = attr {
181                return true;
182            }
183        }
184        false
185    }
186
187    pub fn no_unnamed(&self) {
188        if !self.has_named() {
189            abort!(self.span(), r#"only named parameters are expected"#);
190        }
191    }
192
193    /// Shows compile errors if named parameters are mixed with unnamed ones.
194    pub fn no_names_mixed(&self) {
195        let mut named: Option<bool> = None;
196        for attr in &self.0 {
197            match attr {
198                AttrParam::Named { name, .. } => {
199                    if let Some(named) = &named {
200                        if !*named {
201                            abort!(
202                                name.span(),
203                                r#"named and unnamed parameters cannot be mixed"#
204                            );
205                        }
206                    }
207                    named = Some(true);
208                }
209                AttrParam::Unnamed(content) => {
210                    if let Some(named) = &named {
211                        if *named {
212                            abort!(
213                                content.span(),
214                                r#"named and unnamed parameters cannot be mixed"#
215                            );
216                        }
217                    }
218                    named = Some(false);
219                }
220            }
221        }
222    }
223}
224
225impl IntoIterator for AttrParams {
226    type Item = AttrParam;
227    type IntoIter = IntoIter<AttrParam>;
228
229    fn into_iter(self) -> Self::IntoIter {
230        self.0.into_iter()
231    }
232}
233
234fn parse_until_comma(input: syn::parse::ParseStream) -> TokenStream {
235    let mut ts = TokenStream::new();
236    input
237        .step(|cursor| {
238            let mut rest = *cursor;
239            while let Some((tt, next)) = rest.token_tree() {
240                match &tt {
241                    TokenTree::Punct(punct) if punct.as_char() == ',' => {
242                        return Ok(((), rest));
243                    }
244                    tt => {
245                        ts.append(tt.clone());
246                        rest = next
247                    }
248                }
249            }
250            Ok(((), rest))
251        })
252        .unwrap();
253    ts
254}