| //! Facility for interpreting structured content inside of an `Attribute`. |
| |
| use crate::ext::IdentExt; |
| use crate::lit::Lit; |
| use crate::parse::{Error, ParseStream, Parser, Result}; |
| use crate::path::{Path, PathSegment}; |
| use crate::punctuated::Punctuated; |
| use proc_macro2::Ident; |
| use std::fmt::Display; |
| |
| /// Make a parser that is usable with `parse_macro_input!` in a |
| /// `#[proc_macro_attribute]` macro. |
| /// |
| /// *Warning:* When parsing attribute args **other than** the |
| /// `proc_macro::TokenStream` input of a `proc_macro_attribute`, you do **not** |
| /// need this function. In several cases your callers will get worse error |
| /// messages if you use this function, because the surrounding delimiter's span |
| /// is concealed from attribute macros by rustc. Use |
| /// [`Attribute::parse_nested_meta`] instead. |
| /// |
| /// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta |
| /// |
| /// # Example |
| /// |
| /// This example implements an attribute macro whose invocations look like this: |
| /// |
| /// ``` |
| /// # const IGNORE: &str = stringify! { |
| /// #[tea(kind = "EarlGrey", hot)] |
| /// struct Picard {...} |
| /// # }; |
| /// ``` |
| /// |
| /// The "parameters" supported by the attribute are: |
| /// |
| /// - `kind = "..."` |
| /// - `hot` |
| /// - `with(sugar, milk, ...)`, a comma-separated list of ingredients |
| /// |
| /// ``` |
| /// # extern crate proc_macro; |
| /// # |
| /// use proc_macro::TokenStream; |
| /// use syn::{parse_macro_input, LitStr, Path}; |
| /// |
| /// # const IGNORE: &str = stringify! { |
| /// #[proc_macro_attribute] |
| /// # }; |
| /// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream { |
| /// let mut kind: Option<LitStr> = None; |
| /// let mut hot: bool = false; |
| /// let mut with: Vec<Path> = Vec::new(); |
| /// let tea_parser = syn::meta::parser(|meta| { |
| /// if meta.path.is_ident("kind") { |
| /// kind = Some(meta.value()?.parse()?); |
| /// Ok(()) |
| /// } else if meta.path.is_ident("hot") { |
| /// hot = true; |
| /// Ok(()) |
| /// } else if meta.path.is_ident("with") { |
| /// meta.parse_nested_meta(|meta| { |
| /// with.push(meta.path); |
| /// Ok(()) |
| /// }) |
| /// } else { |
| /// Err(meta.error("unsupported tea property")) |
| /// } |
| /// }); |
| /// |
| /// parse_macro_input!(args with tea_parser); |
| /// eprintln!("kind={kind:?} hot={hot} with={with:?}"); |
| /// |
| /// /* ... */ |
| /// # TokenStream::new() |
| /// } |
| /// ``` |
| /// |
| /// The `syn::meta` library will take care of dealing with the commas including |
| /// trailing commas, and producing sensible error messages on unexpected input. |
| /// |
| /// ```console |
| /// error: expected `,` |
| /// --> src/main.rs:3:37 |
| /// | |
| /// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))] |
| /// | ^ |
| /// ``` |
| /// |
| /// # Example |
| /// |
| /// Same as above but we factor out most of the logic into a separate function. |
| /// |
| /// ``` |
| /// # extern crate proc_macro; |
| /// # |
| /// use proc_macro::TokenStream; |
| /// use syn::meta::ParseNestedMeta; |
| /// use syn::parse::{Parser, Result}; |
| /// use syn::{parse_macro_input, LitStr, Path}; |
| /// |
| /// # const IGNORE: &str = stringify! { |
| /// #[proc_macro_attribute] |
| /// # }; |
| /// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream { |
| /// let mut attrs = TeaAttributes::default(); |
| /// let tea_parser = syn::meta::parser(|meta| attrs.parse(meta)); |
| /// parse_macro_input!(args with tea_parser); |
| /// |
| /// /* ... */ |
| /// # TokenStream::new() |
| /// } |
| /// |
| /// #[derive(Default)] |
| /// struct TeaAttributes { |
| /// kind: Option<LitStr>, |
| /// hot: bool, |
| /// with: Vec<Path>, |
| /// } |
| /// |
| /// impl TeaAttributes { |
| /// fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> { |
| /// if meta.path.is_ident("kind") { |
| /// self.kind = Some(meta.value()?.parse()?); |
| /// Ok(()) |
| /// } else /* just like in last example */ |
| /// # { unimplemented!() } |
| /// |
| /// } |
| /// } |
| /// ``` |
| pub fn parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()> { |
| |input: ParseStream| { |
| if input.is_empty() { |
| Ok(()) |
| } else { |
| parse_nested_meta(input, logic) |
| } |
| } |
| } |
| |
| /// Context for parsing a single property in the conventional syntax for |
| /// structured attributes. |
| /// |
| /// # Examples |
| /// |
| /// Refer to usage examples on the following two entry-points: |
| /// |
| /// - [`Attribute::parse_nested_meta`] if you have an entire `Attribute` to |
| /// parse. Always use this if possible. Generally this is able to produce |
| /// better error messages because `Attribute` holds span information for all |
| /// of the delimiters therein. |
| /// |
| /// - [`syn::meta::parser`] if you are implementing a `proc_macro_attribute` |
| /// macro and parsing the arguments to the attribute macro, i.e. the ones |
| /// written in the same attribute that dispatched the macro invocation. Rustc |
| /// does not pass span information for the surrounding delimiters into the |
| /// attribute macro invocation in this situation, so error messages might be |
| /// less precise. |
| /// |
| /// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta |
| /// [`syn::meta::parser`]: crate::meta::parser |
| #[non_exhaustive] |
| pub struct ParseNestedMeta<'a> { |
| pub path: Path, |
| pub input: ParseStream<'a>, |
| } |
| |
| impl<'a> ParseNestedMeta<'a> { |
| /// Used when parsing `key = "value"` syntax. |
| /// |
| /// All it does is advance `meta.input` past the `=` sign in the input. You |
| /// could accomplish the same effect by writing |
| /// `meta.parse::<Token![=]>()?`, so at most it is a minor convenience to |
| /// use `meta.value()?`. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use syn::{parse_quote, Attribute, LitStr}; |
| /// |
| /// let attr: Attribute = parse_quote! { |
| /// #[tea(kind = "EarlGrey")] |
| /// }; |
| /// // conceptually: |
| /// if attr.path().is_ident("tea") { // this parses the `tea` |
| /// attr.parse_nested_meta(|meta| { // this parses the `(` |
| /// if meta.path.is_ident("kind") { // this parses the `kind` |
| /// let value = meta.value()?; // this parses the `=` |
| /// let s: LitStr = value.parse()?; // this parses `"EarlGrey"` |
| /// if s.value() == "EarlGrey" { |
| /// // ... |
| /// } |
| /// Ok(()) |
| /// } else { |
| /// Err(meta.error("unsupported attribute")) |
| /// } |
| /// })?; |
| /// } |
| /// # anyhow::Ok(()) |
| /// ``` |
| pub fn value(&self) -> Result<ParseStream<'a>> { |
| self.input.parse::<Token![=]>()?; |
| Ok(self.input) |
| } |
| |
| /// Used when parsing `list(...)` syntax **if** the content inside the |
| /// nested parentheses is also expected to conform to Rust's structured |
| /// attribute convention. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use syn::{parse_quote, Attribute}; |
| /// |
| /// let attr: Attribute = parse_quote! { |
| /// #[tea(with(sugar, milk))] |
| /// }; |
| /// |
| /// if attr.path().is_ident("tea") { |
| /// attr.parse_nested_meta(|meta| { |
| /// if meta.path.is_ident("with") { |
| /// meta.parse_nested_meta(|meta| { // <--- |
| /// if meta.path.is_ident("sugar") { |
| /// // Here we can go even deeper if needed. |
| /// Ok(()) |
| /// } else if meta.path.is_ident("milk") { |
| /// Ok(()) |
| /// } else { |
| /// Err(meta.error("unsupported ingredient")) |
| /// } |
| /// }) |
| /// } else { |
| /// Err(meta.error("unsupported tea property")) |
| /// } |
| /// })?; |
| /// } |
| /// # anyhow::Ok(()) |
| /// ``` |
| /// |
| /// # Counterexample |
| /// |
| /// If you don't need `parse_nested_meta`'s help in parsing the content |
| /// written within the nested parentheses, keep in mind that you can always |
| /// just parse it yourself from the exposed ParseStream. Rust syntax permits |
| /// arbitrary tokens within those parentheses so for the crazier stuff, |
| /// `parse_nested_meta` is not what you want. |
| /// |
| /// ``` |
| /// use syn::{parenthesized, parse_quote, Attribute, LitInt}; |
| /// |
| /// let attr: Attribute = parse_quote! { |
| /// #[repr(align(32))] |
| /// }; |
| /// |
| /// let mut align: Option<LitInt> = None; |
| /// if attr.path().is_ident("repr") { |
| /// attr.parse_nested_meta(|meta| { |
| /// if meta.path.is_ident("align") { |
| /// let content; |
| /// parenthesized!(content in meta.input); |
| /// align = Some(content.parse()?); |
| /// Ok(()) |
| /// } else { |
| /// Err(meta.error("unsupported repr")) |
| /// } |
| /// })?; |
| /// } |
| /// # anyhow::Ok(()) |
| /// ``` |
| pub fn parse_nested_meta( |
| &self, |
| logic: impl FnMut(ParseNestedMeta) -> Result<()>, |
| ) -> Result<()> { |
| let content; |
| parenthesized!(content in self.input); |
| parse_nested_meta(&content, logic) |
| } |
| |
| /// Report that the attribute's content did not conform to expectations. |
| /// |
| /// The span of the resulting error will cover `meta.path` *and* everything |
| /// that has been parsed so far since it. |
| /// |
| /// There are 2 ways you might call this. First, if `meta.path` is not |
| /// something you recognize: |
| /// |
| /// ``` |
| /// # use syn::Attribute; |
| /// # |
| /// # fn example(attr: &Attribute) -> syn::Result<()> { |
| /// attr.parse_nested_meta(|meta| { |
| /// if meta.path.is_ident("kind") { |
| /// // ... |
| /// Ok(()) |
| /// } else { |
| /// Err(meta.error("unsupported tea property")) |
| /// } |
| /// })?; |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// In this case, it behaves exactly like |
| /// `syn::Error::new_spanned(&meta.path, "message...")`. |
| /// |
| /// ```console |
| /// error: unsupported tea property |
| /// --> src/main.rs:3:26 |
| /// | |
| /// 3 | #[tea(kind = "EarlGrey", wat = "foo")] |
| /// | ^^^ |
| /// ``` |
| /// |
| /// More usefully, the second place is if you've already parsed a value but |
| /// have decided not to accept the value: |
| /// |
| /// ``` |
| /// # use syn::Attribute; |
| /// # |
| /// # fn example(attr: &Attribute) -> syn::Result<()> { |
| /// use syn::Expr; |
| /// |
| /// attr.parse_nested_meta(|meta| { |
| /// if meta.path.is_ident("kind") { |
| /// let expr: Expr = meta.value()?.parse()?; |
| /// match expr { |
| /// Expr::Lit(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// Expr::Path(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// Expr::Macro(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// _ => Err(meta.error("tea kind must be a string literal, path, or macro")), |
| /// } |
| /// } else /* as above */ |
| /// # { unimplemented!() } |
| /// |
| /// })?; |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// ```console |
| /// error: tea kind must be a string literal, path, or macro |
| /// --> src/main.rs:3:7 |
| /// | |
| /// 3 | #[tea(kind = async { replicator.await })] |
| /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| /// ``` |
| /// |
| /// Often you may want to use `syn::Error::new_spanned` even in this |
| /// situation. In the above code, that would be: |
| /// |
| /// ``` |
| /// # use syn::{Error, Expr}; |
| /// # |
| /// # fn example(expr: Expr) -> syn::Result<()> { |
| /// match expr { |
| /// Expr::Lit(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// Expr::Path(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// Expr::Macro(expr) => /* ... */ |
| /// # unimplemented!(), |
| /// _ => Err(Error::new_spanned(expr, "unsupported expression type for `kind`")), |
| /// } |
| /// # } |
| /// ``` |
| /// |
| /// ```console |
| /// error: unsupported expression type for `kind` |
| /// --> src/main.rs:3:14 |
| /// | |
| /// 3 | #[tea(kind = async { replicator.await })] |
| /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| /// ``` |
| pub fn error(&self, msg: impl Display) -> Error { |
| let start_span = self.path.segments[0].ident.span(); |
| let end_span = self.input.cursor().prev_span(); |
| crate::error::new2(start_span, end_span, msg) |
| } |
| } |
| |
| pub(crate) fn parse_nested_meta( |
| input: ParseStream, |
| mut logic: impl FnMut(ParseNestedMeta) -> Result<()>, |
| ) -> Result<()> { |
| loop { |
| let path = input.call(parse_meta_path)?; |
| logic(ParseNestedMeta { path, input })?; |
| if input.is_empty() { |
| return Ok(()); |
| } |
| input.parse::<Token![,]>()?; |
| if input.is_empty() { |
| return Ok(()); |
| } |
| } |
| } |
| |
| // Like Path::parse_mod_style, but accepts keywords in the path. |
| fn parse_meta_path(input: ParseStream) -> Result<Path> { |
| Ok(Path { |
| leading_colon: input.parse()?, |
| segments: { |
| let mut segments = Punctuated::new(); |
| if input.peek(Ident::peek_any) { |
| let ident = Ident::parse_any(input)?; |
| segments.push_value(PathSegment::from(ident)); |
| } else if input.is_empty() { |
| return Err(input.error("expected nested attribute")); |
| } else if input.peek(Lit) { |
| return Err(input.error("unexpected literal in nested attribute, expected ident")); |
| } else { |
| return Err(input.error("unexpected token in nested attribute, expected ident")); |
| } |
| while input.peek(Token![::]) { |
| let punct = input.parse()?; |
| segments.push_punct(punct); |
| let ident = Ident::parse_any(input)?; |
| segments.push_value(PathSegment::from(ident)); |
| } |
| segments |
| }, |
| }) |
| } |