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