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 /// ```
parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()>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 /// ```
value(&self) -> Result<ParseStream<'a>>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 /// ```
parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()>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 /// ```
error(&self, msg: impl Display) -> Error380 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
parse_nested_meta( input: ParseStream, mut logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()>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.
parse_meta_path(input: ParseStream) -> Result<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