use super::*; use proc_macro2::TokenStream; use std::iter; use std::slice; #[cfg(feature = "parsing")] use crate::meta::{self, ParseNestedMeta}; #[cfg(feature = "parsing")] use crate::parse::{Parse, ParseStream, Parser, Result}; ast_struct! { /// An attribute, like `#[repr(transparent)]`. /// ///
/// /// # Syntax /// /// Rust has six types of attributes. /// /// - Outer attributes like `#[repr(transparent)]`. These appear outside or /// in front of the item they describe. /// /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside /// of the item they describe, usually a module. /// /// - Outer one-line doc comments like `/// Example`. /// /// - Inner one-line doc comments like `//! Please file an issue`. /// /// - Outer documentation blocks `/** Example */`. /// /// - Inner documentation blocks `/*! Please file an issue */`. /// /// The `style` field of type `AttrStyle` distinguishes whether an attribute /// is outer or inner. /// /// Every attribute has a `path` that indicates the intended interpretation /// of the rest of the attribute's contents. The path and the optional /// additional contents are represented together in the `meta` field of the /// attribute in three possible varieties: /// /// - Meta::Path — attributes whose information content conveys just a /// path, for example the `#[test]` attribute. /// /// - Meta::List — attributes that carry arbitrary tokens after the /// path, surrounded by a delimiter (parenthesis, bracket, or brace). For /// example `#[derive(Copy)]` or `#[precondition(x < 5)]`. /// /// - Meta::NameValue — attributes with an `=` sign after the path, /// followed by a Rust expression. For example `#[path = /// "sys/windows.rs"]`. /// /// All doc comments are represented in the NameValue style with a path of /// "doc", as this is how they are processed by the compiler and by /// `macro_rules!` macros. /// /// ```text /// #[derive(Copy, Clone)] /// ~~~~~~Path /// ^^^^^^^^^^^^^^^^^^^Meta::List /// /// #[path = "sys/windows.rs"] /// ~~~~Path /// ^^^^^^^^^^^^^^^^^^^^^^^Meta::NameValue /// /// #[test] /// ^^^^Meta::Path /// ``` /// ///
/// /// # Parsing from tokens to Attribute /// /// This type does not implement the [`Parse`] trait and thus cannot be /// parsed directly by [`ParseStream::parse`]. Instead use /// [`ParseStream::call`] with one of the two parser functions /// [`Attribute::parse_outer`] or [`Attribute::parse_inner`] depending on /// which you intend to parse. /// /// [`Parse`]: parse::Parse /// [`ParseStream::parse`]: parse::ParseBuffer::parse /// [`ParseStream::call`]: parse::ParseBuffer::call /// /// ``` /// use syn::{Attribute, Ident, Result, Token}; /// use syn::parse::{Parse, ParseStream}; /// /// // Parses a unit struct with attributes. /// // /// // #[path = "s.tmpl"] /// // struct S; /// struct UnitStruct { /// attrs: Vec, /// struct_token: Token![struct], /// name: Ident, /// semi_token: Token![;], /// } /// /// impl Parse for UnitStruct { /// fn parse(input: ParseStream) -> Result { /// Ok(UnitStruct { /// attrs: input.call(Attribute::parse_outer)?, /// struct_token: input.parse()?, /// name: input.parse()?, /// semi_token: input.parse()?, /// }) /// } /// } /// ``` /// ///


/// /// # Parsing from Attribute to structured arguments /// /// The grammar of attributes in Rust is very flexible, which makes the /// syntax tree not that useful on its own. In particular, arguments of the /// `Meta::List` variety of attribute are held in an arbitrary `tokens: /// TokenStream`. Macros are expected to check the `path` of the attribute, /// decide whether they recognize it, and then parse the remaining tokens /// according to whatever grammar they wish to require for that kind of /// attribute. Use [`parse_args()`] to parse those tokens into the expected /// data structure. /// /// [`parse_args()`]: Attribute::parse_args /// ///


