darling_core/ast/data/
nested_meta.rs

1use proc_macro2::{Delimiter, TokenStream, TokenTree};
2use quote::ToTokens;
3use syn::{
4    ext::IdentExt,
5    parse::{discouraged::Speculative, ParseStream, Parser},
6    punctuated::Punctuated,
7    token::{self, Brace, Bracket, Paren},
8    Expr, ExprLit, Ident, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue, Path, PathSegment,
9    Token,
10};
11
12fn parse_meta_path<'a>(input: ParseStream<'a>) -> syn::Result<Path> {
13    Ok(Path {
14        leading_colon: input.parse()?,
15        segments: {
16            let mut segments = Punctuated::new();
17            loop {
18                // Allow all identifiers, including keywords.
19                if !input.peek(Ident::peek_any) {
20                    break;
21                }
22
23                let ident = Ident::parse_any(input)?;
24                segments.push_value(PathSegment::from(ident));
25                if !input.peek(Token![::]) {
26                    break;
27                }
28                let punct = input.parse()?;
29                segments.push_punct(punct);
30            }
31            if segments.is_empty() {
32                return Err(input.parse::<Ident>().unwrap_err());
33            } else if segments.trailing_punct() {
34                return Err(input.error("expected path segment after `::`"));
35            }
36            segments
37        },
38    })
39}
40
41fn parse_meta_after_path<'a>(path: Path, input: ParseStream<'a>) -> syn::Result<Meta> {
42    if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) {
43        parse_meta_list_after_path(path, input).map(Meta::List)
44    } else if input.peek(Token![=]) {
45        parse_meta_name_value_after_path(path, input).map(Meta::NameValue)
46    } else {
47        Ok(Meta::Path(path))
48    }
49}
50
51fn parse_meta_list_after_path<'a>(path: Path, input: ParseStream<'a>) -> syn::Result<MetaList> {
52    let (delimiter, tokens) = input.step(|cursor| {
53        if let Some((TokenTree::Group(g), rest)) = cursor.token_tree() {
54            let span = g.delim_span();
55            let delimiter = match g.delimiter() {
56                Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
57                Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
58                Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
59                Delimiter::None => {
60                    return Err(cursor.error("expected delimiter"));
61                }
62            };
63            Ok(((delimiter, g.stream()), rest))
64        } else {
65            Err(cursor.error("expected delimiter"))
66        }
67    })?;
68    Ok(MetaList {
69        path,
70        delimiter,
71        tokens,
72    })
73}
74
75fn parse_meta_name_value_after_path<'a>(
76    path: Path,
77    input: ParseStream<'a>,
78) -> syn::Result<MetaNameValue> {
79    let eq_token: Token![=] = input.parse()?;
80    let ahead = input.fork();
81    let lit: Option<Lit> = ahead.parse()?;
82    let value = if let (Some(lit), true) = (lit, ahead.is_empty()) {
83        input.advance_to(&ahead);
84        Expr::Lit(ExprLit {
85            attrs: Vec::new(),
86            lit,
87        })
88    } else if input.peek(Token![#]) && input.peek2(token::Bracket) {
89        return Err(input.error("unexpected attribute inside of attribute"));
90    } else {
91        input.parse()?
92    };
93    Ok(MetaNameValue {
94        path,
95        eq_token,
96        value,
97    })
98}
99
100#[derive(Debug, Clone)]
101// Addressing this would break many users of the crate.
102#[allow(clippy::large_enum_variant)]
103pub enum NestedMeta {
104    Meta(syn::Meta),
105    Lit(syn::Lit),
106}
107
108impl NestedMeta {
109    pub fn parse_meta_list(tokens: TokenStream) -> syn::Result<Vec<Self>> {
110        syn::punctuated::Punctuated::<NestedMeta, Token![,]>::parse_terminated
111            .parse2(tokens)
112            .map(|punctuated| punctuated.into_iter().collect())
113    }
114}
115
116impl syn::parse::Parse for NestedMeta {
117    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
118        // The difference from `syn::Meta` and `NestedMeta`:
119        // 1. `syn::Meta` requires a path, named value, or meta list only.
120        //   1. `outer(path)`
121        //   2. `outer(key = "value")`, the identifier of the key cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
122        //   3. `outer(inner(..))`, the identifier of the inner meta cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
123        // 2. `NestedMeta` allows a literal, which is useful for attributes like `#[outer("foo")]`.
124        //   1. `outer("foo")`
125        //   2. `outer(42)`
126        //   3. `outer(key = "value")`, the identifier of the key can be strict keywords in rust, like `self`, `super`, `crate`, etc.
127        //     1. `outer(self = "value")`
128        //     2. `outer(type = "Foo")`
129        //     3. `outer(crate = "bar")`
130        //   4. `outer(inner(..))`, the identifier of the inner meta can be strict keywords in rust, like `self`, `super`, `crate`, etc.
131        //     1. `outer(self(..))`
132        //     2. `outer(super(..))`
133        //     3. `outer(crate(..))`
134        if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(syn::Token![=])) {
135            input.parse().map(Self::Lit)
136        } else if input.peek(syn::Ident::peek_any) {
137            let path = parse_meta_path(input)?;
138            parse_meta_after_path(path, input).map(Self::Meta)
139        } else {
140            Err(input.error("expected identifier or literal"))
141        }
142    }
143}
144
145impl ToTokens for NestedMeta {
146    fn to_tokens(&self, tokens: &mut TokenStream) {
147        match self {
148            NestedMeta::Meta(meta) => meta.to_tokens(tokens),
149            NestedMeta::Lit(lit) => lit.to_tokens(tokens),
150        }
151    }
152}