1use syn::ext::IdentExt; 2use syn::parse::{Error, ParseStream, Result}; 3use syn::{Ident, LitStr, Token}; 4 5pub struct QualifiedName { 6 pub segments: Vec<Ident>, 7} 8 9impl QualifiedName { 10 pub fn parse_quoted(lit: &LitStr) -> Result<Self> { 11 if lit.value().is_empty() { 12 let segments = Vec::new(); 13 Ok(QualifiedName { segments }) 14 } else { 15 lit.parse_with(|input: ParseStream| { 16 let allow_raw = false; 17 parse_unquoted(input, allow_raw) 18 }) 19 } 20 } 21 22 pub fn parse_unquoted(input: ParseStream) -> Result<Self> { 23 let allow_raw = true; 24 parse_unquoted(input, allow_raw) 25 } 26 27 pub fn parse_quoted_or_unquoted(input: ParseStream) -> Result<Self> { 28 if input.peek(LitStr) { 29 let lit: LitStr = input.parse()?; 30 Self::parse_quoted(&lit) 31 } else { 32 Self::parse_unquoted(input) 33 } 34 } 35} 36 37fn parse_unquoted(input: ParseStream, allow_raw: bool) -> Result<QualifiedName> { 38 let mut segments = Vec::new(); 39 let mut trailing_punct = true; 40 let leading_colons: Option<Token![::]> = input.parse()?; 41 while trailing_punct && input.peek(Ident::peek_any) { 42 let mut ident = Ident::parse_any(input)?; 43 if let Some(unraw) = ident.to_string().strip_prefix("r#") { 44 if !allow_raw { 45 let msg = format!( 46 "raw identifier `{}` is not allowed in a quoted namespace; use `{}`, or remove quotes", 47 ident, unraw, 48 ); 49 return Err(Error::new(ident.span(), msg)); 50 } 51 ident = Ident::new(unraw, ident.span()); 52 } 53 segments.push(ident); 54 let colons: Option<Token![::]> = input.parse()?; 55 trailing_punct = colons.is_some(); 56 } 57 if segments.is_empty() && leading_colons.is_none() { 58 return Err(input.error("expected path")); 59 } else if trailing_punct { 60 return Err(input.error("expected path segment")); 61 } 62 Ok(QualifiedName { segments }) 63} 64