#[cfg(feature = "parsing")]
use crate::error::Result;
use crate::expr::Expr;
use crate::generics::TypeParamBound;
use crate::ident::Ident;
use crate::lifetime::Lifetime;
use crate::punctuated::Punctuated;
use crate::token;
use crate::ty::{ReturnType, Type};
use alloc::boxed::Box;

ast_struct! {
    /// A path at which a named item is exported (e.g. `alloc::collections::HashMap`).
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct Path {
        pub leading_colon: Option<Token![::]>,
        pub segments: Punctuated<PathSegment, Token![::]>,
    }
}

impl<T> From<T> for Path
where
    T: Into<PathSegment>,
{
    fn from(segment: T) -> Self {
        let mut path = Path {
            leading_colon: None,
            segments: Punctuated::new(),
        };
        path.segments.push_value(segment.into());
        path
    }
}

impl Path {
    /// Determines whether this is a path of length 1 equal to the given
    /// ident.
    ///
    /// For them to compare equal, it must be the case that:
    ///
    /// - the path has no leading colon,
    /// - the number of path segments is 1,
    /// - the first path segment has no angle bracketed or parenthesized
    ///   path arguments, and
    /// - the ident of the first path segment is equal to the given one.
    ///
    /// # Example
    ///
    /// ```
    /// use proc_macro2::TokenStream;
    /// use syn::{Attribute, Error, Meta, Result};
    ///
    /// fn get_serde_meta_item(attr: &Attribute) -> Result<Option<&TokenStream>> {
    ///     if attr.path().is_ident("serde") {
    ///         match &attr.meta {
    ///             Meta::List(meta) => Ok(Some(&meta.tokens)),
    ///             bad => Err(Error::new_spanned(bad, "unrecognized attribute")),
    ///         }
    ///     } else {
    ///         Ok(None)
    ///     }
    /// }
    /// ```
    pub fn is_ident<I>(&self, ident: &I) -> bool
    where
        I: ?Sized,
        Ident: PartialEq<I>,
    {
        match self.get_ident() {
            Some(id) => id == ident,
            None => false,
        }
    }

    /// If this path consists of a single ident, returns the ident.
    ///
    /// A path is considered an ident if:
    ///
    /// - the path has no leading colon,
    /// - the number of path segments is 1, and
    /// - the first path segment has no angle bracketed or parenthesized
    ///   path arguments.
    pub fn get_ident(&self) -> Option<&Ident> {
        if self.leading_colon.is_none()
            && self.segments.len() == 1
            && self.segments[0].arguments.is_none()
        {
            Some(&self.segments[0].ident)
        } else {
            None
        }
    }

    /// An error if this path is not a single ident, as defined in `get_ident`.
    #[cfg(feature = "parsing")]
    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    pub fn require_ident(&self) -> Result<&Ident> {
        self.get_ident().ok_or_else(|| {
            crate::error::new2(
                self.segments.first().unwrap().ident.span(),
                self.segments.last().unwrap().ident.span(),
                "expected this path to be an identifier",
            )
        })
    }
}

ast_struct! {
    /// A segment of a path together with any path arguments on that segment.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct PathSegment {
        pub ident: Ident,
        pub arguments: PathArguments,
    }
}

impl<T> From<T> for PathSegment
where
    T: Into<Ident>,
{
    fn from(ident: T) -> Self {
        PathSegment {
            ident: ident.into(),
            arguments: PathArguments::None,
        }
    }
}

ast_enum! {
    /// Angle bracketed or parenthesized arguments of a path segment.
    ///
    /// ## Angle bracketed
    ///
    /// The `<'a, T>` in `core::slice::iter<'a, T>`.
    ///
    /// ## Parenthesized
    ///
    /// The `(A, B) -> C` in `Fn(A, B) -> C`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub enum PathArguments {
        None,
        /// The `<'a, T>` in `core::slice::iter<'a, T>`.
        AngleBracketed(AngleBracketedGenericArguments),
        /// The `(A, B) -> C` in `Fn(A, B) -> C`.
        Parenthesized(ParenthesizedGenericArguments),
    }
}

