syn/
meta.rs

1//! Facility for interpreting structured content inside of an `Attribute`.
2
3use crate::error::{Error, Result};
4use crate::ext::IdentExt as _;
5use crate::lit::Lit;
6use crate::parse::{ParseStream, Parser};
7use crate::path::{Path, PathSegment};
8use crate::punctuated::Punctuated;
9use proc_macro2::Ident;
10use std::fmt::Display;
11
12/// Make a parser that is usable with `parse_macro_input!` in a
13/// `#[proc_macro_attribute]` macro.
14///
15/// *Warning:* When parsing attribute args **other than** the
16/// `proc_macro::TokenStream` input of a `proc_macro_attribute`, you do **not**
17/// need this function. In several cases your callers will get worse error
18/// messages if you use this function, because the surrounding delimiter's span
19/// is concealed from attribute macros by rustc. Use
20/// [`Attribute::parse_nested_meta`] instead.
21///
22/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
23///
24/// # Example
25///
26/// This example implements an attribute macro whose invocations look like this:
27///
28/// ```
29/// # const IGNORE: &str = stringify! {
30/// #[tea(kind = "EarlGrey", hot)]
31/// struct Picard {...}
32/// # };
33/// ```
34///
35/// The "parameters" supported by the attribute are:
36///
37/// - `kind = "..."`
38/// - `hot`
39/// - `with(sugar, milk, ...)`, a comma-separated list of ingredients
40///
41/// ```
42/// # extern crate proc_macro;
43/// #
44/// use proc_macro::TokenStream;
45/// use syn::{parse_macro_input, LitStr, Path};
46///
47/// # const IGNORE: &str = stringify! {
48/// #[proc_macro_attribute]
49/// # };
50/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
51///     let mut kind: Option<LitStr> = None;
52///     let mut hot: bool = false;
53///     let mut with: Vec<Path> = Vec::new();
54///     let tea_parser = syn::meta::parser(|meta| {
55///         if meta.path.is_ident("kind") {
56///             kind = Some(meta.value()?.parse()?);
57///             Ok(())
58///         } else if meta.path.is_ident("hot") {
59///             hot = true;
60///             Ok(())
61///         } else if meta.path.is_ident("with") {
62///             meta.parse_nested_meta(|meta| {
63///                 with.push(meta.path);
64///                 Ok(())
65///             })
66///         } else {
67///             Err(meta.error("unsupported tea property"))
68///         }
69///     });
70///
71///     parse_macro_input!(args with tea_parser);
72///     eprintln!("kind={kind:?} hot={hot} with={with:?}");
73///
74///     /* ... */
75/// #   TokenStream::new()
76/// }
77/// ```
78///
79/// The `syn::meta` library will take care of dealing with the commas including
80/// trailing commas, and producing sensible error messages on unexpected input.
81///
82/// ```console
83/// error: expected `,`
84///  --> src/main.rs:3:37
85///   |
86/// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))]
87///   |                                     ^
88/// ```
89///
90/// # Example
91///
92/// Same as above but we factor out most of the logic into a separate function.
93///
94/// ```
95/// # extern crate proc_macro;
96/// #
97/// use proc_macro::TokenStream;
98/// use syn::meta::ParseNestedMeta;
99/// use syn::parse::{Parser, Result};
100/// use syn::{parse_macro_input, LitStr, Path};
101///
102/// # const IGNORE: &str = stringify! {
103/// #[proc_macro_attribute]
104/// # };
105/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
106///     let mut attrs = TeaAttributes::default();
107///     let tea_parser = syn::meta::parser(|meta| attrs.parse(meta));
108///     parse_macro_input!(args with tea_parser);
109///
110///     /* ... */
111/// #   TokenStream::new()
112/// }
113///
114/// #[derive(Default)]
115/// struct TeaAttributes {
116///     kind: Option<LitStr>,
117///     hot: bool,
118///     with: Vec<Path>,
119/// }
120///
121/// impl TeaAttributes {
122///     fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
123///         if meta.path.is_ident("kind") {
124///             self.kind = Some(meta.value()?.parse()?);
125///             Ok(())
126///         } else /* just like in last example */
127/// #           { unimplemented!() }
128///
129///     }
130/// }
131/// ```
132pub fn parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()> {
133    |input: ParseStream| {
134        if input.is_empty() {
135            Ok(())
136        } else {
137            parse_nested_meta(input, logic)
138        }
139    }
140}
141
142/// Context for parsing a single property in the conventional syntax for
143/// structured attributes.
144///
145/// # Examples
146///
147/// Refer to usage examples on the following two entry-points:
148///
149/// - [`Attribute::parse_nested_meta`] if you have an entire `Attribute` to
150///   parse. Always use this if possible. Generally this is able to produce
151///   better error messages because `Attribute` holds span information for all
152///   of the delimiters therein.
153///
154/// - [`syn::meta::parser`] if you are implementing a `proc_macro_attribute`
155///   macro and parsing the arguments to the attribute macro, i.e. the ones
156///   written in the same attribute that dispatched the macro invocation. Rustc
157///   does not pass span information for the surrounding delimiters into the
158///   attribute macro invocation in this situation, so error messages might be
159///   less precise.
160///
161/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
162/// [`syn::meta::parser`]: crate::meta::parser
163#[non_exhaustive]
164pub struct ParseNestedMeta<'a> {
165    pub path: Path,
166    pub input: ParseStream<'a>,
167}
168
169impl<'a> ParseNestedMeta<'a> {
170    /// Used when parsing `key = "value"` syntax.
171    ///
172    /// All it does is advance `meta.input` past the `=` sign in the input. You
173    /// could accomplish the same effect by writing
174    /// `meta.parse::<Token![=]>()?`, so at most it is a minor convenience to
175    /// use `meta.value()?`.
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use syn::{parse_quote, Attribute, LitStr};
181    ///
182    /// let attr: Attribute = parse_quote! {
183    ///     #[tea(kind = "EarlGrey")]
184    /// };
185    ///                                          // conceptually:
186    /// if attr.path().is_ident("tea") {         // this parses the `tea`
187    ///     attr.parse_nested_meta(|meta| {      // this parses the `(`
188    ///         if meta.path.is_ident("kind") {  // this parses the `kind`
189    ///             let value = meta.value()?;   // this parses the `=`
190    ///             let s: LitStr = value.parse()?;  // this parses `"EarlGrey"`
191    ///             if s.value() == "EarlGrey" {
192    ///                 // ...
193    ///             }
194    ///             Ok(())
195    ///         } else {
196    ///             Err(meta.error("unsupported attribute"))
197    ///         }
198    ///     })?;
199    /// }
200    /// # anyhow::Ok(())
201    /// ```
202    pub fn value(&self) -> Result<ParseStream<'a>> {
203        self.input.parse::<Token![=]>()?;
204        Ok(self.input)
205    }
206
207    /// Used when parsing `list(...)` syntax **if** the content inside the
208    /// nested parentheses is also expected to conform to Rust's structured
209    /// attribute convention.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// use syn::{parse_quote, Attribute};
215    ///
216    /// let attr: Attribute = parse_quote! {
217    ///     #[tea(with(sugar, milk))]
218    /// };
219    ///
220    /// if attr.path().is_ident("tea") {
221    ///     attr.parse_nested_meta(|meta| {
222    ///         if meta.path.is_ident("with") {
223    ///             meta.parse_nested_meta(|meta| {  // <---
224    ///                 if meta.path.is_ident("sugar") {
225    ///                     // Here we can go even deeper if needed.
226    ///                     Ok(())
227    ///                 } else if meta.path.is_ident("milk") {
228    ///                     Ok(())
229    ///                 } else {
230    ///                     Err(meta.error("unsupported ingredient"))
231    ///                 }
232    ///             })
233    ///         } else {
234    ///             Err(meta.error("unsupported tea property"))
235    ///         }
236    ///     })?;
237    /// }
238    /// # anyhow::Ok(())
239    /// ```
240    ///
241    /// # Counterexample
242    ///
243    /// If you don't need `parse_nested_meta`'s help in parsing the content
244    /// written within the nested parentheses, keep in mind that you can always
245    /// just parse it yourself from the exposed ParseStream. Rust syntax permits
246    /// arbitrary tokens within those parentheses so for the crazier stuff,
247    /// `parse_nested_meta` is not what you want.
248    ///
249    /// ```
250    /// use syn::{parenthesized, parse_quote, Attribute, LitInt};
251    ///
252    /// let attr: Attribute = parse_quote! {
253    ///     #[repr(align(32))]
254    /// };
255    ///
256    /// let mut align: Option<LitInt> = None;
257    /// if attr.path().is_ident("repr") {
258    ///     attr.parse_nested_meta(|meta| {
259    ///         if meta.path.is_ident("align") {
260    ///             let content;
261    ///             parenthesized!(content in meta.input);
262    ///             align = Some(content.parse()?);
263    ///             Ok(())
264    ///         } else {
265    ///             Err(meta.error("unsupported repr"))
266    ///         }
267    ///     })?;
268    /// }
269    /// # anyhow::Ok(())
270    /// ```
271    pub fn parse_nested_meta(
272        &self,
273        logic: impl FnMut(ParseNestedMeta) -> Result<()>,
274    ) -> Result<()> {
275        let content;
276        parenthesized!(content in self.input);
277        parse_nested_meta(&content, logic)
278    }
279
280    /// Report that the attribute's content did not conform to expectations.
281    ///
282    /// The span of the resulting error will cover `meta.path` *and* everything
283    /// that has been parsed so far since it.
284    ///
285    /// There are 2 ways you might call this. First, if `meta.path` is not
286    /// something you recognize:
287    ///
288    /// ```
289    /// # use syn::Attribute;
290    /// #
291    /// # fn example(attr: &Attribute) -> syn::Result<()> {
292    /// attr.parse_nested_meta(|meta| {
293    ///     if meta.path.is_ident("kind") {
294    ///         // ...
295    ///         Ok(())
296    ///     } else {
297    ///         Err(meta.error("unsupported tea property"))
298    ///     }
299    /// })?;
300    /// # Ok(())
301    /// # }
302    /// ```
303    ///
304    /// In this case, it behaves exactly like
305    /// `syn::Error::new_spanned(&meta.path, "message...")`.
306    ///
307    /// ```console
308    /// error: unsupported tea property
309    ///  --> src/main.rs:3:26
310    ///   |
311    /// 3 | #[tea(kind = "EarlGrey", wat = "foo")]
312    ///   |                          ^^^
313    /// ```
314    ///
315    /// More usefully, the second place is if you've already parsed a value but
316    /// have decided not to accept the value:
317    ///
318    /// ```
319    /// # use syn::Attribute;
320    /// #
321    /// # fn example(attr: &Attribute) -> syn::Result<()> {
322    /// use syn::Expr;
323    ///
324    /// attr.parse_nested_meta(|meta| {
325    ///     if meta.path.is_ident("kind") {
326    ///         let expr: Expr = meta.value()?.parse()?;
327    ///         match expr {
328    ///             Expr::Lit(expr) => /* ... */
329    /// #               unimplemented!(),
330    ///             Expr::Path(expr) => /* ... */
331    /// #               unimplemented!(),
332    ///             Expr::Macro(expr) => /* ... */
333    /// #               unimplemented!(),
334    ///             _ => Err(meta.error("tea kind must be a string literal, path, or macro")),
335    ///         }
336    ///     } else /* as above */
337    /// #       { unimplemented!() }
338    ///
339    /// })?;
340    /// # Ok(())
341    /// # }
342    /// ```
343    ///
344    /// ```console
345    /// error: tea kind must be a string literal, path, or macro
346    ///  --> src/main.rs:3:7
347    ///   |
348    /// 3 | #[tea(kind = async { replicator.await })]
349    ///   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
350    /// ```
351    ///
352    /// Often you may want to use `syn::Error::new_spanned` even in this
353    /// situation. In the above code, that would be:
354    ///
355    /// ```
356    /// # use syn::{Error, Expr};
357    /// #
358    /// # fn example(expr: Expr) -> syn::Result<()> {
359    ///     match expr {
360    ///         Expr::Lit(expr) => /* ... */
361    /// #           unimplemented!(),
362    ///         Expr::Path(expr) => /* ... */
363    /// #           unimplemented!(),
364    ///         Expr::Macro(expr) => /* ... */
365    /// #           unimplemented!(),
366    ///         _ => Err(Error::new_spanned(expr, "unsupported expression type for `kind`")),
367    ///     }
368    /// # }
369    /// ```
370    ///
371    /// ```console
372    /// error: unsupported expression type for `kind`
373    ///  --> src/main.rs:3:14
374    ///   |
375    /// 3 | #[tea(kind = async { replicator.await })]
376    ///   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^
377    /// ```
378    pub fn error(&self, msg: impl Display) -> Error {
379        let start_span = self.path.segments[0].ident.span();
380        let end_span = self.input.cursor().prev_span();
381        crate::error::new2(start_span, end_span, msg)
382    }
383}
384
385pub(crate) fn parse_nested_meta(
386    input: ParseStream,
387    mut logic: impl FnMut(ParseNestedMeta) -> Result<()>,
388) -> Result<()> {
389    loop {
390        let path = input.call(parse_meta_path)?;
391        logic(ParseNestedMeta { path, input })?;
392        if input.is_empty() {
393            return Ok(());
394        }
395        input.parse::<Token![,]>()?;
396        if input.is_empty() {
397            return Ok(());
398        }
399    }
400}
401
402// Like Path::parse_mod_style, but accepts keywords in the path.
403fn parse_meta_path(input: ParseStream) -> Result<Path> {
404    Ok(Path {
405        leading_colon: input.parse()?,
406        segments: {
407            let mut segments = Punctuated::new();
408            if input.peek(Ident::peek_any) {
409                let ident = Ident::parse_any(input)?;
410                segments.push_value(PathSegment::from(ident));
411            } else if input.is_empty() {
412                return Err(input.error("expected nested attribute"));
413            } else if input.peek(Lit) {
414                return Err(input.error("unexpected literal in nested attribute, expected ident"));
415            } else {
416                return Err(input.error("unexpected token in nested attribute, expected ident"));
417            }
418            while input.peek(Token![::]) {
419                let punct = input.parse()?;
420                segments.push_punct(punct);
421                let ident = Ident::parse_any(input)?;
422                segments.push_value(PathSegment::from(ident));
423            }
424            segments
425        },
426    })
427}