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