impl Default for PathArguments {
    fn default() -> Self {
        PathArguments::None
    }
}

impl PathArguments {
    pub fn is_empty(&self) -> bool {
        match self {
            PathArguments::None => true,
            PathArguments::AngleBracketed(bracketed) => bracketed.args.is_empty(),
            PathArguments::Parenthesized(_) => false,
        }
    }

    pub fn is_none(&self) -> bool {
        match self {
            PathArguments::None => true,
            PathArguments::AngleBracketed(_) | PathArguments::Parenthesized(_) => false,
        }
    }
}

ast_enum! {
    /// An individual generic argument, like `'a`, `T`, or `Item = T`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    #[non_exhaustive]
    pub enum GenericArgument {
        /// A lifetime argument.
        Lifetime(Lifetime),
        /// A type argument.
        Type(Type),
        /// A const expression. Must be inside of a block.
        ///
        /// NOTE: Identity expressions are represented as Type arguments, as
        /// they are indistinguishable syntactically.
        Const(Expr),
        /// A binding (equality constraint) on an associated type: the `Item =
        /// u8` in `Iterator<Item = u8>`.
        AssocType(AssocType),
        /// An equality constraint on an associated constant: the `PANIC =
        /// false` in `Trait<PANIC = false>`.
        AssocConst(AssocConst),
        /// An associated type bound: `Iterator<Item: Display>`.
        Constraint(Constraint),
    }
}

ast_struct! {
    /// Angle bracketed arguments of a path segment: the `<K, V>` in `HashMap<K,
    /// V>`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct AngleBracketedGenericArguments {
        pub colon2_token: Option<Token![::]>,
        pub lt_token: Token![<],
        pub args: Punctuated<GenericArgument, Token![,]>,
        pub gt_token: Token![>],
    }
}

ast_struct! {
    /// A binding (equality constraint) on an associated type: the `Item = u8`
    /// in `Iterator<Item = u8>`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct AssocType {
        pub ident: Ident,
        pub generics: Option<AngleBracketedGenericArguments>,
        pub eq_token: Token![=],
        pub ty: Type,
    }
}

ast_struct! {
    /// An equality constraint on an associated constant: the `PANIC = false` in
    /// `Trait<PANIC = false>`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct AssocConst {
        pub ident: Ident,
        pub generics: Option<AngleBracketedGenericArguments>,
        pub eq_token: Token![=],
        pub value: Expr,
    }
}

ast_struct! {
    /// An associated type bound: `Iterator<Item: Display>`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct Constraint {
        pub ident: Ident,
        pub generics: Option<AngleBracketedGenericArguments>,
        pub colon_token: Token![:],
        pub bounds: Punctuated<TypeParamBound, Token![+]>,
    }
}

ast_struct! {
    /// Arguments of a function path segment: the `(A, B) -> C` in `Fn(A,B) ->
    /// C`.
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct ParenthesizedGenericArguments {
        pub paren_token: token::Paren,
        /// `(A, B)`
        pub inputs: Punctuated<Type, Token![,]>,
        /// `C`
        pub output: ReturnType,
    }
}

ast_struct! {
    /// The explicit Self type in a qualified path: the `T` in `<T as
    /// Display>::fmt`.
    ///
    /// The actual path, including the trait and the associated item, is stored
    /// separately. The `position` field represents the index of the associated
    /// item qualified with this Self type.
    ///
    /// ```text
    /// <Vec<T> as a::b::Trait>::AssociatedItem
    ///  ^~~~~~    ~~~~~~~~~~~~~~^
    ///  ty        position = 3
    ///
    /// <Vec<T>>::AssociatedItem
    ///  ^~~~~~   ^
    ///  ty       position = 0
    /// ```
    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
    pub struct QSelf {
        pub lt_token: Token![<],
        pub ty: Box<Type>,
        pub position: usize,
        pub as_token: Option<Token![as]>,
        pub gt_token: Token![>],
    }
}

