Revision control

Copy as Markdown

Other Tools

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)]`.
///
/// <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 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 &mdash; attributes whose information content conveys just a
/// path, for example the `#[test]` attribute.
///
/// - Meta::List &mdash; 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 &mdash; 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
/// ```
///
/// <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
/// `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
///
/// <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);
/// ```
#[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::<T>(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<T: Parse>(&self) -> Result<T> {
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<F: Parser>(&self, parser: F) -> Result<F::Output> {
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.
///
///
/// # 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::<usize>;
/// let mut repr_packed = None::<usize>;
/// 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::<usize>;
/// let mut repr_packed = None::<usize>;
/// for attr in &input.attrs {
/// if attr.path().is_ident("repr") {
/// let nested = attr.parse_args_with(Punctuated::<Meta, Token![,]>::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<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.
///
/// # 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<Vec<Self>> {
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<T: Parse>(&self) -> Result<T> {
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<F: Parser>(&self, parser: F) -> Result<F::Output> {
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<Item = &'a Attribute>;
fn outer(self) -> Self::Ret;
fn inner(self) -> Self::Ret;
}
impl<'a> FilterAttrs<'a> for &'a [Attribute] {
type Ret = iter::Filter<slice::Iter<'a, Attribute>, 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 as _;
use crate::parse::{Parse, ParseStream, Result};
use std::fmt::{self, Display};
pub(crate) fn parse_inner(input: ParseStream, attrs: &mut Vec<Attribute>) -> 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<Attribute> {
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<Attribute> {
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<Self> {
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<Self> {
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<Self> {
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<Meta> {
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<MetaList> {
let (delimiter, tokens) = mac::parse_delimiter(input)?;
Ok(MetaList {
path,
delimiter,
tokens,
})
}
fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result<MetaNameValue> {
let eq_token: Token![=] = input.parse()?;
let ahead = input.fork();
let lit: Option<Lit> = 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);
}
}
}