blob: 95eaf2d59b086e96183e0c611261449e754ec89d [file] [log] [blame]
use super::*;
use crate::punctuated::Punctuated;
use proc_macro2::TokenStream;
use std::iter;
#[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.*
///
/// <br>
///
/// # 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
/// ```
///
/// <br>
///
/// # 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<Attribute>,
/// struct_token: Token![struct],
/// name: Ident,
/// semi_token: Token![;],
/// }
///
/// impl Parse for UnitStruct {
/// fn parse(input: ParseStream) -> Result<Self> {
/// Ok(UnitStruct {
/// attrs: input.call(Attribute::parse_outer)?,
/// struct_token: input.parse()?,
/// name: input.parse()?,
/// semi_token: input.parse()?,
/// })
/// }
/// }
/// ```
///
/// <p><br></p>
///
/// # 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
///
/// <p><br></p>
///
/// # 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);
/// ```
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")]
pub fn parse_meta(&self) -> Result<Meta> {
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::<T>(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")]
pub fn parse_args<T: Parse>(&self) -> Result<T> {
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")]
pub fn parse_args_with<F: Parser>(&self, parser: F) -> Result<F::Output> {
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")]
pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
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")]
pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
let mut attrs = Vec::new();
while input.peek(Token![#]) && input.peek2(Token![!]) {
attrs.push(input.call(parsing::single_parse_inner)?);
}
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<ParseBuffer<'a>> {
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 */`
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]: enum.Expr.html#syntax-tree-enums
//
// TODO: change syntax-tree-enum link to an intra rustdoc link, currently
// blocked on https://github.com/rust-lang/rust/issues/62833
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.*
pub struct MetaList {
pub path: Path,
pub paren_token: token::Paren,
pub nested: Punctuated<NestedMeta, Token![,]>,
}
}
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.*
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.*
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()
/// }
/// ```
pub type AttributeArgs = Vec<NestedMeta>;
pub trait FilterAttrs<'a> {
type Ret: Iterator<Item = &'a Attribute>;
fn outer(self) -> Self::Ret;
fn inner(self) -> Self::Ret;
}
impl<'a, T> FilterAttrs<'a> for T
where
T: IntoIterator<Item = &'a Attribute>,
{
type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
fn outer(self) -> Self::Ret {
fn is_outer(attr: &&Attribute) -> bool {
match attr.style {
AttrStyle::Outer => true,
AttrStyle::Inner(_) => false,
}
}
self.into_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.into_iter().filter(is_inner)
}
}
#[cfg(feature = "parsing")]
pub mod parsing {
use super::*;
use crate::ext::IdentExt;
use crate::parse::{Parse, ParseStream, Result};
#[cfg(feature = "full")]
use crate::private;
pub fn single_parse_inner(input: ParseStream) -> Result<Attribute> {
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<Attribute> {
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()?,
})
}
#[cfg(feature = "full")]
impl private {
pub fn attrs(outer: Vec<Attribute>, inner: Vec<Attribute>) -> Vec<Attribute> {
let mut attrs = outer;
attrs.extend(inner);
attrs
}
}
// 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();
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
},
})
}
impl Parse for Meta {
fn parse(input: ParseStream) -> Result<Self> {
let path = input.call(parse_meta_path)?;
parse_meta_after_path(path, input)
}
}
impl Parse for MetaList {
fn parse(input: ParseStream) -> Result<Self> {
let path = input.call(parse_meta_path)?;
parse_meta_list_after_path(path, input)
}
}
impl Parse for MetaNameValue {
fn parse(input: ParseStream) -> Result<Self> {
let path = input.call(parse_meta_path)?;
parse_meta_name_value_after_path(path, input)
}
}
impl Parse for NestedMeta {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) {
input.parse().map(NestedMeta::Lit)
} else if input.peek(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<Meta> {
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<MetaList> {
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<MetaNameValue> {
Ok(MetaNameValue {
path,
eq_token: input.parse()?,
lit: input.parse()?,
})
}
}
#[cfg(feature = "printing")]
mod printing {
use super::*;
use proc_macro2::TokenStream;
use quote::ToTokens;
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);
});
}
}
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);
})
}
}
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);
}
}
}