syn_unnamed_struct/
attr.rs

1use crate::{CustomExpr, ExprUnnamedStruct};
2use proc_macro2::{Ident, TokenStream};
3use quote::ToTokens;
4use syn::{
5    ext::IdentExt,
6    parenthesized,
7    parse::{Parse, ParseStream},
8    punctuated::Punctuated,
9    token, Lit, LitBool, Path, PathSegment, Token,
10};
11
12//replace the Meta objects too so we can nest unnamed structs in normal meta objects
13pub enum Meta {
14    Path(MetaPath),
15    List(MetaList),
16    UnnamedList(UnnamedMetaList),
17    NameValue(MetaNameValue),
18}
19
20impl Parse for Meta {
21    fn parse(input: ParseStream) -> syn::Result<Self> {
22        if input.peek(token::Paren) {
23            let lst = input.parse::<UnnamedMetaList>()?;
24            Ok(Meta::UnnamedList(lst))
25        } else {
26            let path = input.parse::<MetaPath>()?;
27            parse_meta_after_path(path, input)
28        }
29    }
30}
31
32impl ToTokens for Meta {
33    fn to_tokens(&self, tokens: &mut TokenStream) {
34        match self {
35            Meta::Path(value) => value.to_tokens(tokens),
36            Meta::List(value) => value.to_tokens(tokens),
37            Meta::UnnamedList(value) => value.to_tokens(tokens),
38            Meta::NameValue(value) => value.to_tokens(tokens),
39        }
40    }
41}
42
43pub struct MetaPath(Path);
44
45impl MetaPath {
46    pub fn get_inner(&self) -> &Path {
47        &self.0
48    }
49}
50
51impl Parse for MetaPath {
52    fn parse(input: ParseStream) -> syn::Result<Self> {
53        Ok(MetaPath(Path {
54            leading_colon: input.parse()?,
55            segments: {
56                let mut segments = Punctuated::new();
57                while input.peek(Ident::peek_any) {
58                    let ident = Ident::parse_any(input)?;
59                    segments.push_value(PathSegment::from(ident));
60                    if !input.peek(Token![::]) {
61                        break;
62                    }
63                    let punct = input.parse()?;
64                    segments.push_punct(punct);
65                }
66                if segments.is_empty() {
67                    return Err(input.error("expected path"));
68                } else if segments.trailing_punct() {
69                    return Err(input.error("expected path segment"));
70                }
71                segments
72            },
73        }))
74    }
75}
76
77impl ToTokens for MetaPath {
78    fn to_tokens(&self, tokens: &mut TokenStream) {
79        self.0.to_tokens(tokens);
80    }
81}
82
83pub struct UnnamedMetaList {
84    pub paren_token: token::Paren,
85    pub nested: Punctuated<NestedMeta, Token![,]>,
86}
87
88impl Parse for UnnamedMetaList {
89    fn parse(input: ParseStream) -> syn::Result<Self> {
90        let content;
91        let paren_token = parenthesized!(content in input);
92
93        Ok(UnnamedMetaList {
94            paren_token,
95            nested: content.parse_terminated(NestedMeta::parse)?,
96        })
97    }
98}
99
100impl ToTokens for UnnamedMetaList {
101    fn to_tokens(&self, tokens: &mut TokenStream) {
102        self.paren_token.surround(tokens, |tokens| {
103            self.nested.to_tokens(tokens);
104        });
105    }
106}
107
108pub struct MetaList {
109    pub path: MetaPath,
110    pub paren_token: token::Paren,
111    pub nested: Punctuated<NestedMeta, Token![,]>,
112}
113
114impl Parse for MetaList {
115    fn parse(input: ParseStream) -> syn::Result<Self> {
116        let path = input.parse::<MetaPath>()?;
117        parse_meta_list_after_path(path, input)
118    }
119}
120
121impl ToTokens for MetaList {
122    fn to_tokens(&self, tokens: &mut TokenStream) {
123        self.path.to_tokens(tokens);
124        self.paren_token.surround(tokens, |tokens| {
125            self.nested.to_tokens(tokens);
126        });
127    }
128}
129
130//our own enumeration of possible values for the field values
131pub enum MetaValue {
132    Lit(Lit),
133    ExprUnnamedStruct(ExprUnnamedStruct<CustomExpr>),
134    UnnamedMetaList(UnnamedMetaList),
135}
136
137impl Parse for MetaValue {
138    fn parse(input: ParseStream) -> syn::Result<Self> {
139        if input.peek(token::Paren) {
140            let lst = input.parse::<UnnamedMetaList>()?;
141            Ok(MetaValue::UnnamedMetaList(lst))
142        } else if input.peek(token::Brace) {
143            let obj = <ExprUnnamedStruct<CustomExpr>>::parse(input)?;
144            Ok(MetaValue::ExprUnnamedStruct(obj))
145        } else {
146            let expr = Lit::parse(input)?;
147            Ok(MetaValue::Lit(expr))
148        }
149    }
150}
151
152impl ToTokens for MetaValue {
153    fn to_tokens(&self, tokens: &mut TokenStream) {
154        match self {
155            MetaValue::Lit(value) => value.to_tokens(tokens),
156            MetaValue::ExprUnnamedStruct(value) => value.to_tokens(tokens),
157            MetaValue::UnnamedMetaList(value) => value.to_tokens(tokens),
158        }
159    }
160}
161
162pub struct MetaNameValue {
163    pub path: MetaPath,
164    pub eq_token: Token![=],
165    pub value: MetaValue,
166}
167
168impl Parse for MetaNameValue {
169    fn parse(input: ParseStream) -> syn::Result<Self> {
170        let path = input.parse::<MetaPath>()?;
171        parse_meta_name_value_after_path(path, input)
172    }
173}
174impl ToTokens for MetaNameValue {
175    fn to_tokens(&self, tokens: &mut TokenStream) {
176        self.path.to_tokens(tokens);
177        self.eq_token.to_tokens(tokens);
178        self.value.to_tokens(tokens);
179    }
180}
181
182pub enum NestedMeta {
183    Meta(Meta),
184    Lit(Lit),
185    Expr(CustomExpr),
186}
187
188impl Parse for NestedMeta {
189    fn parse(input: ParseStream) -> syn::Result<Self> {
190        if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) {
191            input.parse().map(NestedMeta::Lit)
192        } else if input.peek(Ident::peek_any)
193            || input.peek(Token![::]) && input.peek3(Ident::peek_any)
194        {
195            input.parse().map(NestedMeta::Meta)
196        } else {
197            Err(input.error("expected identifier or literal"))
198        }
199    }
200}
201
202impl ToTokens for NestedMeta {
203    fn to_tokens(&self, tokens: &mut TokenStream) {
204        match self {
205            NestedMeta::Meta(value) => value.to_tokens(tokens),
206            NestedMeta::Expr(value) => value.to_tokens(tokens),
207            NestedMeta::Lit(value) => value.to_tokens(tokens),
208        }
209    }
210}
211
212pub fn parse_meta_after_path(path: MetaPath, input: ParseStream) -> syn::Result<Meta> {
213    if input.peek(token::Paren) {
214        parse_meta_list_after_path(path, input).map(Meta::List)
215    } else if input.peek(Token![=]) {
216        parse_meta_name_value_after_path(path, input).map(Meta::NameValue)
217    } else {
218        Ok(Meta::Path(path))
219    }
220}
221
222fn parse_meta_list_after_path(path: MetaPath, input: ParseStream) -> syn::Result<MetaList> {
223    let content;
224    let paren_token = parenthesized!(content in input);
225    Ok(MetaList {
226        path,
227        paren_token,
228        nested: content.parse_terminated(NestedMeta::parse)?,
229    })
230}
231
232fn parse_meta_name_value_after_path(
233    path: MetaPath,
234    input: ParseStream,
235) -> syn::Result<MetaNameValue> {
236    Ok(MetaNameValue {
237        path,
238        eq_token: input.parse()?,
239        value: input.parse()?,
240    })
241}
242
243#[cfg(test)]
244mod tests {
245    use syn::{parse::Parser, Attribute};
246
247    use super::*;
248
249    #[test]
250    fn test_meta() {
251        let attrs = Parser::parse_str(Attribute::parse_outer, "#[blah(name=\"MyVal\", active)]")
252            .expect("attribute");
253        let elem = attrs
254            .first()
255            .unwrap()
256            .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
257            .expect("Could not parse the args");
258        let elem_str = elem.to_token_stream().to_string();
259
260        assert_eq!(elem_str, "name = \"MyVal\" , active");
261    }
262
263    #[test]
264    fn test_meta_list() {
265        let attrs = Parser::parse_str(Attribute::parse_outer, "#[blah(a, b, more(one=1, two=2))]")
266            .expect("attribute");
267        let elem = attrs
268            .first()
269            .unwrap()
270            .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
271            .expect("Could not parse the args");
272        let elem_str = elem.to_token_stream().to_string();
273
274        assert_eq!(elem_str, "a , b , more (one = 1 , two = 2)");
275    }
276
277    #[test]
278    fn test_meta_unnamed_struct() {
279        let attrs = Parser::parse_str(
280            Attribute::parse_outer,
281            "#[blah(name=\"MyVal\", age=33, other={name: \"ok\"})]",
282        )
283        .expect("attribute");
284        let elem = attrs
285            .first()
286            .unwrap()
287            .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
288            .expect("Could not parse the args");
289        let elem_str = elem.to_token_stream().to_string();
290
291        assert_eq!(
292            elem_str,
293            "name = \"MyVal\" , age = 33 , other = { name : \"ok\" }"
294        );
295    }
296
297    #[test]
298    fn test_meta_unnamed_list() {
299        let attrs = Parser::parse_str(Attribute::parse_outer, "#[blah(a, b, (one=1, two=2))]")
300            .expect("attribute");
301        let elem = attrs
302            .first()
303            .unwrap()
304            .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
305            .expect("Could not parse the args");
306        let elem_str = elem.to_token_stream().to_string();
307
308        assert_eq!(elem_str, "a , b , (one = 1 , two = 2)");
309    }
310
311    #[test]
312    fn test_meta_unnamed_nested_list() {
313        let attrs = Parser::parse_str(Attribute::parse_outer, "#[blah(a, b, c = (one=1, two=2))]")
314            .expect("attribute");
315        let elem = attrs
316            .first()
317            .unwrap()
318            .parse_args_with(<Punctuated<Meta, Token![,]>>::parse_terminated)
319            .expect("Could not parse the args");
320        let elem_str = elem.to_token_stream().to_string();
321
322        assert_eq!(elem_str, "a , b , c = (one = 1 , two = 2)");
323    }
324}