use super::*; use crate::punctuated::Punctuated; ast_struct! { /// A path at which a named item is exported (e.g. `std::collections::HashMap`). #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Path { pub leading_colon: Option, pub segments: Punctuated, } } impl From for Path where T: Into, { 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> { /// 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(&self, ident: &I) -> bool where I: ?Sized, Ident: PartialEq, { 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(doc_cfg, 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(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct PathSegment { pub ident: Ident, pub arguments: PathArguments, } } impl From for PathSegment where T: Into, { 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 `std::slice::iter<'a, T>`. /// /// ## Parenthesized /// /// The `(A, B) -> C` in `Fn(A, B) -> C`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum PathArguments { None, /// The `<'a, T>` in `std::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(doc_cfg, 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`. AssocType(AssocType), /// An equality constraint on an associated constant: the `PANIC = /// false` in `Trait`. AssocConst(AssocConst), /// An associated type bound: `Iterator`. Constraint(Constraint), } } ast_struct! { /// Angle bracketed arguments of a path segment: the `` in `HashMap`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct AngleBracketedGenericArguments { pub colon2_token: Option, pub lt_token: Token![<], pub args: Punctuated, pub gt_token: Token![>], } } ast_struct! { /// A binding (equality constraint) on an associated type: the `Item = u8` /// in `Iterator`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct AssocType { pub ident: Ident, pub generics: Option, pub eq_token: Token![=], pub ty: Type, } } ast_struct! { /// An equality constraint on an associated constant: the `PANIC = false` in /// `Trait`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct AssocConst { pub ident: Ident, pub generics: Option, pub eq_token: Token![=], pub value: Expr, } } ast_struct! { /// An associated type bound: `Iterator`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Constraint { pub ident: Ident, pub generics: Option, pub colon_token: Token![:], pub bounds: Punctuated, } } ast_struct! { /// Arguments of a function path segment: the `(A, B) -> C` in `Fn(A,B) -> /// C`. #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct ParenthesizedGenericArguments { pub paren_token: token::Paren, /// `(A, B)` pub inputs: Punctuated, /// `C` pub output: ReturnType, } } ast_struct! { /// The explicit Self type in a qualified path: the `T` in `::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 /// as a::b::Trait>::AssociatedItem /// ^~~~~~ ~~~~~~~~~~~~~~^ /// ty position = 3 /// /// >::AssociatedItem /// ^~~~~~ ^ /// ty position = 0 /// ``` #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct QSelf { pub lt_token: Token![<], pub ty: Box, pub position: usize, pub as_token: Option, pub gt_token: Token![>], } } #[cfg(feature = "parsing")] pub(crate) mod parsing { use super::*; use crate::ext::IdentExt; use crate::parse::{Parse, ParseStream, Result}; #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for Path { fn parse(input: ParseStream) -> Result { Self::parse_helper(input, false) } } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for GenericArgument { fn parse(input: ParseStream) -> Result { 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::>()? { 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()?, })) }; } #[cfg(feature = "full")] if let Some(colon_token) = input.parse::>()? { 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; } let value: TypeParamBound = input.parse()?; bounds.push_value(value); 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 { 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::()?; 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(doc_cfg, doc(cfg(all(feature = "parsing", feature = "full"))))] pub fn parse_turbofish(input: ParseStream) -> Result { let colon2_token: Token![::] = input.parse()?; Self::do_parse(Some(colon2_token), input) } fn do_parse(colon2_token: Option, input: ParseStream) -> Result { 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(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for AngleBracketedGenericArguments { fn parse(input: ParseStream) -> Result { let colon2_token: Option = input.parse()?; Self::do_parse(colon2_token, input) } } #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for ParenthesizedGenericArguments { fn parse(input: ParseStream) -> Result { 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(doc_cfg, doc(cfg(feature = "parsing")))] impl Parse for PathSegment { fn parse(input: ParseStream) -> Result { Self::parse_helper(input, false) } } impl PathSegment { fn parse_helper(input: ParseStream, expr_style: bool) -> Result { 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.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 std::collections::HashMap; /// // /// // Note that generic parameters are not allowed in a `use` statement /// // so the following must not be accepted. /// // /// // use a::::c; /// struct SingleUse { /// use_token: Token![use], /// path: Path, /// } /// /// impl Parse for SingleUse { /// fn parse(input: ParseStream) -> Result { /// Ok(SingleUse { /// use_token: input.parse()?, /// path: input.call(Path::parse_mod_style)?, /// }) /// } /// } /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub fn parse_mod_style(input: ParseStream) -> Result { 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::().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 { 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, 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 super::*; use crate::print::TokensOrDefault; #[cfg(feature = "parsing")] use crate::spanned::Spanned; #[cfg(feature = "parsing")] use proc_macro2::Span; use proc_macro2::TokenStream; use quote::ToTokens; use std::cmp; #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for Path { fn to_tokens(&self, tokens: &mut TokenStream) { self.leading_colon.to_tokens(tokens); self.segments.to_tokens(tokens); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for PathSegment { fn to_tokens(&self, tokens: &mut TokenStream) { self.ident.to_tokens(tokens); self.arguments.to_tokens(tokens); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for PathArguments { fn to_tokens(&self, tokens: &mut TokenStream) { match self { PathArguments::None => {} PathArguments::AngleBracketed(arguments) => { arguments.to_tokens(tokens); } PathArguments::Parenthesized(arguments) => { arguments.to_tokens(tokens); } } } } #[cfg_attr(doc_cfg, 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) => match expr { Expr::Lit(expr) => expr.to_tokens(tokens), Expr::Path(expr) if expr.attrs.is_empty() && expr.qself.is_none() && expr.path.get_ident().is_some() => { expr.to_tokens(tokens); } #[cfg(feature = "full")] Expr::Block(expr) => expr.to_tokens(tokens), #[cfg(not(feature = "full"))] Expr::Verbatim(expr) => expr.to_tokens(tokens), // ERROR CORRECTION: Add braces to make sure that the // generated code is valid. _ => token::Brace::default().surround(tokens, |tokens| { expr.to_tokens(tokens); }), }, GenericArgument::AssocType(assoc) => assoc.to_tokens(tokens), GenericArgument::AssocConst(assoc) => assoc.to_tokens(tokens), GenericArgument::Constraint(constraint) => constraint.to_tokens(tokens), } } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for AngleBracketedGenericArguments { fn to_tokens(&self, tokens: &mut TokenStream) { self.colon2_token.to_tokens(tokens); self.lt_token.to_tokens(tokens); // Print lifetimes before types/consts/bindings, regardless of their // order in self.args. let mut trailing_or_empty = true; for param in self.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 self.args.pairs() { match param.value() { GenericArgument::Type(_) | GenericArgument::Const(_) | GenericArgument::AssocType(_) | GenericArgument::AssocConst(_) | GenericArgument::Constraint(_) => { if !trailing_or_empty { ::default().to_tokens(tokens); } param.to_tokens(tokens); trailing_or_empty = param.punct().is_some(); } GenericArgument::Lifetime(_) => {} } } self.gt_token.to_tokens(tokens); } } #[cfg_attr(doc_cfg, 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(doc_cfg, 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); self.value.to_tokens(tokens); } } #[cfg_attr(doc_cfg, 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(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ParenthesizedGenericArguments { fn to_tokens(&self, tokens: &mut TokenStream) { self.paren_token.surround(tokens, |tokens| { self.inputs.to_tokens(tokens); }); self.output.to_tokens(tokens); } } pub(crate) fn print_path(tokens: &mut TokenStream, qself: &Option, path: &Path) { let qself = match qself { Some(qself) => qself, None => { path.to_tokens(tokens); 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() { if i + 1 == pos { segment.value().to_tokens(tokens); qself.gt_token.to_tokens(tokens); segment.punct().to_tokens(tokens); } else { segment.to_tokens(tokens); } } } else { qself.gt_token.to_tokens(tokens); path.leading_colon.to_tokens(tokens); } for segment in segments { segment.to_tokens(tokens); } } #[cfg(feature = "parsing")] #[cfg_attr(doc_cfg, 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() } } }