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