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