/// /// # Doc comments /// /// The compiler transforms doc comments, such as `/// comment` and `/*! /// comment */`, into attributes before macros are expanded. Each comment is /// expanded into an attribute of the form `#[doc = r"comment"]`. /// /// As an example, the following `mod` items are expanded identically: /// /// ``` /// # use syn::{ItemMod, parse_quote}; /// let doc: ItemMod = parse_quote! { /// /// Single line doc comments /// /// We write so many! /// /** /// * Multi-line comments... /// * May span many lines /// */ /// mod example { /// //! Of course, they can be inner too /// /*! And fit in a single line */ /// } /// }; /// let attr: ItemMod = parse_quote! { /// #[doc = r" Single line doc comments"] /// #[doc = r" We write so many!"] /// #[doc = r" /// * Multi-line comments... /// * May span many lines /// "] /// mod example { /// #![doc = r" Of course, they can be inner too"] /// #![doc = r" And fit in a single line "] /// } /// }; /// assert_eq!(doc, attr); /// ``` #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Attribute { pub pound_token: Token![#], pub style: AttrStyle, pub bracket_token: token::Bracket, pub meta: Meta, } } impl Attribute { /// Returns the path that identifies the interpretation of this attribute. /// /// For example this would return the `test` in `#[test]`, the `derive` in /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`. pub fn path(&self) -> &Path { self.meta.path() } /// Parse the arguments to the attribute as a syntax tree. /// /// This is similar to pulling out the `TokenStream` from `Meta::List` and /// doing `syn::parse2::(meta_list.tokens)`, except that using /// `parse_args` the error message has a more useful span when `tokens` is /// empty. /// /// The surrounding delimiters are *not* included in the input to the /// parser. /// /// ```text /// #[my_attr(value < 5)] /// ^^^^^^^^^ what gets parsed /// ``` /// /// # Example /// /// ``` /// use syn::{parse_quote, Attribute, Expr}; /// /// let attr: Attribute = parse_quote! { /// #[precondition(value < 5)] /// }; /// /// if attr.path().is_ident("precondition") { /// let precondition: Expr = attr.parse_args()?; /// // ... /// } /// # anyhow::Ok(()) /// ``` #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_args(&self) -> Result { self.parse_args_with(T::parse) } /// Parse the arguments to the attribute using the given parser. /// /// # Example /// /// ``` /// use syn::{parse_quote, Attribute}; /// /// let attr: Attribute = parse_quote! { /// #[inception { #[brrrrrrraaaaawwwwrwrrrmrmrmmrmrmmmmm] }] /// }; /// /// let bwom = attr.parse_args_with(Attribute::parse_outer)?; /// /// // Attribute does not have a Parse impl, so we couldn't directly do: /// // let bwom: Attribute = attr.parse_args()?; /// # anyhow::Ok(()) /// ``` #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_args_with(&self, parser: F) -> Result { match &self.meta { Meta::Path(path) => Err(crate::error::new2( path.segments.first().unwrap().ident.span(), path.segments.last().unwrap().ident.span(), format!( "expected attribute arguments in parentheses: {}[{}(...)]", parsing::DisplayAttrStyle(&self.style), parsing::DisplayPath(path), ), )), Meta::NameValue(meta) => Err(Error::new( meta.eq_token.span, format_args!( "expected parentheses: {}[{}(...)]", parsing::DisplayAttrStyle(&self.style), parsing::DisplayPath(&meta.path), ), )), Meta::List(meta) => meta.parse_args_with(parser), } } /// Parse the arguments to the attribute, expecting it to follow the /// conventional structure used by most of Rust's built-in attributes. /// /// The [*Meta Item Attribute Syntax*][syntax] section in the Rust reference /// explains the convention in more detail. Not all attributes follow this /// convention, so [`parse_args()`][Self::parse_args] is available if you /// need to parse arbitrarily goofy attribute syntax. /// /// [syntax]: https://doc.rust-lang.org/reference/attributes.html#meta-item-attribute-syntax /// /// # Example /// /// We'll parse a struct, and then parse some of Rust's `#[repr]` attribute /// syntax. /// /// ``` /// use syn::{parenthesized, parse_quote, token, ItemStruct, LitInt}; /// /// let input: ItemStruct = parse_quote! { /// #[repr(C, align(4))] /// pub struct MyStruct(u16, u32); /// }; /// /// let mut repr_c = false; /// let mut repr_transparent = false; /// let mut repr_align = None::; /// let mut repr_packed = None::; /// for attr in &input.attrs { /// if attr.path().is_ident("repr") { /// attr.parse_nested_meta(|meta| { /// // #[repr(C)] /// if meta.path.is_ident("C") { /// repr_c = true; /// return Ok(()); /// } /// /// // #[repr(transparent)] /// if meta.path.is_ident("transparent") { /// repr_transparent = true; /// return Ok(()); /// } /// /// // #[repr(align(N))] /// if meta.path.is_ident("align") { /// let content; /// parenthesized!(content in meta.input); /// let lit: LitInt = content.parse()?; /// let n: usize = lit.base10_parse()?; /// repr_align = Some(n); /// return Ok(()); /// } /// /// // #[repr(packed)] or #[repr(packed(N))], omitted N means 1 /// if meta.path.is_ident("packed") { /// if meta.input.peek(token::Paren) { /// let content; /// parenthesized!(content in meta.input); /// let lit: LitInt = content.parse()?; /// let n: usize = lit.base10_parse()?; /// repr_packed = Some(n); /// } else { /// repr_packed = Some(1); /// } /// return Ok(()); /// } /// /// Err(meta.error("unrecognized repr")) /// })?; /// } /// } /// # anyhow::Ok(()) /// ``` /// /// # Alternatives /// /// In some cases, for attributes which have nested layers of structured /// content, the following less flexible approach might be more convenient: /// /// ``` /// # use syn::{parse_quote, ItemStruct}; /// # /// # let input: ItemStruct = parse_quote! { /// # #[repr(C, align(4))] /// # pub struct MyStruct(u16, u32); /// # }; /// # /// use syn::punctuated::Punctuated; /// use syn::{parenthesized, token, Error, LitInt, Meta, Token}; /// /// let mut repr_c = false; /// let mut repr_transparent = false; /// let mut repr_align = None::; /// let mut repr_packed = None::; /// for attr in &input.attrs { /// if attr.path().is_ident("repr") { /// let nested = attr.parse_args_with(Punctuated::::parse_terminated)?; /// for meta in nested { /// match meta { /// // #[repr(C)] /// Meta::Path(path) if path.is_ident("C") => { /// repr_c = true; /// } /// /// // #[repr(align(N))] /// Meta::List(meta) if meta.path.is_ident("align") => { /// let lit: LitInt = meta.parse_args()?; /// let n: usize = lit.base10_parse()?; /// repr_align = Some(n); /// } /// /// /* ... */ /// /// _ => { /// return Err(Error::new_spanned(meta, "unrecognized repr")); /// } /// } /// } /// } /// } /// # Ok(()) /// ``` #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()> { self.parse_args_with(meta::parser(logic)) } /// Parses zero or more outer attributes from the stream. /// /// # Example /// /// See /// [*Parsing from tokens to Attribute*](#parsing-from-tokens-to-attribute). #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_outer(input: ParseStream) -> Result> { let mut attrs = Vec::new(); while input.peek(Token![#]) { attrs.push(input.call(parsing::single_parse_outer)?); } Ok(attrs) } /// Parses zero or more inner attributes from the stream. /// /// # Example /// /// See /// [*Parsing from tokens to Attribute*](#parsing-from-tokens-to-attribute). #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_inner(input: ParseStream) -> Result> { let mut attrs = Vec::new(); parsing::parse_inner(input, &mut attrs)?; Ok(attrs) } } ast_enum! { /// Distinguishes between attributes that decorate an item and attributes /// that are contained within an item. /// /// # Outer attributes /// /// - `#[repr(transparent)]` /// - `/// # Example` /// - `/** Please file an issue */` /// /// # Inner attributes /// /// - `#![feature(proc_macro)]` /// - `//! # Example` /// - `/*! Please file an issue */` #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum AttrStyle { Outer, Inner(Token![!]), } } ast_enum_of_structs! { /// Content of a compile-time structured attribute. /// /// ## Path /// /// A meta path is like the `test` in `#[test]`. /// /// ## List /// /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`. /// /// ## NameValue /// /// A name-value meta is like the `path = "..."` in `#[path = /// "sys/windows.rs"]`. /// /// # Syntax tree enum /// /// This type is a [syntax tree enum]. /// /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Meta { Path(Path), /// A structured list within an attribute, like `derive(Copy, Clone)`. List(MetaList), /// A name-value pair within an attribute, like `feature = "nightly"`. NameValue(MetaNameValue), } } ast_struct! { /// A structured list within an attribute, like `derive(Copy, Clone)`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaList { pub path: Path, pub delimiter: MacroDelimiter, pub tokens: TokenStream, } } ast_struct! { /// A name-value pair within an attribute, like `feature = "nightly"`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaNameValue { pub path: Path, pub eq_token: Token![=], pub value: Expr, } } impl Meta { /// Returns the path that begins this structured meta item. /// /// For example this would return the `test` in `#[test]`, the `derive` in /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`. pub fn path(&self) -> &Path { match self { Meta::Path(path) => path, Meta::List(meta) => &meta.path, Meta::NameValue(meta) => &meta.path, } } /// Error if this is a `Meta::List` or `Meta::NameValue`. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn require_path_only(&self) -> Result<&Path> { let error_span = match self { Meta::Path(path) => return Ok(path), Meta::List(meta) => meta.delimiter.span().open(), Meta::NameValue(meta) => meta.eq_token.span, }; Err(Error::new(error_span, "unexpected token in attribute")) } /// Error if this is a `Meta::Path` or `Meta::NameValue`. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn require_list(&self) -> Result<&MetaList> { match self { Meta::List(meta) => Ok(meta), Meta::Path(path) => Err(crate::error::new2( path.segments.first().unwrap().ident.span(), path.segments.last().unwrap().ident.span(), format!( "expected attribute arguments in parentheses: `{}(...)`", parsing::DisplayPath(path), ), )), Meta::NameValue(meta) => Err(Error::new(meta.eq_token.span, "expected `(`")), } } /// Error if this is a `Meta::Path` or `Meta::List`. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn require_name_value(&self) -> Result<&MetaNameValue> { match self { Meta::NameValue(meta) => Ok(meta), Meta::Path(path) => Err(crate::error::new2( path.segments.first().unwrap().ident.span(), path.segments.last().unwrap().ident.span(), format!( "expected a value for this attribute: `{} = ...`", parsing::DisplayPath(path), ), )), Meta::List(meta) => Err(Error::new(meta.delimiter.span().open(), "expected `=`")), } } } impl MetaList { /// See [`Attribute::parse_args`]. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_args(&self) -> Result { self.parse_args_with(T::parse) } /// See [`Attribute::parse_args_with`]. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_args_with(&self, parser: F) -> Result { let scope = self.delimiter.span().close(); crate::parse::parse_scoped(parser, scope, self.tokens.clone()) } /// See [`Attribute::parse_nested_meta`]. #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, ) -> Result<()> { self.parse_args_with(meta::parser(logic)) } } pub(crate) trait FilterAttrs<'a> { type Ret: Iterator; fn outer(self) -> Self::Ret; fn inner(self) -> Self::Ret; } impl<'a> FilterAttrs<'a> for &'a [Attribute] { type Ret = iter::Filter, fn(&&Attribute) -> bool>; fn outer(self) -> Self::Ret { fn is_outer(attr: &&Attribute) -> bool { match attr.style { AttrStyle::Outer => true, AttrStyle::Inner(_) => false, } } self.iter().filter(is_outer) } fn inner(self) -> Self::Ret { fn is_inner(attr: &&Attribute) -> bool { match attr.style { AttrStyle::Inner(_) => true, AttrStyle::Outer => false, } } self.iter().filter(is_inner) } } #[cfg(feature = "parsing")] pub(crate) mod parsing { use super::*; use crate::parse::discouraged::Speculative; use crate::parse::{Parse, ParseStream, Result}; use std::fmt::{self, Display}; pub(crate) fn parse_inner(input: ParseStream, attrs: &mut Vec) -> Result<()> { while input.peek(Token![#]) && input.peek2(Token![!]) { attrs.push(input.call(parsing::single_parse_inner)?); } Ok(()) } pub(crate) fn single_parse_inner(input: ParseStream) -> Result { let content; Ok(Attribute { pound_token: input.parse()?, style: AttrStyle::Inner(input.parse()?), bracket_token: bracketed!(content in input), meta: content.parse()?, }) } pub(crate) fn single_parse_outer(input: ParseStream) -> Result { let content; Ok(Attribute { pound_token: input.parse()?, style: AttrStyle::Outer, bracket_token: bracketed!(content in input), meta: content.parse()?, }) } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for Meta { fn parse(input: ParseStream) -> Result { let path = input.call(Path::parse_mod_style)?; parse_meta_after_path(path, input) } } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for MetaList { fn parse(input: ParseStream) -> Result { let path = input.call(Path::parse_mod_style)?; parse_meta_list_after_path(path, input) } } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for MetaNameValue { fn parse(input: ParseStream) -> Result { let path = input.call(Path::parse_mod_style)?; parse_meta_name_value_after_path(path, input) } } pub(crate) fn parse_meta_after_path(path: Path, input: ParseStream) -> Result { if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) { parse_meta_list_after_path(path, input).map(Meta::List) } else if input.peek(Token![=]) { parse_meta_name_value_after_path(path, input).map(Meta::NameValue) } else { Ok(Meta::Path(path)) } } fn parse_meta_list_after_path(path: Path, input: ParseStream) -> Result { let (delimiter, tokens) = mac::parse_delimiter(input)?; Ok(MetaList { path, delimiter, tokens, }) } fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result { let eq_token: Token![=] = input.parse()?; let ahead = input.fork(); let lit: Option = ahead.parse()?; let value = if let (Some(lit), true) = (lit, ahead.is_empty()) { input.advance_to(&ahead); Expr::Lit(ExprLit { attrs: Vec::new(), lit, }) } else if input.peek(Token![#]) && input.peek2(token::Bracket) { return Err(input.error("unexpected attribute inside of attribute")); } else { input.parse()? }; Ok(MetaNameValue { path, eq_token, value, }) } pub(super) struct DisplayAttrStyle<'a>(pub &'a AttrStyle); impl<'a> Display for DisplayAttrStyle<'a> { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(match self.0 { AttrStyle::Outer => "#", AttrStyle::Inner(_) => "#!", }) } } pub(super) struct DisplayPath<'a>(pub &'a Path); impl<'a> Display for DisplayPath<'a> { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { for (i, segment) in self.0.segments.iter().enumerate() { if i > 0 || self.0.leading_colon.is_some() { formatter.write_str("::")?; } write!(formatter, "{}", segment.ident)?; } Ok(()) } } } #[cfg(feature = "printing")] mod printing { use super::*; use proc_macro2::TokenStream; use quote::ToTokens; #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.pound_token.to_tokens(tokens); if let AttrStyle::Inner(b) = &self.style { b.to_tokens(tokens); } self.bracket_token.surround(tokens, |tokens| { self.meta.to_tokens(tokens); }); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for MetaList { fn to_tokens(&self, tokens: &mut TokenStream) { self.path.to_tokens(tokens); self.delimiter.surround(tokens, self.tokens.clone()); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for MetaNameValue { fn to_tokens(&self, tokens: &mut TokenStream) { self.path.to_tokens(tokens); self.eq_token.to_tokens(tokens); self.value.to_tokens(tokens); } } }