429 lines · 13.9 KB · Rust
RawDownload
// SPDX-License-Identifier: Apache-2.0 OR MIT //! Facility for interpreting structured content inside of an `Attribute`. use crate::error::{Error, Result};use crate::ext::IdentExt as _;use crate::lit::Lit;use crate::parse::{ParseStream, Parser};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        },    })}