Skip to main content

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, StepCursor},
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<NestedMeta> {
42    if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) {
43        parse_meta_list_after_path(path, input)
44            .map(Meta::List)
45            .map(NestedMeta::Meta)
46    } else if input.peek(Token![=]) {
47        Ok(match parse_meta_name_value_after_path(path, input)? {
48            MetaNameValueAnyRhs::ValidExpr(meta) => NestedMeta::Meta(Meta::NameValue(meta)),
49            MetaNameValueAnyRhs::InvalidExpr(meta) => NestedMeta::NameValueInvalidExpr(meta),
50        })
51    } else {
52        Ok(NestedMeta::Meta(Meta::Path(path)))
53    }
54}
55
56fn parse_meta_list_after_path<'a>(path: Path, input: ParseStream<'a>) -> syn::Result<MetaList> {
57    let (delimiter, tokens) = input.step(|cursor| {
58        if let Some((TokenTree::Group(g), rest)) = cursor.token_tree() {
59            let span = g.delim_span();
60            let delimiter = match g.delimiter() {
61                Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
62                Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
63                Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
64                Delimiter::None => {
65                    return Err(cursor.error("expected delimiter"));
66                }
67            };
68            Ok(((delimiter, g.stream()), rest))
69        } else {
70            Err(cursor.error("expected delimiter"))
71        }
72    })?;
73    Ok(MetaList {
74        path,
75        delimiter,
76        tokens,
77    })
78}
79
80enum MetaNameValueAnyRhs {
81    /// RHS after `=` is a valid [`syn::Expr`]
82    ValidExpr(MetaNameValue),
83    /// RHS after `=` is invalid [`syn::Expr`]
84    InvalidExpr(MetaNameValueInvalidExpr),
85}
86
87fn parse_meta_name_value_after_path<'a>(
88    path: Path,
89    input: ParseStream<'a>,
90) -> syn::Result<MetaNameValueAnyRhs> {
91    let eq_token: Token![=] = input.parse()?;
92    let ahead = input.fork();
93    let lit: Option<Lit> = ahead.parse()?;
94    let value = if let (Some(lit), true) = (lit, ahead.is_empty()) {
95        input.advance_to(&ahead);
96        Expr::Lit(ExprLit {
97            attrs: Vec::new(),
98            lit,
99        })
100    } else if input.peek(Token![#]) && input.peek2(token::Bracket) {
101        return Err(input.error("unexpected attribute inside of attribute"));
102    } else {
103        // `input.parse()` advances the original parser, but we
104        // want to backtrack in case parsing into an `Expr` fails
105        let input_fork = input.fork();
106
107        match input.parse() {
108            Ok(expr) => expr,
109            // This isn't a valid expression, it might be something like `pub(in crate::module)`
110            // so we want to save that and let the user parse it (e.g. into a [`syn::Visibility`]).
111            //
112            // For more details, see docs of `darling::util::decode_if_verbatim`
113            Err(error) => {
114                fn eat_until_comma<'c>(
115                    cursor: StepCursor<'c, '_>,
116                ) -> syn::Result<(TokenStream, syn::buffer::Cursor<'c>)> {
117                    let mut rest = *cursor;
118                    let mut ts = TokenStream::new();
119                    while let Some((tt, next)) = rest.token_tree() {
120                        match tt {
121                            TokenTree::Punct(punct) if punct.as_char() == ',' => {
122                                break;
123                            }
124                            tt => {
125                                ts.extend([tt]);
126                                rest = next
127                            }
128                        }
129                    }
130                    Ok((ts, rest))
131                }
132
133                // Eat everything from the start of the attribute, until the ',' token
134                // We'll then parse this into user's custom implementation of `FromMeta::from_verbatim`,
135                // if it exists.
136                let verbatim_input = input_fork.step(eat_until_comma)?;
137
138                // Advance the original parser past the ',' token, so the next
139                // attributes are properly parsed
140                input.step(eat_until_comma)?;
141
142                return Ok(MetaNameValueAnyRhs::InvalidExpr(MetaNameValueInvalidExpr {
143                    path,
144                    eq_token,
145                    value: verbatim_input,
146                    error: error.into(),
147                }));
148            }
149        }
150    };
151    Ok(MetaNameValueAnyRhs::ValidExpr(MetaNameValue {
152        path,
153        eq_token,
154        value,
155    }))
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159// Addressing this would break many users of the crate.
160#[allow(clippy::large_enum_variant)]
161pub enum NestedMeta {
162    Meta(syn::Meta),
163    Lit(syn::Lit),
164    NameValueInvalidExpr(MetaNameValueInvalidExpr),
165}
166
167/// Represents a name-value attribute like `foo = ...` where we failed
168/// to parse the RHS (`...`) as a [`syn::Expr`].
169///
170/// For example:
171///
172/// ```ignore
173/// #[processor(vis = pub(crate))]
174/// ```
175///
176/// We first try to parse that  `vis = pub(crate)` into a [`syn::Meta::NameValue`].
177/// This requires the RHS (`pub(crate)`) to be a valid [`syn::Expr`].
178///
179/// Because `pub(crate)` is not a valid expression, we store the [`TokenStream`]
180/// corresponding to `pub(crate)` in this struct, as well as the original [`syn::Error`]
181/// obtained from <code>\<[syn::Expr] as [Parse](syn::parse::Parse)\>::parse(quote!(pub(crate)))</code>
182///
183/// This [`syn::Error`] is used for good error messages when a type does not implement
184/// [`FromMeta::from_invalid_expr`], as the default implementation of that function just
185/// returns the stored error.
186#[derive(Debug, Clone)]
187pub struct MetaNameValueInvalidExpr {
188    /// In `vis = pub(crate)`, this is the `vis`
189    pub path: Path,
190    /// In `vis = pub(crate)`, this is the `=`
191    pub eq_token: Token![=],
192    /// The original input that we tried to parse as a `syn::Expr`, but failed
193    ///
194    /// In `vis = pub(crate)`, this is `pub(crate)`
195    pub value: TokenStream,
196    /// The error that we got from failing to parse `input` as a `syn::Expr`
197    ///
198    /// In `vis = pub(crate)`, this is:
199    ///
200    /// ```ignore
201    /// <syn::Expr as syn::parse::Parse>::parse(quote!(pub(crate))).unwrap_err()
202    /// ```
203    pub error: crate::Error,
204}
205
206impl ToTokens for MetaNameValueInvalidExpr {
207    fn to_tokens(&self, tokens: &mut TokenStream) {
208        self.path.to_tokens(tokens);
209        self.eq_token.to_tokens(tokens);
210        self.value.to_tokens(tokens);
211    }
212}
213
214impl PartialEq for MetaNameValueInvalidExpr {
215    fn eq(&self, other: &Self) -> bool {
216        self.path == other.path
217            && self.eq_token == other.eq_token
218            && self.value.to_string() == other.value.to_string()
219    }
220}
221
222impl Eq for MetaNameValueInvalidExpr {}
223
224impl NestedMeta {
225    pub fn parse_meta_list(tokens: TokenStream) -> syn::Result<Vec<Self>> {
226        syn::punctuated::Punctuated::<NestedMeta, Token![,]>::parse_terminated
227            .parse2(tokens)
228            .map(|punctuated| punctuated.into_iter().collect())
229    }
230}
231
232impl syn::parse::Parse for NestedMeta {
233    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
234        // The difference from `syn::Meta` and `NestedMeta`:
235        // 1. `syn::Meta` requires a path, named value, or meta list only.
236        //   1. `outer(path)`
237        //   2. `outer(key = "value")`, the identifier of the key cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
238        //   3. `outer(inner(..))`, the identifier of the inner meta cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
239        // 2. `NestedMeta` allows a literal, which is useful for attributes like `#[outer("foo")]`.
240        //   1. `outer("foo")`
241        //   2. `outer(42)`
242        //   3. `outer(key = "value")`, the identifier of the key can be strict keywords in rust, like `self`, `super`, `crate`, etc.
243        //     1. `outer(self = "value")`
244        //     2. `outer(type = "Foo")`
245        //     3. `outer(crate = "bar")`
246        //   4. `outer(inner(..))`, the identifier of the inner meta can be strict keywords in rust, like `self`, `super`, `crate`, etc.
247        //     1. `outer(self(..))`
248        //     2. `outer(super(..))`
249        //     3. `outer(crate(..))`
250        if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(syn::Token![=])) {
251            input.parse().map(Self::Lit)
252        } else if input.peek(syn::Ident::peek_any)
253            || input.peek(Token![::]) && input.peek3(syn::Ident::peek_any)
254        {
255            let path = parse_meta_path(input)?;
256            parse_meta_after_path(path, input)
257        } else {
258            Err(input.error("expected identifier or literal"))
259        }
260    }
261}
262
263impl ToTokens for NestedMeta {
264    fn to_tokens(&self, tokens: &mut TokenStream) {
265        match self {
266            NestedMeta::Meta(meta) => meta.to_tokens(tokens),
267            NestedMeta::Lit(lit) => lit.to_tokens(tokens),
268            NestedMeta::NameValueInvalidExpr(meta) => meta.to_tokens(tokens),
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use syn::parse_quote;
276
277    use super::*;
278
279    /// Absolute paths (paths prefixed with `::`) were not recognized as valid
280    ///
281    /// Issue: <https://github.com/TedDriggs/darling/issues/394>
282    #[test]
283    fn absolute_path() {
284        let input: NestedMeta = parse_quote!(::prost::Message);
285        assert_eq!(
286            input,
287            NestedMeta::Meta(Meta::Path(parse_quote!(::prost::Message)))
288        );
289    }
290}