From a7e6f4366a2b854c80334a65f4996f231c1896e6 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 28 Jul 2019 15:42:05 -0700 Subject: [PATCH] Parse paths in Meta Closes #597. --- src/attr.rs | 117 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 0a2e6cc4..ae2bab7f 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -9,6 +9,8 @@ use proc_macro2::{Delimiter, Spacing, TokenTree}; #[cfg(feature = "parsing")] use crate::parse::{ParseStream, Result}; +#[cfg(feature = "parsing")] +use crate::punctuated::Pair; #[cfg(feature = "extra-traits")] use crate::tt::TokenStreamHelper; #[cfg(feature = "extra-traits")] @@ -135,22 +137,29 @@ impl Attribute { /// a [`Meta`] if possible. #[cfg(feature = "parsing")] pub fn parse_meta(&self) -> Result { - if let Some(ref colon) = self.path.leading_colon { - return Err(Error::new(colon.spans[0], "expected meta identifier")); + fn clone_ident_segment(segment: &PathSegment) -> PathSegment { + PathSegment { + ident: segment.ident.clone(), + arguments: PathArguments::None, + } } - let first_segment = self - .path - .segments - .pairs() - .next() - .expect("paths have at least one segment"); - if let Some(colon) = first_segment.punct() { - return Err(Error::new(colon.spans[0], "expected meta value")); - } - let ident = first_segment.value().ident.clone(); + let path = Path { + leading_colon: self.path.leading_colon.clone(), + segments: self + .path + .segments + .pairs() + .map(|pair| match pair { + Pair::Punctuated(seg, punct) => { + Pair::Punctuated(clone_ident_segment(seg), punct.clone()) + } + Pair::End(seg) => Pair::End(clone_ident_segment(seg)), + }) + .collect(), + }; - let parser = |input: ParseStream| parsing::parse_meta_after_ident(ident, input); + let parser = |input: ParseStream| parsing::parse_meta_after_path(path, input); parse::Parser::parse2(parser, self.tokens.clone()) } @@ -271,7 +280,7 @@ fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[Toke span: ident.span(), })) } else { - NestedMeta::Meta(Meta::Word(ident.clone())) + NestedMeta::Meta(Meta::Path(ident.clone())) }; Some((nested_meta, &tts[1..])) } @@ -352,9 +361,9 @@ ast_enum_of_structs! { /// *This type is available if Syn is built with the `"derive"` or `"full"` /// feature.* /// - /// ## Word + /// ## Path /// - /// A meta word is like the `test` in `#[test]`. + /// A meta path is like the `test` in `#[test]`. /// /// ## List /// @@ -374,13 +383,13 @@ ast_enum_of_structs! { // TODO: change syntax-tree-enum link to an intra rustdoc link, currently // blocked on https://github.com/rust-lang/rust/issues/62833 pub enum Meta { - pub Word(Ident), + pub Path(Path), /// A structured list within an attribute, like `derive(Copy, Clone)`. /// /// *This type is available if Syn is built with the `"derive"` or /// `"full"` feature.* pub List(MetaList { - pub ident: Ident, + pub path: Path, pub paren_token: token::Paren, pub nested: Punctuated, }), @@ -389,7 +398,7 @@ ast_enum_of_structs! { /// *This type is available if Syn is built with the `"derive"` or /// `"full"` feature.* pub NameValue(MetaNameValue { - pub ident: Ident, + pub path: Path, pub eq_token: Token![=], pub lit: Lit, }), @@ -401,11 +410,11 @@ impl Meta { /// /// For example this would return the `test` in `#[test]`, the `derive` in /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`. - pub fn name(&self) -> Ident { + pub fn path(&self) -> &Path { match *self { - Meta::Word(ref meta) => meta.clone(), - Meta::List(ref meta) => meta.ident.clone(), - Meta::NameValue(ref meta) => meta.ident.clone(), + Meta::Path(ref path) => path, + Meta::List(ref meta) => &meta.path, + Meta::NameValue(ref meta) => &meta.path, } } } @@ -417,7 +426,7 @@ ast_enum_of_structs! { /// feature.* pub enum NestedMeta { /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which - /// would be a nested `Meta::Word`. + /// would be a nested `Meta::Path`. pub Meta(Meta), /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`. @@ -538,24 +547,49 @@ pub mod parsing { } } + // Like Path::parse_mod_style but accepts keywords in the path. + fn parse_meta_path(input: ParseStream) -> Result { + Ok(Path { + leading_colon: input.parse()?, + segments: { + let mut segments = Punctuated::new(); + while input.peek(Ident::peek_any) { + 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.error("expected path")); + } else if segments.trailing_punct() { + return Err(input.error("expected path segment")); + } + segments + }, + }) + } + impl Parse for Meta { fn parse(input: ParseStream) -> Result { - let ident = input.call(Ident::parse_any)?; - parse_meta_after_ident(ident, input) + let path = input.call(parse_meta_path)?; + parse_meta_after_path(path, input) } } impl Parse for MetaList { fn parse(input: ParseStream) -> Result { - let ident = input.call(Ident::parse_any)?; - parse_meta_list_after_ident(ident, input) + let path = input.call(parse_meta_path)?; + parse_meta_list_after_path(path, input) } } impl Parse for MetaNameValue { fn parse(input: ParseStream) -> Result { - let ident = input.call(Ident::parse_any)?; - parse_meta_name_value_after_ident(ident, input) + let path = input.call(parse_meta_path)?; + parse_meta_name_value_after_path(path, input) } } @@ -571,31 +605,28 @@ pub mod parsing { } } - pub fn parse_meta_after_ident(ident: Ident, input: ParseStream) -> Result { + pub fn parse_meta_after_path(path: Path, input: ParseStream) -> Result { if input.peek(token::Paren) { - parse_meta_list_after_ident(ident, input).map(Meta::List) + parse_meta_list_after_path(path, input).map(Meta::List) } else if input.peek(Token![=]) { - parse_meta_name_value_after_ident(ident, input).map(Meta::NameValue) + parse_meta_name_value_after_path(path, input).map(Meta::NameValue) } else { - Ok(Meta::Word(ident)) + Ok(Meta::Path(path)) } } - fn parse_meta_list_after_ident(ident: Ident, input: ParseStream) -> Result { + fn parse_meta_list_after_path(path: Path, input: ParseStream) -> Result { let content; Ok(MetaList { - ident: ident, + path: path, paren_token: parenthesized!(content in input), nested: content.parse_terminated(NestedMeta::parse)?, }) } - fn parse_meta_name_value_after_ident( - ident: Ident, - input: ParseStream, - ) -> Result { + fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result { Ok(MetaNameValue { - ident: ident, + path: path, eq_token: input.parse()?, lit: input.parse()?, }) @@ -623,7 +654,7 @@ mod printing { impl ToTokens for MetaList { fn to_tokens(&self, tokens: &mut TokenStream) { - self.ident.to_tokens(tokens); + self.path.to_tokens(tokens); self.paren_token.surround(tokens, |tokens| { self.nested.to_tokens(tokens); }) @@ -632,7 +663,7 @@ mod printing { impl ToTokens for MetaNameValue { fn to_tokens(&self, tokens: &mut TokenStream) { - self.ident.to_tokens(tokens); + self.path.to_tokens(tokens); self.eq_token.to_tokens(tokens); self.lit.to_tokens(tokens); }