use super::*; use crate::punctuated::Punctuated; use proc_macro2::TokenStream; use std::iter; use std::slice; #[cfg(feature = "parsing")] use crate::parse::{Parse, ParseBuffer, ParseStream, Parser, Result}; #[cfg(feature = "parsing")] use crate::punctuated::Pair; ast_struct! { /// An attribute like `#[repr(transparent)]`. /// /// *This type is available only if Syn is built with the `"derive"` or `"full"` /// feature.* /// ///
/// /// # 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 doc comments like `/// # Example`. /// - Inner doc comments like `//! Please file an issue`. /// - Outer block comments `/** # Example */`. /// - Inner block comments `/*! Please file an issue */`. /// /// The `style` field of type `AttrStyle` distinguishes whether an attribute /// is outer or inner. Doc comments and block comments are promoted to /// attributes, as this is how they are processed by the compiler and by /// `macro_rules!` macros. /// /// The `path` field gives the possibly colon-delimited path against which /// the attribute is resolved. It is equal to `"doc"` for desugared doc /// comments. The `tokens` field contains the rest of the attribute body as /// tokens. /// /// ```text /// #[derive(Copy)] #[crate::precondition x < 5] /// ^^^^^^~~~~~~ ^^^^^^^^^^^^^^^^^^^ ~~~~~ /// path tokens path tokens /// ``` /// ///
/// /// # 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 /// 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. /// /// If the attribute you are parsing is expected to conform to the /// conventional structured form of attribute, use [`parse_meta()`] to /// obtain that structured representation. If the attribute follows some /// other grammar of its own, use [`parse_args()`] to parse that into the /// expected data structure. /// /// [`parse_meta()`]: Attribute::parse_meta /// [`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 path: Path, pub tokens: TokenStream, } } impl Attribute { /// Parses the content of the attribute, consisting of the path and tokens, /// as a [`Meta`] if possible. /// /// *This function is available only if Syn is built with the `"parsing"` /// feature.* #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_meta(&self) -> Result { fn clone_ident_segment(segment: &PathSegment) -> PathSegment { PathSegment { ident: segment.ident.clone(), arguments: PathArguments::None, } } let path = Path { leading_colon: self .path .leading_colon .as_ref() .map(|colon| Token![::](colon.spans)), segments: self .path .segments .pairs() .map(|pair| match pair { Pair::Punctuated(seg, punct) => { Pair::Punctuated(clone_ident_segment(seg), Token![::](punct.spans)) } Pair::End(seg) => Pair::End(clone_ident_segment(seg)), }) .collect(), }; let parser = |input: ParseStream| parsing::parse_meta_after_path(path, input); parse::Parser::parse2(parser, self.tokens.clone()) } /// Parse the arguments to the attribute as a syntax tree. /// /// This is similar to `syn::parse2::(attr.tokens)` except that: /// /// - the surrounding delimiters are *not* included in the input to the /// parser; and /// - the error message has a more useful span when `tokens` is empty. /// /// ```text /// #[my_attr(value < 5)] /// ^^^^^^^^^ what gets parsed /// ``` /// /// *This function is available only if Syn is built with the `"parsing"` /// feature.* #[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. /// /// *This function is available only if Syn is built with the `"parsing"` /// feature.* #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_args_with(&self, parser: F) -> Result { let parser = |input: ParseStream| { let args = enter_args(self, input)?; parse::parse_stream(parser, &args) }; parser.parse2(self.tokens.clone()) } /// Parses zero or more outer attributes from the stream. /// /// *This function is available only if Syn is built with the `"parsing"` /// feature.* #[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. /// /// *This function is available only if Syn is built with the `"parsing"` /// feature.* #[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) } } #[cfg(feature = "parsing")] fn expected_parentheses(attr: &Attribute) -> String { let style = match attr.style { AttrStyle::Outer => "#", AttrStyle::Inner(_) => "#!", }; let mut path = String::new(); for segment in &attr.path.segments { if !path.is_empty() || attr.path.leading_colon.is_some() { path += "::"; } path += &segment.ident.to_string(); } format!("{}[{}(...)]", style, path) } #[cfg(feature = "parsing")] fn enter_args<'a>(attr: &Attribute, input: ParseStream<'a>) -> Result> { if input.is_empty() { let expected = expected_parentheses(attr); let msg = format!("expected attribute arguments in parentheses: {}", expected); return Err(crate::error::new2( attr.pound_token.span, attr.bracket_token.span, msg, )); } else if input.peek(Token![=]) { let expected = expected_parentheses(attr); let msg = format!("expected parentheses: {}", expected); return Err(input.error(msg)); }; let content; if input.peek(token::Paren) { parenthesized!(content in input); } else if input.peek(token::Bracket) { bracketed!(content in input); } else if input.peek(token::Brace) { braced!(content in input); } else { return Err(input.error("unexpected token in attribute arguments")); } if input.is_empty() { Ok(content) } else { Err(input.error("unexpected token in attribute arguments")) } } ast_enum! { /// Distinguishes between attributes that decorate an item and attributes /// that are contained within an item. /// /// *This type is available only if Syn is built with the `"derive"` or `"full"` /// feature.* /// /// # 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. /// /// *This type is available only if Syn is built with the `"derive"` or `"full"` /// feature.* /// /// ## 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)`. /// /// *This type is available only if Syn is built with the `"derive"` or /// `"full"` feature.* #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaList { pub path: Path, pub paren_token: token::Paren, pub nested: Punctuated, } } ast_struct! { /// A name-value pair within an attribute, like `feature = "nightly"`. /// /// *This type is available only if Syn is built with the `"derive"` or /// `"full"` feature.* #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaNameValue { pub path: Path, pub eq_token: Token![=], pub lit: Lit, } } impl Meta { /// Returns the identifier 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, } } } ast_enum_of_structs! { /// Element of a compile-time attribute list. /// /// *This type is available only if Syn is built with the `"derive"` or `"full"` /// feature.* #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum NestedMeta { /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which /// would be a nested `Meta::Path`. Meta(Meta), /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`. Lit(Lit), } } /// Conventional argument type associated with an invocation of an attribute /// macro. /// /// For example if we are developing an attribute macro that is intended to be /// invoked on function items as follows: /// /// ``` /// # const IGNORE: &str = stringify! { /// #[my_attribute(path = "/v1/refresh")] /// # }; /// pub fn refresh() { /// /* ... */ /// } /// ``` /// /// The implementation of this macro would want to parse its attribute arguments /// as type `AttributeArgs`. /// /// ``` /// # extern crate proc_macro; /// # /// use proc_macro::TokenStream; /// use syn::{parse_macro_input, AttributeArgs, ItemFn}; /// /// # const IGNORE: &str = stringify! { /// #[proc_macro_attribute] /// # }; /// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream { /// let args = parse_macro_input!(args as AttributeArgs); /// let input = parse_macro_input!(input as ItemFn); /// /// /* ... */ /// # "".parse().unwrap() /// } /// ``` #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub type AttributeArgs = Vec; pub 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 mod parsing { use super::*; use crate::ext::IdentExt; use crate::parse::{Parse, ParseStream, Result}; pub 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 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), path: content.call(Path::parse_mod_style)?, tokens: content.parse()?, }) } pub fn single_parse_outer(input: ParseStream) -> Result { let content; Ok(Attribute { pound_token: input.parse()?, style: AttrStyle::Outer, bracket_token: bracketed!(content in input), path: content.call(Path::parse_mod_style)?, tokens: content.parse()?, }) } // Like Path::parse_mod_style but accepts keywords in the path. fn parse_meta_path(input: ParseStream) -> Result { Ok(Path { leading_colon: input.parse()?, segments: { let mut segments = Punctuated::new(); while input.peek(Ident::peek_any) { let ident = Ident::parse_any(input)?; segments.push_value(PathSegment::from(ident)); if !input.peek(Token![::]) { break; } let punct = input.parse()?; segments.push_punct(punct); } if segments.is_empty() { return Err(input.error("expected path")); } else if segments.trailing_punct() { return Err(input.error("expected path segment")); } segments }, }) } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for Meta { fn parse(input: ParseStream) -> Result { let path = input.call(parse_meta_path)?; 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(parse_meta_path)?; 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(parse_meta_path)?; parse_meta_name_value_after_path(path, input) } } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for NestedMeta { fn parse(input: ParseStream) -> Result { if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) { input.parse().map(NestedMeta::Lit) } else if input.peek(Ident::peek_any) || input.peek(Token![::]) && input.peek3(Ident::peek_any) { input.parse().map(NestedMeta::Meta) } else { Err(input.error("expected identifier or literal")) } } } pub fn parse_meta_after_path(path: Path, input: ParseStream) -> Result { if input.peek(token::Paren) { 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 content; Ok(MetaList { path, paren_token: parenthesized!(content in input), nested: content.parse_terminated(NestedMeta::parse)?, }) } fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result { Ok(MetaNameValue { path, eq_token: input.parse()?, lit: input.parse()?, }) } } #[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.path.to_tokens(tokens); self.tokens.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.paren_token.surround(tokens, |tokens| { self.nested.to_tokens(tokens); }); } } #[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.lit.to_tokens(tokens); } } }