#[cfg(feature = "parsing")]
pub(crate) mod parsing {
    use crate::error::Result;
    #[cfg(feature = "full")]
    use crate::expr::ExprBlock;
    use crate::expr::{Expr, ExprPath};
    use crate::ext::IdentExt as _;
    use crate::generics::TypeParamBound;
    use crate::ident::Ident;
    use crate::lifetime::Lifetime;
    use crate::lit::Lit;
    use crate::parse::{Parse, ParseStream};
    use crate::path::{
        AngleBracketedGenericArguments, AssocConst, AssocType, Constraint, GenericArgument,
        ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf,
    };
    use crate::punctuated::Punctuated;
    use crate::token;
    use crate::ty::{ReturnType, Type};
    #[cfg(not(feature = "full"))]
    use crate::verbatim;
    use alloc::boxed::Box;
    use alloc::vec::Vec;

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for Path {
        fn parse(input: ParseStream) -> Result<Self> {
            Self::parse_helper(input, false)
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for GenericArgument {
        fn parse(input: ParseStream) -> Result<Self> {
            if input.peek(Lifetime) && !input.peek2(Token![+]) {
                return Ok(GenericArgument::Lifetime(input.parse()?));
            }

            if input.peek(Lit) || input.peek(token::Brace) {
                return const_argument(input).map(GenericArgument::Const);
            }

            let mut argument: Type = input.parse()?;

            match argument {
                Type::Path(mut ty)
                    if ty.qself.is_none()
                        && ty.path.leading_colon.is_none()
                        && ty.path.segments.len() == 1
                        && match &ty.path.segments[0].arguments {
                            PathArguments::None | PathArguments::AngleBracketed(_) => true,
                            PathArguments::Parenthesized(_) => false,
                        } =>
                {
                    if let Some(eq_token) = input.parse::<Option<Token![=]>>()? {
                        let segment = ty.path.segments.pop().unwrap().into_value();
                        let ident = segment.ident;
                        let generics = match segment.arguments {
                            PathArguments::None => None,
                            PathArguments::AngleBracketed(arguments) => Some(arguments),
                            PathArguments::Parenthesized(_) => unreachable!(),
                        };
                        return if input.peek(Lit) || input.peek(token::Brace) {
                            Ok(GenericArgument::AssocConst(AssocConst {
                                ident,
                                generics,
                                eq_token,
                                value: const_argument(input)?,
                            }))
                        } else {
                            Ok(GenericArgument::AssocType(AssocType {
                                ident,
                                generics,
                                eq_token,
                                ty: input.parse()?,
                            }))
                        };
                    }

                    if let Some(colon_token) = input.parse::<Option<Token![:]>>()? {
                        let segment = ty.path.segments.pop().unwrap().into_value();
                        return Ok(GenericArgument::Constraint(Constraint {
                            ident: segment.ident,
                            generics: match segment.arguments {
                                PathArguments::None => None,
                                PathArguments::AngleBracketed(arguments) => Some(arguments),
                                PathArguments::Parenthesized(_) => unreachable!(),
                            },
                            colon_token,
                            bounds: {
                                let mut bounds = Punctuated::new();
                                loop {
                                    if input.peek(Token![,]) || input.peek(Token![>]) {
                                        break;
                                    }
                                    bounds.push_value({
                                        let allow_precise_capture = false;
                                        let allow_const = true;
                                        TypeParamBound::parse_single(
                                            input,
                                            allow_precise_capture,
                                            allow_const,
                                        )?
                                    });
                                    if !input.peek(Token![+]) {
                                        break;
                                    }
                                    let punct: Token![+] = input.parse()?;
                                    bounds.push_punct(punct);
                                }
                                bounds
                            },
                        }));
                    }

                    argument = Type::Path(ty);
                }
                _ => {}
            }

            Ok(GenericArgument::Type(argument))
        }
    }

    pub(crate) fn const_argument(input: ParseStream) -> Result<Expr> {
        let lookahead = input.lookahead1();

        if input.peek(Lit) {
            let lit = input.parse()?;
            return Ok(Expr::Lit(lit));
        }

        if input.peek(Ident) {
            let ident: Ident = input.parse()?;
            return Ok(Expr::Path(ExprPath {
                attrs: Vec::new(),
                qself: None,
                path: Path::from(ident),
            }));
        }

        if input.peek(token::Brace) {
            #[cfg(feature = "full")]
            {
                let block: ExprBlock = input.parse()?;
                return Ok(Expr::Block(block));
            }

            #[cfg(not(feature = "full"))]
            {
                let begin = input.fork();
                let content;
                braced!(content in input);
                content.parse::<Expr>()?;
                let verbatim = verbatim::between(&begin, input);
                return Ok(Expr::Verbatim(verbatim));
            }
        }

        Err(lookahead.error())
    }

    impl AngleBracketedGenericArguments {
        /// Parse `::<…>` with mandatory leading `::`.
        ///
        /// The ordinary [`Parse`] impl for `AngleBracketedGenericArguments`
        /// parses optional leading `::`.
        #[cfg(feature = "full")]
        #[cfg_attr(docsrs, doc(cfg(all(feature = "parsing", feature = "full"))))]
        pub fn parse_turbofish(input: ParseStream) -> Result<Self> {
            let colon2_token: Token![::] = input.parse()?;
            Self::do_parse(Some(colon2_token), input)
        }

        pub(crate) fn do_parse(
            colon2_token: Option<Token![::]>,
            input: ParseStream,
        ) -> Result<Self> {
            Ok(AngleBracketedGenericArguments {
                colon2_token,
                lt_token: input.parse()?,
                args: {
                    let mut args = Punctuated::new();
                    loop {
                        if input.peek(Token![>]) {
                            break;
                        }
                        let value: GenericArgument = input.parse()?;
                        args.push_value(value);
                        if input.peek(Token![>]) {
                            break;
                        }
                        let punct: Token![,] = input.parse()?;
                        args.push_punct(punct);
                    }
                    args
                },
                gt_token: input.parse()?,
            })
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for AngleBracketedGenericArguments {
        fn parse(input: ParseStream) -> Result<Self> {
            let colon2_token: Option<Token![::]> = input.parse()?;
            Self::do_parse(colon2_token, input)
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for ParenthesizedGenericArguments {
        fn parse(input: ParseStream) -> Result<Self> {
            let content;
            Ok(ParenthesizedGenericArguments {
                paren_token: parenthesized!(content in input),
                inputs: content.parse_terminated(Type::parse, Token![,])?,
                output: input.call(ReturnType::without_plus)?,
            })
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for PathSegment {
        fn parse(input: ParseStream) -> Result<Self> {
            Self::parse_helper(input, false)
        }
    }

    impl PathSegment {
        fn parse_helper(input: ParseStream, expr_style: bool) -> Result<Self> {
            if input.peek(Token![super])
                || input.peek(Token![self])
                || input.peek(Token![crate])
                || cfg!(feature = "full") && input.peek(Token![try])
            {
                let ident = input.call(Ident::parse_any)?;
                return Ok(PathSegment::from(ident));
            }

            let ident = if input.peek(Token![Self]) {
                input.call(Ident::parse_any)?
            } else {
                input.parse()?
            };

            if !expr_style
                && input.peek(Token![<])
                && !input.peek(Token![<=])
                && !input.peek(Token![<<=])
                || input.peek(Token![::]) && input.peek3(Token![<])
            {
                Ok(PathSegment {
                    ident,
                    arguments: PathArguments::AngleBracketed(input.parse()?),
                })
            } else {
                Ok(PathSegment::from(ident))
            }
        }
    }

    impl Path {
        /// Parse a `Path` containing no path arguments on any of its segments.
        ///
        /// # Example
        ///
        /// ```
        /// use syn::{Path, Result, Token};
        /// use syn::parse::{Parse, ParseStream};
        ///
        /// // A simplified single `use` statement like:
        /// //
        /// //     use alloc::collections::HashMap;
        /// //
        /// // Note that generic parameters are not allowed in a `use` statement
        /// // so the following must not be accepted.
        /// //
        /// //     use a::<b>::c;
        /// struct SingleUse {
        ///     use_token: Token![use],
        ///     path: Path,
        /// }
        ///
        /// impl Parse for SingleUse {
        ///     fn parse(input: ParseStream) -> Result<Self> {
        ///         Ok(SingleUse {
        ///             use_token: input.parse()?,
        ///             path: input.call(Path::parse_mod_style)?,
        ///         })
        ///     }
        /// }
        /// ```
        #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
        pub fn parse_mod_style(input: ParseStream) -> Result<Self> {
            Ok(Path {
                leading_colon: input.parse()?,
                segments: {
                    let mut segments = Punctuated::new();
                    loop {
                        if !input.peek(Ident)
                            && !input.peek(Token![super])
                            && !input.peek(Token![self])
                            && !input.peek(Token![Self])
                            && !input.peek(Token![crate])
                        {
                            break;
                        }
                        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.parse::<Ident>().unwrap_err());
                    } else if segments.trailing_punct() {
                        return Err(input.error("expected path segment after `::`"));
                    }
                    segments
                },
            })
        }

        pub(crate) fn parse_helper(input: ParseStream, expr_style: bool) -> Result<Self> {
            let mut path = Path {
                leading_colon: input.parse()?,
                segments: {
                    let mut segments = Punctuated::new();
                    let value = PathSegment::parse_helper(input, expr_style)?;
                    segments.push_value(value);
                    segments
                },
            };
            Path::parse_rest(input, &mut path, expr_style)?;
            Ok(path)
        }

        pub(crate) fn parse_rest(
            input: ParseStream,
            path: &mut Self,
            expr_style: bool,
        ) -> Result<()> {
            while input.peek(Token![::]) && !input.peek3(token::Paren) {
                let punct: Token![::] = input.parse()?;
                path.segments.push_punct(punct);
                let value = PathSegment::parse_helper(input, expr_style)?;
                path.segments.push_value(value);
            }
            Ok(())
        }

        pub(crate) fn is_mod_style(&self) -> bool {
            self.segments
                .iter()
                .all(|segment| segment.arguments.is_none())
        }
    }

    pub(crate) fn qpath(input: ParseStream, expr_style: bool) -> Result<(Option<QSelf>, Path)> {
        if input.peek(Token![<]) {
            let lt_token: Token![<] = input.parse()?;
            let this: Type = input.parse()?;
            let path = if input.peek(Token![as]) {
                let as_token: Token![as] = input.parse()?;
                let path: Path = input.parse()?;
                Some((as_token, path))
            } else {
                None
            };
            let gt_token: Token![>] = input.parse()?;
            let colon2_token: Token![::] = input.parse()?;
            let mut rest = Punctuated::new();
            loop {
                let path = PathSegment::parse_helper(input, expr_style)?;
                rest.push_value(path);
                if !input.peek(Token![::]) {
                    break;
                }
                let punct: Token![::] = input.parse()?;
                rest.push_punct(punct);
            }
            let (position, as_token, path) = match path {
                Some((as_token, mut path)) => {
                    let pos = path.segments.len();
                    path.segments.push_punct(colon2_token);
                    path.segments.extend(rest.into_pairs());
                    (pos, Some(as_token), path)
                }
                None => {
                    let path = Path {
                        leading_colon: Some(colon2_token),
                        segments: rest,
                    };
                    (0, None, path)
                }
            };
            let qself = QSelf {
                lt_token,
                ty: Box::new(this),
                position,
                as_token,
                gt_token,
            };
            Ok((Some(qself), path))
        } else {
            let path = Path::parse_helper(input, expr_style)?;
            Ok((None, path))
        }
    }
}

#[cfg(feature = "printing")]
pub(crate) mod printing {
    use crate::generics;
    use crate::path::{
        AngleBracketedGenericArguments, AssocConst, AssocType, Constraint, GenericArgument,
        ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf,
    };
    use crate::print::TokensOrDefault;
    #[cfg(feature = "parsing")]
    use crate::spanned::Spanned;
    use core::cmp;
    #[cfg(feature = "parsing")]
    use proc_macro2::Span;
    use proc_macro2::TokenStream;
    use quote::ToTokens;

    pub(crate) enum PathStyle {
        Expr,
        Mod,
        AsWritten,
    }

    impl Copy for PathStyle {}

    impl Clone for PathStyle {
        fn clone(&self) -> Self {
            *self
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for Path {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            print_path(tokens, self, PathStyle::AsWritten);
        }
    }

    pub(crate) fn print_path(tokens: &mut TokenStream, path: &Path, style: PathStyle) {
        path.leading_colon.to_tokens(tokens);
        for segment in path.segments.pairs() {
            print_path_segment(tokens, segment.value(), style);
            segment.punct().to_tokens(tokens);
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for PathSegment {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            print_path_segment(tokens, self, PathStyle::AsWritten);
        }
    }

    fn print_path_segment(tokens: &mut TokenStream, segment: &PathSegment, style: PathStyle) {
        segment.ident.to_tokens(tokens);
        print_path_arguments(tokens, &segment.arguments, style);
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for PathArguments {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            print_path_arguments(tokens, self, PathStyle::AsWritten);
        }
    }

    fn print_path_arguments(tokens: &mut TokenStream, arguments: &PathArguments, style: PathStyle) {
        match arguments {
            PathArguments::None => {}
            PathArguments::AngleBracketed(arguments) => {
                print_angle_bracketed_generic_arguments(tokens, arguments, style);
            }
            PathArguments::Parenthesized(arguments) => {
                print_parenthesized_generic_arguments(tokens, arguments, style);
            }
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for GenericArgument {
        #[allow(clippy::match_same_arms)]
        fn to_tokens(&self, tokens: &mut TokenStream) {
            match self {
                GenericArgument::Lifetime(lt) => lt.to_tokens(tokens),
                GenericArgument::Type(ty) => ty.to_tokens(tokens),
                GenericArgument::Const(expr) => {
                    generics::printing::print_const_argument(expr, tokens);
                }
                GenericArgument::AssocType(assoc) => assoc.to_tokens(tokens),
                GenericArgument::AssocConst(assoc) => assoc.to_tokens(tokens),
                GenericArgument::Constraint(constraint) => constraint.to_tokens(tokens),
            }
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for AngleBracketedGenericArguments {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            print_angle_bracketed_generic_arguments(tokens, self, PathStyle::AsWritten);
        }
    }

    pub(crate) fn print_angle_bracketed_generic_arguments(
        tokens: &mut TokenStream,
        arguments: &AngleBracketedGenericArguments,
        style: PathStyle,
    ) {
        if let PathStyle::Mod = style {
            return;
        }

        conditionally_print_turbofish(tokens, &arguments.colon2_token, style);
        arguments.lt_token.to_tokens(tokens);

        // Print lifetimes before types/consts/bindings, regardless of their
        // order in args.
        let mut trailing_or_empty = true;
        for param in arguments.args.pairs() {
            match param.value() {
                GenericArgument::Lifetime(_) => {
                    param.to_tokens(tokens);
                    trailing_or_empty = param.punct().is_some();
                }
                GenericArgument::Type(_)
                | GenericArgument::Const(_)
                | GenericArgument::AssocType(_)
                | GenericArgument::AssocConst(_)
                | GenericArgument::Constraint(_) => {}
            }
        }
        for param in arguments.args.pairs() {
            match param.value() {
                GenericArgument::Type(_)
                | GenericArgument::Const(_)
                | GenericArgument::AssocType(_)
                | GenericArgument::AssocConst(_)
                | GenericArgument::Constraint(_) => {
                    if !trailing_or_empty {
                        <Token![,]>::default().to_tokens(tokens);
                    }
                    param.to_tokens(tokens);
                    trailing_or_empty = param.punct().is_some();
                }
                GenericArgument::Lifetime(_) => {}
            }
        }

        arguments.gt_token.to_tokens(tokens);
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for AssocType {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.generics.to_tokens(tokens);
            self.eq_token.to_tokens(tokens);
            self.ty.to_tokens(tokens);
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for AssocConst {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.generics.to_tokens(tokens);
            self.eq_token.to_tokens(tokens);
            generics::printing::print_const_argument(&self.value, tokens);
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for Constraint {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.generics.to_tokens(tokens);
            self.colon_token.to_tokens(tokens);
            self.bounds.to_tokens(tokens);
        }
    }

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for ParenthesizedGenericArguments {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            print_parenthesized_generic_arguments(tokens, self, PathStyle::AsWritten);
        }
    }

    fn print_parenthesized_generic_arguments(
        tokens: &mut TokenStream,
        arguments: &ParenthesizedGenericArguments,
        style: PathStyle,
    ) {
        if let PathStyle::Mod = style {
            return;
        }

        conditionally_print_turbofish(tokens, &None, style);
        arguments.paren_token.surround(tokens, |tokens| {
            arguments.inputs.to_tokens(tokens);
        });
        arguments.output.to_tokens(tokens);
    }

    pub(crate) fn print_qpath(
        tokens: &mut TokenStream,
        qself: &Option<QSelf>,
        path: &Path,
        style: PathStyle,
    ) {
        let qself = match qself {
            Some(qself) => qself,
            None => {
                print_path(tokens, path, style);
                return;
            }
        };
        qself.lt_token.to_tokens(tokens);
        qself.ty.to_tokens(tokens);

        let pos = cmp::min(qself.position, path.segments.len());
        let mut segments = path.segments.pairs();
        if pos > 0 {
            TokensOrDefault(&qself.as_token).to_tokens(tokens);
            path.leading_colon.to_tokens(tokens);
            for (i, segment) in segments.by_ref().take(pos).enumerate() {
                print_path_segment(tokens, segment.value(), PathStyle::AsWritten);
                if i + 1 == pos {
                    qself.gt_token.to_tokens(tokens);
                }
                segment.punct().to_tokens(tokens);
            }
        } else {
            qself.gt_token.to_tokens(tokens);
            path.leading_colon.to_tokens(tokens);
        }
        for segment in segments {
            print_path_segment(tokens, segment.value(), style);
            segment.punct().to_tokens(tokens);
        }
    }

    fn conditionally_print_turbofish(
        tokens: &mut TokenStream,
        colon2_token: &Option<Token![::]>,
        style: PathStyle,
    ) {
        match style {
            PathStyle::Expr => TokensOrDefault(colon2_token).to_tokens(tokens),
            PathStyle::Mod => unreachable!(),
            PathStyle::AsWritten => colon2_token.to_tokens(tokens),
        }
    }

    #[cfg(feature = "parsing")]
    #[cfg_attr(docsrs, doc(cfg(all(feature = "parsing", feature = "printing"))))]
    impl Spanned for QSelf {
        fn span(&self) -> Span {
            struct QSelfDelimiters<'a>(&'a QSelf);

            impl<'a> ToTokens for QSelfDelimiters<'a> {
                fn to_tokens(&self, tokens: &mut TokenStream) {
                    self.0.lt_token.to_tokens(tokens);
                    self.0.gt_token.to_tokens(tokens);
                }
            }

            QSelfDelimiters(self).span()
        }
    }
}
