// SPDX-License-Identifier: Apache-2.0 OR MIT12//! Facility for interpreting structured content inside of an `Attribute`.34use crate::error::{Error, Result};5use crate::ext::IdentExt as _;6use crate::lit::Lit;7use crate::parse::{ParseStream, Parser};8use crate::path::{Path, PathSegment};9use crate::punctuated::Punctuated;10use proc_macro2::Ident;11use std::fmt::Display;1213/// Make a parser that is usable with `parse_macro_input!` in a14/// `#[proc_macro_attribute]` macro.15///16/// *Warning:* When parsing attribute args **other than** the17/// `proc_macro::TokenStream` input of a `proc_macro_attribute`, you do **not**18/// need this function. In several cases your callers will get worse error19/// messages if you use this function, because the surrounding delimiter's span20/// is concealed from attribute macros by rustc. Use21/// [`Attribute::parse_nested_meta`] instead.22///23/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta24///25/// # Example26///27/// This example implements an attribute macro whose invocations look like this:28///29/// ```30/// # const IGNORE: &str = stringify! {31/// #[tea(kind = "EarlGrey", hot)]32/// struct Picard {...}33/// # };34/// ```35///36/// The "parameters" supported by the attribute are:37///38/// - `kind = "..."`39/// - `hot`40/// - `with(sugar, milk, ...)`, a comma-separated list of ingredients41///42/// ```43/// # extern crate proc_macro;44/// #45/// use proc_macro::TokenStream;46/// use syn::{parse_macro_input, LitStr, Path};47///48/// # const IGNORE: &str = stringify! {49/// #[proc_macro_attribute]50/// # };51/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {52/// let mut kind: Option<LitStr> = None;53/// let mut hot: bool = false;54/// let mut with: Vec<Path> = Vec::new();55/// let tea_parser = syn::meta::parser(|meta| {56/// if meta.path.is_ident("kind") {57/// kind = Some(meta.value()?.parse()?);58/// Ok(())59/// } else if meta.path.is_ident("hot") {60/// hot = true;61/// Ok(())62/// } else if meta.path.is_ident("with") {63/// meta.parse_nested_meta(|meta| {64/// with.push(meta.path);65/// Ok(())66/// })67/// } else {68/// Err(meta.error("unsupported tea property"))69/// }70/// });71///72/// parse_macro_input!(args with tea_parser);73/// eprintln!("kind={kind:?} hot={hot} with={with:?}");74///75/// /* ... */76/// # TokenStream::new()77/// }78/// ```79///80/// The `syn::meta` library will take care of dealing with the commas including81/// trailing commas, and producing sensible error messages on unexpected input.82///83/// ```console84/// error: expected `,`85/// --> src/main.rs:3:3786/// |87/// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))]88/// | ^89/// ```90///91/// # Example92///93/// Same as above but we factor out most of the logic into a separate function.94///95/// ```96/// # extern crate proc_macro;97/// #98/// use proc_macro::TokenStream;99/// use syn::meta::ParseNestedMeta;100/// use syn::parse::{Parser, Result};101/// use syn::{parse_macro_input, LitStr, Path};102///103/// # const IGNORE: &str = stringify! {104/// #[proc_macro_attribute]105/// # };106/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {107/// let mut attrs = TeaAttributes::default();108/// let tea_parser = syn::meta::parser(|meta| attrs.parse(meta));109/// parse_macro_input!(args with tea_parser);110///111/// /* ... */112/// # TokenStream::new()113/// }114///115/// #[derive(Default)]116/// struct TeaAttributes {117/// kind: Option<LitStr>,118/// hot: bool,119/// with: Vec<Path>,120/// }121///122/// impl TeaAttributes {123/// fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {124/// if meta.path.is_ident("kind") {125/// self.kind = Some(meta.value()?.parse()?);126/// Ok(())127/// } else /* just like in last example */128/// # { unimplemented!() }129///130/// }131/// }132/// ```133pub fn parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()> {134|input: ParseStream| {135if input.is_empty() {136Ok(())137} else {138parse_nested_meta(input, logic)139}140}141}142143/// Context for parsing a single property in the conventional syntax for144/// structured attributes.145///146/// # Examples147///148/// Refer to usage examples on the following two entry-points:149///150/// - [`Attribute::parse_nested_meta`] if you have an entire `Attribute` to151/// parse. Always use this if possible. Generally this is able to produce152/// better error messages because `Attribute` holds span information for all153/// of the delimiters therein.154///155/// - [`syn::meta::parser`] if you are implementing a `proc_macro_attribute`156/// macro and parsing the arguments to the attribute macro, i.e. the ones157/// written in the same attribute that dispatched the macro invocation. Rustc158/// does not pass span information for the surrounding delimiters into the159/// attribute macro invocation in this situation, so error messages might be160/// less precise.161///162/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta163/// [`syn::meta::parser`]: crate::meta::parser164#[non_exhaustive]165pub struct ParseNestedMeta<'a> {166pub path: Path,167pub input: ParseStream<'a>,168}169170impl<'a> ParseNestedMeta<'a> {171/// Used when parsing `key = "value"` syntax.172///173/// All it does is advance `meta.input` past the `=` sign in the input. You174/// could accomplish the same effect by writing175/// `meta.parse::<Token![=]>()?`, so at most it is a minor convenience to176/// use `meta.value()?`.177///178/// # Example179///180/// ```181/// use syn::{parse_quote, Attribute, LitStr};182///183/// let attr: Attribute = parse_quote! {184/// #[tea(kind = "EarlGrey")]185/// };186/// // conceptually:187/// if attr.path().is_ident("tea") { // this parses the `tea`188/// attr.parse_nested_meta(|meta| { // this parses the `(`189/// if meta.path.is_ident("kind") { // this parses the `kind`190/// let value = meta.value()?; // this parses the `=`191/// let s: LitStr = value.parse()?; // this parses `"EarlGrey"`192/// if s.value() == "EarlGrey" {193/// // ...194/// }195/// Ok(())196/// } else {197/// Err(meta.error("unsupported attribute"))198/// }199/// })?;200/// }201/// # anyhow::Ok(())202/// ```203pub fn value(&self) -> Result<ParseStream<'a>> {204self.input.parse::<Token![=]>()?;205Ok(self.input)206}207208/// Used when parsing `list(...)` syntax **if** the content inside the209/// nested parentheses is also expected to conform to Rust's structured210/// attribute convention.211///212/// # Example213///214/// ```215/// use syn::{parse_quote, Attribute};216///217/// let attr: Attribute = parse_quote! {218/// #[tea(with(sugar, milk))]219/// };220///221/// if attr.path().is_ident("tea") {222/// attr.parse_nested_meta(|meta| {223/// if meta.path.is_ident("with") {224/// meta.parse_nested_meta(|meta| { // <---225/// if meta.path.is_ident("sugar") {226/// // Here we can go even deeper if needed.227/// Ok(())228/// } else if meta.path.is_ident("milk") {229/// Ok(())230/// } else {231/// Err(meta.error("unsupported ingredient"))232/// }233/// })234/// } else {235/// Err(meta.error("unsupported tea property"))236/// }237/// })?;238/// }239/// # anyhow::Ok(())240/// ```241///242/// # Counterexample243///244/// If you don't need `parse_nested_meta`'s help in parsing the content245/// written within the nested parentheses, keep in mind that you can always246/// just parse it yourself from the exposed ParseStream. Rust syntax permits247/// arbitrary tokens within those parentheses so for the crazier stuff,248/// `parse_nested_meta` is not what you want.249///250/// ```251/// use syn::{parenthesized, parse_quote, Attribute, LitInt};252///253/// let attr: Attribute = parse_quote! {254/// #[repr(align(32))]255/// };256///257/// let mut align: Option<LitInt> = None;258/// if attr.path().is_ident("repr") {259/// attr.parse_nested_meta(|meta| {260/// if meta.path.is_ident("align") {261/// let content;262/// parenthesized!(content in meta.input);263/// align = Some(content.parse()?);264/// Ok(())265/// } else {266/// Err(meta.error("unsupported repr"))267/// }268/// })?;269/// }270/// # anyhow::Ok(())271/// ```272pub fn parse_nested_meta(273&self,274logic: impl FnMut(ParseNestedMeta) -> Result<()>,275) -> Result<()> {276let content;277parenthesized!(content in self.input);278parse_nested_meta(&content, logic)279}280281/// Report that the attribute's content did not conform to expectations.282///283/// The span of the resulting error will cover `meta.path` *and* everything284/// that has been parsed so far since it.285///286/// There are 2 ways you might call this. First, if `meta.path` is not287/// something you recognize:288///289/// ```290/// # use syn::Attribute;291/// #292/// # fn example(attr: &Attribute) -> syn::Result<()> {293/// attr.parse_nested_meta(|meta| {294/// if meta.path.is_ident("kind") {295/// // ...296/// Ok(())297/// } else {298/// Err(meta.error("unsupported tea property"))299/// }300/// })?;301/// # Ok(())302/// # }303/// ```304///305/// In this case, it behaves exactly like306/// `syn::Error::new_spanned(&meta.path, "message...")`.307///308/// ```console309/// error: unsupported tea property310/// --> src/main.rs:3:26311/// |312/// 3 | #[tea(kind = "EarlGrey", wat = "foo")]313/// | ^^^314/// ```315///316/// More usefully, the second place is if you've already parsed a value but317/// have decided not to accept the value:318///319/// ```320/// # use syn::Attribute;321/// #322/// # fn example(attr: &Attribute) -> syn::Result<()> {323/// use syn::Expr;324///325/// attr.parse_nested_meta(|meta| {326/// if meta.path.is_ident("kind") {327/// let expr: Expr = meta.value()?.parse()?;328/// match expr {329/// Expr::Lit(expr) => /* ... */330/// # unimplemented!(),331/// Expr::Path(expr) => /* ... */332/// # unimplemented!(),333/// Expr::Macro(expr) => /* ... */334/// # unimplemented!(),335/// _ => Err(meta.error("tea kind must be a string literal, path, or macro")),336/// }337/// } else /* as above */338/// # { unimplemented!() }339///340/// })?;341/// # Ok(())342/// # }343/// ```344///345/// ```console346/// error: tea kind must be a string literal, path, or macro347/// --> src/main.rs:3:7348/// |349/// 3 | #[tea(kind = async { replicator.await })]350/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^351/// ```352///353/// Often you may want to use `syn::Error::new_spanned` even in this354/// situation. In the above code, that would be:355///356/// ```357/// # use syn::{Error, Expr};358/// #359/// # fn example(expr: Expr) -> syn::Result<()> {360/// match expr {361/// Expr::Lit(expr) => /* ... */362/// # unimplemented!(),363/// Expr::Path(expr) => /* ... */364/// # unimplemented!(),365/// Expr::Macro(expr) => /* ... */366/// # unimplemented!(),367/// _ => Err(Error::new_spanned(expr, "unsupported expression type for `kind`")),368/// }369/// # }370/// ```371///372/// ```console373/// error: unsupported expression type for `kind`374/// --> src/main.rs:3:14375/// |376/// 3 | #[tea(kind = async { replicator.await })]377/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^378/// ```379pub fn error(&self, msg: impl Display) -> Error {380let start_span = self.path.segments[0].ident.span();381let end_span = self.input.cursor().prev_span();382crate::error::new2(start_span, end_span, msg)383}384}385386pub(crate) fn parse_nested_meta(387input: ParseStream,388mut logic: impl FnMut(ParseNestedMeta) -> Result<()>,389) -> Result<()> {390loop {391let path = input.call(parse_meta_path)?;392logic(ParseNestedMeta { path, input })?;393if input.is_empty() {394return Ok(());395}396input.parse::<Token![,]>()?;397if input.is_empty() {398return Ok(());399}400}401}402403// Like Path::parse_mod_style, but accepts keywords in the path.404fn parse_meta_path(input: ParseStream) -> Result<Path> {405Ok(Path {406leading_colon: input.parse()?,407segments: {408let mut segments = Punctuated::new();409if input.peek(Ident::peek_any) {410let ident = Ident::parse_any(input)?;411segments.push_value(PathSegment::from(ident));412} else if input.is_empty() {413return Err(input.error("expected nested attribute"));414} else if input.peek(Lit) {415return Err(input.error("unexpected literal in nested attribute, expected ident"));416} else {417return Err(input.error("unexpected token in nested attribute, expected ident"));418}419while input.peek(Token![::]) {420let punct = input.parse()?;421segments.push_punct(punct);422let ident = Ident::parse_any(input)?;423segments.push_value(PathSegment::from(ident));424}425segments426},427})428}429430431