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.
    ///
    /// [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::<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);
        }
    }
}
