1 //! Facility for interpreting structured content inside of an `Attribute`.
2 
3 use crate::error::{Error, Result};
4 use crate::ext::IdentExt as _;
5 use crate::lit::Lit;
6 use crate::parse::{ParseStream, Parser};
7 use crate::path::{Path, PathSegment};
8 use crate::punctuated::Punctuated;
9 use proc_macro2::Ident;
10 use 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 /// ```
parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()>132 pub 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]
164 pub struct ParseNestedMeta<'a> {
165     pub path: Path,
166     pub input: ParseStream<'a>,
167 }
168 
169 impl<'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     /// ```
value(&self) -> Result<ParseStream<'a>>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     /// ```
parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()>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     /// ```
error(&self, msg: impl Display) -> Error378     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 
parse_nested_meta( input: ParseStream, mut logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()>385 pub(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.
parse_meta_path(input: ParseStream) -> Result<Path>403 fn 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 }
428