diff options
Diffstat (limited to 'vendor/syn/src/meta.rs')
-rw-r--r-- | vendor/syn/src/meta.rs | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/vendor/syn/src/meta.rs b/vendor/syn/src/meta.rs new file mode 100644 index 0000000..66a0a46 --- /dev/null +++ b/vendor/syn/src/meta.rs @@ -0,0 +1,426 @@ +//! Facility for interpreting structured content inside of an `Attribute`. + +use crate::ext::IdentExt as _; +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 + }, + }) +} |