From eaa2cff1ed965e3cc53e5176838a1ed0ac408e0d Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 15 Apr 2017 03:32:13 -0700 Subject: [PATCH 01/12] Support parsing attributes with arbitrary tokens. --- src/attr.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/attr.rs b/src/attr.rs index 443daa5a46..8b5ee18a90 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -47,6 +47,11 @@ pub enum MetaItem { /// /// E.g. `feature = "foo"` as in `#[feature = "foo"]` NameValue(Ident, Lit), + + /// Tokens meta item. + /// + /// E.g. `test foo bar` as in `#[test foo bar]` + Tokens(Ident, Vec), } impl MetaItem { @@ -58,7 +63,8 @@ impl MetaItem { match *self { MetaItem::Word(ref name) | MetaItem::List(ref name, _) | - MetaItem::NameValue(ref name, _) => name.as_ref(), + MetaItem::NameValue(ref name, _) | + MetaItem::Tokens(ref name, _) => name.as_ref(), } } } @@ -111,6 +117,7 @@ pub mod parsing { use super::*; use ident::parsing::ident; use lit::parsing::lit; + use mac::parsing::token_trees; use synom::space::{block_comment, whitespace}; #[cfg(feature = "full")] @@ -169,6 +176,25 @@ pub mod parsing { }) ) | + do_parse!( + name_and_token_trees: preceded!( + punct!("#"), + delimited!( + punct!("["), + tuple!(ident, token_trees), + punct!("]") + ) + ) >> + (Attribute { + style: AttrStyle::Outer, + value: { + let (name, token_trees) = name_and_token_trees; + MetaItem::Tokens(name, token_trees) + }, + is_sugared_doc: false, + }) + ) + | do_parse!( punct!("///") >> not!(tag!("/")) >> @@ -286,6 +312,10 @@ mod printing { tokens.append("="); value.to_tokens(tokens); } + MetaItem::Tokens(ref name, ref token_trees) => { + name.to_tokens(tokens); + tokens.append_all(token_trees); + } } } } From bf395bf8c801aaea384af1ca98db8c8b54f25924 Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 15 Apr 2017 15:35:22 -0700 Subject: [PATCH 02/12] fixup! Only store tokens in Attribute. --- src/attr.rs | 309 +++++++++++++++++++++----------------- tests/test_generics.rs | 3 +- tests/test_macro_input.rs | 60 ++++++-- 3 files changed, 219 insertions(+), 153 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 8b5ee18a90..e4038828e9 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -6,13 +6,99 @@ use std::iter; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Attribute { pub style: AttrStyle, - pub value: MetaItem, + pub name: Ident, + pub tts: Vec, pub is_sugared_doc: bool, } impl Attribute { - pub fn name(&self) -> &str { - self.value.name() + pub fn meta_item(&self) -> Option { + if self.tts.is_empty() { + return Some(MetaItem::Word); + } + + if self.tts.len() == 1 { + if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, ref tts }) = self.tts[0] { + fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMetaItem, &[TokenTree])> { + assert!(!tts.is_empty()); + + match tts[0] { + TokenTree::Token(Token::Literal(ref lit)) => { + Some((NestedMetaItem::Literal(lit.clone()), &tts[1..])) + } + + TokenTree::Token(Token::Ident(ref ident)) => { + if tts.len() >= 3 { + match (&tts[1], &tts[2]) { + (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { + return Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::NameValue(lit.clone())), &tts[3..])); + } + + _ => {} + } + } + + if tts.len() >= 2 { + if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] { + return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) { + Some(nested_meta_items) => { + Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::List(nested_meta_items)), &tts[2..])) + } + + None => None + }; + } + } + + Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::Word), &tts[1..])) + } + + _ => None + } + } + + fn list_of_nested_meta_items_from_tokens(mut result: Vec, tts: &[TokenTree]) -> Option> { + if tts.is_empty() { + return Some(result); + } + + match nested_meta_item_from_tokens(tts) { + Some((nested_meta_item, rest)) => { + result.push(nested_meta_item); + if rest.is_empty() { + list_of_nested_meta_items_from_tokens(result, rest) + } + else { + if let TokenTree::Token(Token::Comma) = rest[0] { + list_of_nested_meta_items_from_tokens(result, &rest[1..]) + } + else { + None + } + } + } + + None => None + } + } + + if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) { + return Some(MetaItem::List(nested_meta_items)); + } + } + } + + if self.tts.len() == 2 { + match (&self.tts[0], &self.tts[1]) { + (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { + return Some(MetaItem::NameValue(lit.clone())); + } + + _ => {} + } + } + + None } } @@ -36,37 +122,17 @@ pub enum MetaItem { /// Word meta item. /// /// E.g. `test` as in `#[test]` - Word(Ident), + Word, /// List meta item. /// /// E.g. `derive(..)` as in `#[derive(..)]` - List(Ident, Vec), + List(Vec), /// Name-value meta item. /// /// E.g. `feature = "foo"` as in `#[feature = "foo"]` - NameValue(Ident, Lit), - - /// Tokens meta item. - /// - /// E.g. `test foo bar` as in `#[test foo bar]` - Tokens(Ident, Vec), -} - -impl MetaItem { - /// Name of the item. - /// - /// E.g. `test` as in `#[test]`, `derive` as in `#[derive(..)]`, and - /// `feature` as in `#[feature = "foo"]`. - pub fn name(&self) -> &str { - match *self { - MetaItem::Word(ref name) | - MetaItem::List(ref name, _) | - MetaItem::NameValue(ref name, _) | - MetaItem::Tokens(ref name, _) => name.as_ref(), - } - } + NameValue(Lit), } /// Possible values inside of compile-time attribute lists. @@ -76,8 +142,8 @@ impl MetaItem { pub enum NestedMetaItem { /// A full `MetaItem`. /// - /// E.g. `Copy` in `#[derive(Copy)]` would be a `MetaItem::Word(Ident::from("Copy"))`. - MetaItem(MetaItem), + /// E.g. `Copy` in `#[derive(Copy)]` would be a `(Ident::from("Copy"), MetaItem::Word)`. + MetaItem(Ident, MetaItem), /// A Rust literal. /// @@ -116,7 +182,6 @@ impl<'a, T> FilterAttrs<'a> for T pub mod parsing { use super::*; use ident::parsing::ident; - use lit::parsing::lit; use mac::parsing::token_trees; use synom::space::{block_comment, whitespace}; @@ -125,13 +190,20 @@ pub mod parsing { do_parse!( punct!("#") >> punct!("!") >> - punct!("[") >> - meta_item: meta_item >> - punct!("]") >> - (Attribute { - style: AttrStyle::Inner, - value: meta_item, - is_sugared_doc: false, + name_and_tts: delimited!( + punct!("["), + tuple!(ident, token_trees), + punct!("]") + ) >> + ({ + let (name, tts) = name_and_tts; + + Attribute { + style: AttrStyle::Inner, + name: name, + tts: tts, + is_sugared_doc: false, + } }) ) | @@ -140,10 +212,11 @@ pub mod parsing { content: take_until!("\n") >> (Attribute { style: AttrStyle::Inner, - value: MetaItem::NameValue( - "doc".into(), - format!("//!{}", content).into(), - ), + name: "doc".into(), + tts: vec![ + TokenTree::Token(Token::Eq), + TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))), + ], is_sugared_doc: true, }) ) @@ -154,10 +227,11 @@ pub mod parsing { com: block_comment >> (Attribute { style: AttrStyle::Inner, - value: MetaItem::NameValue( - "doc".into(), - com.into(), - ), + name: "doc".into(), + tts: vec![ + TokenTree::Token(Token::Eq), + TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))), + ], is_sugared_doc: true, }) ) @@ -166,32 +240,20 @@ pub mod parsing { named!(pub outer_attr -> Attribute, alt!( do_parse!( punct!("#") >> - punct!("[") >> - meta_item: meta_item >> - punct!("]") >> - (Attribute { - style: AttrStyle::Outer, - value: meta_item, - is_sugared_doc: false, - }) - ) - | - do_parse!( - name_and_token_trees: preceded!( - punct!("#"), - delimited!( - punct!("["), - tuple!(ident, token_trees), - punct!("]") - ) + name_and_tts: delimited!( + punct!("["), + tuple!(ident, token_trees), + punct!("]") ) >> - (Attribute { - style: AttrStyle::Outer, - value: { - let (name, token_trees) = name_and_token_trees; - MetaItem::Tokens(name, token_trees) - }, - is_sugared_doc: false, + ({ + let (name, tts) = name_and_tts; + + Attribute { + style: AttrStyle::Outer, + name: name, + tts: tts, + is_sugared_doc: false, + } }) ) | @@ -201,10 +263,11 @@ pub mod parsing { content: take_until!("\n") >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( - "doc".into(), - format!("///{}", content).into(), - ), + name: "doc".into(), + tts: vec![ + TokenTree::Token(Token::Eq), + TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))), + ], is_sugared_doc: true, }) ) @@ -215,39 +278,15 @@ pub mod parsing { com: block_comment >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( - "doc".into(), - com.into(), - ), + name: "doc".into(), + tts: vec![ + TokenTree::Token(Token::Eq), + TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))), + ], is_sugared_doc: true, }) ) )); - - named!(meta_item -> MetaItem, alt!( - do_parse!( - id: ident >> - punct!("(") >> - inner: terminated_list!(punct!(","), nested_meta_item) >> - punct!(")") >> - (MetaItem::List(id, inner)) - ) - | - do_parse!( - name: ident >> - punct!("=") >> - value: lit >> - (MetaItem::NameValue(name, value)) - ) - | - map!(ident, MetaItem::Word) - )); - - named!(nested_meta_item -> NestedMetaItem, alt!( - meta_item => { NestedMetaItem::MetaItem } - | - lit => { NestedMetaItem::Literal } - )); } #[cfg(feature = "printing")] @@ -258,28 +297,32 @@ mod printing { impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut Tokens) { - if let Attribute { style, - value: MetaItem::NameValue(ref name, - Lit::Str(ref value, StrStyle::Cooked)), - is_sugared_doc: true } = *self { - if name == "doc" { - match style { - AttrStyle::Inner if value.starts_with("//!") => { - tokens.append(&format!("{}\n", value)); - return; - } - AttrStyle::Inner if value.starts_with("/*!") => { - tokens.append(value); - return; - } - AttrStyle::Outer if value.starts_with("///") => { - tokens.append(&format!("{}\n", value)); - return; - } - AttrStyle::Outer if value.starts_with("/**") => { - tokens.append(value); - return; + if let Attribute { style, ref name, ref tts, is_sugared_doc: true } = *self { + if name == "doc" && tts.len() == 2 { + match (&tts[0], &tts[1]) { + (&TokenTree::Token(Token::Eq), + &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) => { + match style { + AttrStyle::Inner if value.starts_with("//!") => { + tokens.append(&format!("{}\n", value)); + return; + } + AttrStyle::Inner if value.starts_with("/*!") => { + tokens.append(value); + return; + } + AttrStyle::Outer if value.starts_with("///") => { + tokens.append(&format!("{}\n", value)); + return; + } + AttrStyle::Outer if value.starts_with("/**") => { + tokens.append(value); + return; + } + _ => {} + } } + _ => {} } } @@ -290,7 +333,8 @@ mod printing { tokens.append("!"); } tokens.append("["); - self.value.to_tokens(tokens); + self.name.to_tokens(tokens); + tokens.append_all(&self.tts); tokens.append("]"); } } @@ -298,24 +342,16 @@ mod printing { impl ToTokens for MetaItem { fn to_tokens(&self, tokens: &mut Tokens) { match *self { - MetaItem::Word(ref ident) => { - ident.to_tokens(tokens); - } - MetaItem::List(ref ident, ref inner) => { - ident.to_tokens(tokens); + MetaItem::Word => {} + MetaItem::List(ref inner) => { tokens.append("("); tokens.append_separated(inner, ","); tokens.append(")"); } - MetaItem::NameValue(ref name, ref value) => { - name.to_tokens(tokens); + MetaItem::NameValue(ref value) => { tokens.append("="); value.to_tokens(tokens); } - MetaItem::Tokens(ref name, ref token_trees) => { - name.to_tokens(tokens); - tokens.append_all(token_trees); - } } } } @@ -323,7 +359,8 @@ mod printing { impl ToTokens for NestedMetaItem { fn to_tokens(&self, tokens: &mut Tokens) { match *self { - NestedMetaItem::MetaItem(ref nested) => { + NestedMetaItem::MetaItem(ref ident, ref nested) => { + ident.to_tokens(tokens); nested.to_tokens(tokens); } NestedMetaItem::Literal(ref lit) => { diff --git a/tests/test_generics.rs b/tests/test_generics.rs index 0cdce4e37d..18ec457d03 100644 --- a/tests/test_generics.rs +++ b/tests/test_generics.rs @@ -21,7 +21,8 @@ fn test_split_for_impl() { ty_params: vec![TyParam { attrs: vec![Attribute { style: AttrStyle::Outer, - value: MetaItem::Word("may_dangle".into()), + name: "may_dangle".into(), + tts: vec![], is_sugared_doc: false, }], ident: Ident::new("T"), diff --git a/tests/test_macro_input.rs b/tests/test_macro_input.rs index b209bc6a99..2f96b8161a 100644 --- a/tests/test_macro_input.rs +++ b/tests/test_macro_input.rs @@ -30,14 +30,17 @@ fn test_struct() { ident: "Item".into(), vis: Visibility::Public, attrs: vec![Attribute { - style: AttrStyle::Outer, - value: MetaItem::List("derive".into(), - vec![ - NestedMetaItem::MetaItem(MetaItem::Word("Debug".into())), - NestedMetaItem::MetaItem(MetaItem::Word("Clone".into())), - ]), - is_sugared_doc: false, - }], + style: AttrStyle::Outer, + name: "derive".into(), + tts: vec![ + TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![ + TokenTree::Token(Token::Ident(Ident::from("Debug"))), + TokenTree::Token(Token::Comma), + TokenTree::Token(Token::Ident(Ident::from("Clone"))), + ]}) + ], + is_sugared_doc: false, + }], generics: Generics::default(), body: Body::Struct(VariantData::Struct(vec![Field { ident: Some("ident".into()), @@ -68,7 +71,16 @@ fn test_struct() { }])), }; - assert_eq!(expected, parse_macro_input(raw).unwrap()); + let actual = parse_macro_input(raw).unwrap(); + + assert_eq!(expected, actual); + + let expected_meta_item = MetaItem::List(vec![ + NestedMetaItem::MetaItem(Ident::from("Debug"), MetaItem::Word), + NestedMetaItem::MetaItem(Ident::from("Clone"), MetaItem::Word), + ]); + + assert_eq!(expected_meta_item, actual.attrs[0].meta_item().unwrap()); } #[test] @@ -94,18 +106,20 @@ fn test_enum() { attrs: vec![ Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( - "doc".into(), - Lit::Str( + name: "doc".into(), + tts: vec![ + TokenTree::Token(Token::Eq), + TokenTree::Token(Token::Literal(Lit::Str( "/// See the std::result module documentation for details.".into(), StrStyle::Cooked, - ), - ), + ))), + ], is_sugared_doc: true, }, Attribute { style: AttrStyle::Outer, - value: MetaItem::Word("must_use".into()), + name: "must_use".into(), + tts: vec![], is_sugared_doc: false, }, ], @@ -185,5 +199,19 @@ fn test_enum() { ]), }; - assert_eq!(expected, parse_macro_input(raw).unwrap()); + let actual = parse_macro_input(raw).unwrap(); + + assert_eq!(expected, actual); + + let expected_meta_items = vec![ + MetaItem::NameValue(Lit::Str( + "/// See the std::result module documentation for details.".into(), + StrStyle::Cooked, + )), + MetaItem::Word, + ]; + + let actual_meta_items: Vec<_> = actual.attrs.into_iter().map(|attr| attr.meta_item().unwrap()).collect(); + + assert_eq!(expected_meta_items, actual_meta_items); } From bb252731d626ac713ff54cc7acf2d55d580218fe Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 15 Apr 2017 16:15:15 -0700 Subject: [PATCH 03/12] fixup! Fixed build on 1.12.1 and 1.13.0 --- src/attr.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/attr.rs b/src/attr.rs index e4038828e9..be04fdb00f 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -182,6 +182,8 @@ impl<'a, T> FilterAttrs<'a> for T pub mod parsing { use super::*; use ident::parsing::ident; + use lit::{Lit, StrStyle}; + use mac::{Token, TokenTree}; use mac::parsing::token_trees; use synom::space::{block_comment, whitespace}; @@ -293,6 +295,7 @@ pub mod parsing { mod printing { use super::*; use lit::{Lit, StrStyle}; + use mac::{Token, TokenTree}; use quote::{Tokens, ToTokens}; impl ToTokens for Attribute { From aebc06ec6282f3842d43537acc140cc96653400e Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 15 Apr 2017 16:17:13 -0700 Subject: [PATCH 04/12] fixup! Satisfy clippy --- src/attr.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index be04fdb00f..653b1c1406 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -68,13 +68,11 @@ impl Attribute { if rest.is_empty() { list_of_nested_meta_items_from_tokens(result, rest) } + else if let TokenTree::Token(Token::Comma) = rest[0] { + list_of_nested_meta_items_from_tokens(result, &rest[1..]) + } else { - if let TokenTree::Token(Token::Comma) = rest[0] { - list_of_nested_meta_items_from_tokens(result, &rest[1..]) - } - else { - None - } + None } } From 4ded3935a828e37354e214165810339b3beab606 Mon Sep 17 00:00:00 2001 From: Arnavion Date: Sat, 15 Apr 2017 21:54:54 -0700 Subject: [PATCH 05/12] fixup! Added tests --- tests/test_meta_item.rs | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/test_meta_item.rs diff --git a/tests/test_meta_item.rs b/tests/test_meta_item.rs new file mode 100644 index 0000000000..ceb178cd52 --- /dev/null +++ b/tests/test_meta_item.rs @@ -0,0 +1,44 @@ +extern crate syn; +use syn::*; + +#[test] +fn test_meta_item_word() { + run_test("#[foo]", MetaItem::Word) +} + +#[test] +fn test_meta_item_name_value() { + run_test("#[foo = 5]", MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed))) +} + +#[test] +fn test_meta_item_list_lit() { + run_test("#[foo(5)]", MetaItem::List(vec![NestedMetaItem::Literal(Lit::Int(5, IntTy::Unsuffixed))])) +} + +#[test] +fn test_meta_item_list_word() { + run_test("#[foo(bar)]", MetaItem::List(vec![NestedMetaItem::MetaItem(Ident::from("bar"), MetaItem::Word)])) +} + +#[test] +fn test_meta_item_list_name_value() { + run_test("#[foo(bar = 5)]", MetaItem::List(vec![NestedMetaItem::MetaItem(Ident::from("bar"), MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed)))])) +} + +#[test] +fn test_meta_item_multiple() { + run_test("#[foo(word, name = 5, list(name2 = 6), word2)]", MetaItem::List(vec![ + NestedMetaItem::MetaItem(Ident::from("word"), MetaItem::Word), + NestedMetaItem::MetaItem(Ident::from("name"), MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed))), + NestedMetaItem::MetaItem(Ident::from("list"), MetaItem::List(vec![ + NestedMetaItem::MetaItem(Ident::from("name2"), MetaItem::NameValue(Lit::Int(6, IntTy::Unsuffixed))) + ])), + NestedMetaItem::MetaItem(Ident::from("word2"), MetaItem::Word), + ])) +} + +fn run_test(input: &str, expected: MetaItem) { + let attr = parse_outer_attr(input).unwrap(); + assert_eq!(expected, attr.meta_item().unwrap()); +} From e43b1b08f936b112f4008ad34f39fcc19c95a28c Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 02:47:45 -0700 Subject: [PATCH 06/12] fixup! Attribute::name -> path --- src/attr.rs | 50 ++++++++++++++++++++++++--------------- tests/test_generics.rs | 2 +- tests/test_macro_input.rs | 6 ++--- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 653b1c1406..14d0ccf9eb 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -6,7 +6,7 @@ use std::iter; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Attribute { pub style: AttrStyle, - pub name: Ident, + pub path: Path, pub tts: Vec, pub is_sugared_doc: bool, } @@ -179,28 +179,28 @@ impl<'a, T> FilterAttrs<'a> for T #[cfg(feature = "parsing")] pub mod parsing { use super::*; - use ident::parsing::ident; use lit::{Lit, StrStyle}; use mac::{Token, TokenTree}; use mac::parsing::token_trees; use synom::space::{block_comment, whitespace}; + use ty::parsing::path; #[cfg(feature = "full")] named!(pub inner_attr -> Attribute, alt!( do_parse!( punct!("#") >> punct!("!") >> - name_and_tts: delimited!( + path_and_tts: delimited!( punct!("["), - tuple!(ident, token_trees), + tuple!(path, token_trees), punct!("]") ) >> ({ - let (name, tts) = name_and_tts; + let (path, tts) = path_and_tts; Attribute { style: AttrStyle::Inner, - name: name, + path: path, tts: tts, is_sugared_doc: false, } @@ -212,7 +212,7 @@ pub mod parsing { content: take_until!("\n") >> (Attribute { style: AttrStyle::Inner, - name: "doc".into(), + path: "doc".into(), tts: vec![ TokenTree::Token(Token::Eq), TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))), @@ -227,7 +227,7 @@ pub mod parsing { com: block_comment >> (Attribute { style: AttrStyle::Inner, - name: "doc".into(), + path: "doc".into(), tts: vec![ TokenTree::Token(Token::Eq), TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))), @@ -240,17 +240,17 @@ pub mod parsing { named!(pub outer_attr -> Attribute, alt!( do_parse!( punct!("#") >> - name_and_tts: delimited!( + path_and_tts: delimited!( punct!("["), - tuple!(ident, token_trees), + tuple!(path, token_trees), punct!("]") ) >> ({ - let (name, tts) = name_and_tts; + let (path, tts) = path_and_tts; Attribute { style: AttrStyle::Outer, - name: name, + path: path, tts: tts, is_sugared_doc: false, } @@ -263,7 +263,7 @@ pub mod parsing { content: take_until!("\n") >> (Attribute { style: AttrStyle::Outer, - name: "doc".into(), + path: "doc".into(), tts: vec![ TokenTree::Token(Token::Eq), TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))), @@ -278,7 +278,7 @@ pub mod parsing { com: block_comment >> (Attribute { style: AttrStyle::Outer, - name: "doc".into(), + path: "doc".into(), tts: vec![ TokenTree::Token(Token::Eq), TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))), @@ -298,11 +298,21 @@ mod printing { impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut Tokens) { - if let Attribute { style, ref name, ref tts, is_sugared_doc: true } = *self { - if name == "doc" && tts.len() == 2 { - match (&tts[0], &tts[1]) { + match *self { + Attribute { + style, + path: Path { global: false, ref segments }, + ref tts, + is_sugared_doc: true, + } if segments.len() == 1 && + segments[0].ident == "doc" && + segments[0].parameters.is_empty() && + tts.len() == 2 => + { + match (&self.tts[0], &self.tts[1]) { (&TokenTree::Token(Token::Eq), - &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) => { + &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) => + { match style { AttrStyle::Inner if value.starts_with("//!") => { tokens.append(&format!("{}\n", value)); @@ -327,6 +337,8 @@ mod printing { _ => {} } } + + _ => {} } tokens.append("#"); @@ -334,7 +346,7 @@ mod printing { tokens.append("!"); } tokens.append("["); - self.name.to_tokens(tokens); + self.path.to_tokens(tokens); tokens.append_all(&self.tts); tokens.append("]"); } diff --git a/tests/test_generics.rs b/tests/test_generics.rs index 18ec457d03..800daa7395 100644 --- a/tests/test_generics.rs +++ b/tests/test_generics.rs @@ -21,7 +21,7 @@ fn test_split_for_impl() { ty_params: vec![TyParam { attrs: vec![Attribute { style: AttrStyle::Outer, - name: "may_dangle".into(), + path: "may_dangle".into(), tts: vec![], is_sugared_doc: false, }], diff --git a/tests/test_macro_input.rs b/tests/test_macro_input.rs index 2f96b8161a..4f06a37ee1 100644 --- a/tests/test_macro_input.rs +++ b/tests/test_macro_input.rs @@ -31,7 +31,7 @@ fn test_struct() { vis: Visibility::Public, attrs: vec![Attribute { style: AttrStyle::Outer, - name: "derive".into(), + path: "derive".into(), tts: vec![ TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![ TokenTree::Token(Token::Ident(Ident::from("Debug"))), @@ -106,7 +106,7 @@ fn test_enum() { attrs: vec![ Attribute { style: AttrStyle::Outer, - name: "doc".into(), + path: "doc".into(), tts: vec![ TokenTree::Token(Token::Eq), TokenTree::Token(Token::Literal(Lit::Str( @@ -118,7 +118,7 @@ fn test_enum() { }, Attribute { style: AttrStyle::Outer, - name: "must_use".into(), + path: "must_use".into(), tts: vec![], is_sugared_doc: false, }, From 44d2bf3da24e525b4f0a978752adc89dd62906b2 Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 02:47:55 -0700 Subject: [PATCH 07/12] fixup! Comments --- src/attr.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/attr.rs b/src/attr.rs index 14d0ccf9eb..c98a96903d 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -6,12 +6,24 @@ use std::iter; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Attribute { pub style: AttrStyle, + + /// The path of the attribute. + /// + /// E.g. `derive` in `#[derive(Copy)]` + /// E.g. `crate::precondition` in `#[crate::precondition x < 5]` pub path: Path, + + /// Any tokens after the path. + /// + /// E.g. `( Copy )` in `#[derive(Copy)]` + /// E.g. `x < 5` in `#[crate::precondition x < 5]` pub tts: Vec, + pub is_sugared_doc: bool, } impl Attribute { + /// Parses the tokens after the path as a [`MetaItem`](enum.MetaItem.html) if possible. pub fn meta_item(&self) -> Option { if self.tts.is_empty() { return Some(MetaItem::Word); @@ -298,6 +310,7 @@ mod printing { impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut Tokens) { + // If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]` match *self { Attribute { style, From 0b302a3a65ba988aedef6e9e09e3b23a6cd77b2e Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 02:57:28 -0700 Subject: [PATCH 08/12] fixup! Test for attributes with paths. --- tests/test_macro_input.rs | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_macro_input.rs b/tests/test_macro_input.rs index 4f06a37ee1..eca7b33252 100644 --- a/tests/test_macro_input.rs +++ b/tests/test_macro_input.rs @@ -215,3 +215,46 @@ fn test_enum() { assert_eq!(expected_meta_items, actual_meta_items); } + +#[test] +fn test_attr_with_path() { + let raw =r#" + #[::attr_args::identity + fn main() { assert_eq!(foo(), "Hello, world!"); }] + struct Dummy; + "#; + + let expected = MacroInput { + ident: "Dummy".into(), + vis: Visibility::Inherited, + attrs: vec![Attribute { + style: AttrStyle::Outer, + path: Path { global: true, segments: vec!["attr_args".into(), "identity".into()] }, + tts: vec![ + TokenTree::Token(Token::Ident(Ident::from("fn"))), + TokenTree::Token(Token::Ident(Ident::from("main"))), + TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![] }), + TokenTree::Delimited(Delimited { delim: DelimToken::Brace, tts: vec![ + TokenTree::Token(Token::Ident(Ident::from("assert_eq"))), + TokenTree::Token(Token::Not), + TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![ + TokenTree::Token(Token::Ident(Ident::from("foo"))), + TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![] }), + TokenTree::Token(Token::Comma), + TokenTree::Token(Token::Literal(Lit::Str("Hello, world!".into(), StrStyle::Cooked))), + ]}), + TokenTree::Token(Token::Semi), + ]}) + ], + is_sugared_doc: false, + }], + generics: Generics::default(), + body: Body::Struct(VariantData::Unit), + }; + + let actual = parse_macro_input(raw).unwrap(); + + assert_eq!(expected, actual); + + assert!(actual.attrs[0].meta_item().is_none()); +} From a115eba04fa0dfedae149cc0f17c9d83fed679ca Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 03:05:52 -0700 Subject: [PATCH 09/12] fixup! Fix old rust again. --- src/attr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/attr.rs b/src/attr.rs index c98a96903d..0edace9594 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -307,6 +307,7 @@ mod printing { use lit::{Lit, StrStyle}; use mac::{Token, TokenTree}; use quote::{Tokens, ToTokens}; + use ty::Path; impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut Tokens) { From 95f8a7a76b53b5d6f6e415eb5611b5cf8334da2e Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 03:29:56 -0700 Subject: [PATCH 10/12] fixup! Reinstated name in MetaItem since it's no longer trivial to get it from its parent. --- src/attr.rs | 55 +++++++++++++++++++++++++++------------ tests/test_macro_input.rs | 22 ++++++++-------- tests/test_meta_item.rs | 32 +++++++++++++++-------- 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 0edace9594..82bc02a747 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -25,8 +25,14 @@ pub struct Attribute { impl Attribute { /// Parses the tokens after the path as a [`MetaItem`](enum.MetaItem.html) if possible. pub fn meta_item(&self) -> Option { + let name = if self.path.segments.len() == 1 { + &self.path.segments[0].ident + } else { + return None; + }; + if self.tts.is_empty() { - return Some(MetaItem::Word); + return Some(MetaItem::Word(name.clone())); } if self.tts.len() == 1 { @@ -43,7 +49,7 @@ impl Attribute { if tts.len() >= 3 { match (&tts[1], &tts[2]) { (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { - return Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::NameValue(lit.clone())), &tts[3..])); + return Some((NestedMetaItem::MetaItem(MetaItem::NameValue(ident.clone(), lit.clone())), &tts[3..])); } _ => {} @@ -54,7 +60,7 @@ impl Attribute { if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] { return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) { Some(nested_meta_items) => { - Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::List(nested_meta_items)), &tts[2..])) + Some((NestedMetaItem::MetaItem(MetaItem::List(ident.clone(), nested_meta_items)), &tts[2..])) } None => None @@ -62,7 +68,7 @@ impl Attribute { } } - Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::Word), &tts[1..])) + Some((NestedMetaItem::MetaItem(MetaItem::Word(ident.clone())), &tts[1..])) } _ => None @@ -93,7 +99,7 @@ impl Attribute { } if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) { - return Some(MetaItem::List(nested_meta_items)); + return Some(MetaItem::List(name.clone(), nested_meta_items)); } } } @@ -101,7 +107,7 @@ impl Attribute { if self.tts.len() == 2 { match (&self.tts[0], &self.tts[1]) { (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { - return Some(MetaItem::NameValue(lit.clone())); + return Some(MetaItem::NameValue(name.clone(), lit.clone())); } _ => {} @@ -132,17 +138,31 @@ pub enum MetaItem { /// Word meta item. /// /// E.g. `test` as in `#[test]` - Word, + Word(Ident), /// List meta item. /// /// E.g. `derive(..)` as in `#[derive(..)]` - List(Vec), + List(Ident, Vec), /// Name-value meta item. /// /// E.g. `feature = "foo"` as in `#[feature = "foo"]` - NameValue(Lit), + NameValue(Ident, Lit), +} + +impl MetaItem { + /// Name of the item. + /// + /// E.g. `test` as in `#[test]`, `derive` as in `#[derive(..)]`, and + /// `feature` as in `#[feature = "foo"]`. + pub fn name(&self) -> &str { + match *self { + MetaItem::Word(ref name) | + MetaItem::List(ref name, _) | + MetaItem::NameValue(ref name, _) => name.as_ref(), + } + } } /// Possible values inside of compile-time attribute lists. @@ -152,8 +172,8 @@ pub enum MetaItem { pub enum NestedMetaItem { /// A full `MetaItem`. /// - /// E.g. `Copy` in `#[derive(Copy)]` would be a `(Ident::from("Copy"), MetaItem::Word)`. - MetaItem(Ident, MetaItem), + /// E.g. `Copy` in `#[derive(Copy)]` would be a `MetaItem::Word(Ident::from("Copy"))`. + MetaItem(MetaItem), /// A Rust literal. /// @@ -369,13 +389,17 @@ mod printing { impl ToTokens for MetaItem { fn to_tokens(&self, tokens: &mut Tokens) { match *self { - MetaItem::Word => {} - MetaItem::List(ref inner) => { + MetaItem::Word(ref ident) => { + ident.to_tokens(tokens); + } + MetaItem::List(ref ident, ref inner) => { + ident.to_tokens(tokens); tokens.append("("); tokens.append_separated(inner, ","); tokens.append(")"); } - MetaItem::NameValue(ref value) => { + MetaItem::NameValue(ref name, ref value) => { + name.to_tokens(tokens); tokens.append("="); value.to_tokens(tokens); } @@ -386,8 +410,7 @@ mod printing { impl ToTokens for NestedMetaItem { fn to_tokens(&self, tokens: &mut Tokens) { match *self { - NestedMetaItem::MetaItem(ref ident, ref nested) => { - ident.to_tokens(tokens); + NestedMetaItem::MetaItem(ref nested) => { nested.to_tokens(tokens); } NestedMetaItem::Literal(ref lit) => { diff --git a/tests/test_macro_input.rs b/tests/test_macro_input.rs index eca7b33252..353562a6e5 100644 --- a/tests/test_macro_input.rs +++ b/tests/test_macro_input.rs @@ -34,9 +34,9 @@ fn test_struct() { path: "derive".into(), tts: vec![ TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![ - TokenTree::Token(Token::Ident(Ident::from("Debug"))), + TokenTree::Token(Token::Ident("Debug".into())), TokenTree::Token(Token::Comma), - TokenTree::Token(Token::Ident(Ident::from("Clone"))), + TokenTree::Token(Token::Ident("Clone".into())), ]}) ], is_sugared_doc: false, @@ -75,9 +75,9 @@ fn test_struct() { assert_eq!(expected, actual); - let expected_meta_item = MetaItem::List(vec![ - NestedMetaItem::MetaItem(Ident::from("Debug"), MetaItem::Word), - NestedMetaItem::MetaItem(Ident::from("Clone"), MetaItem::Word), + let expected_meta_item = MetaItem::List("derive".into(), vec![ + NestedMetaItem::MetaItem(MetaItem::Word("Debug".into())), + NestedMetaItem::MetaItem(MetaItem::Word("Clone".into())), ]); assert_eq!(expected_meta_item, actual.attrs[0].meta_item().unwrap()); @@ -204,11 +204,11 @@ fn test_enum() { assert_eq!(expected, actual); let expected_meta_items = vec![ - MetaItem::NameValue(Lit::Str( + MetaItem::NameValue("doc".into(), Lit::Str( "/// See the std::result module documentation for details.".into(), StrStyle::Cooked, )), - MetaItem::Word, + MetaItem::Word("must_use".into()), ]; let actual_meta_items: Vec<_> = actual.attrs.into_iter().map(|attr| attr.meta_item().unwrap()).collect(); @@ -231,14 +231,14 @@ fn test_attr_with_path() { style: AttrStyle::Outer, path: Path { global: true, segments: vec!["attr_args".into(), "identity".into()] }, tts: vec![ - TokenTree::Token(Token::Ident(Ident::from("fn"))), - TokenTree::Token(Token::Ident(Ident::from("main"))), + TokenTree::Token(Token::Ident("fn".into())), + TokenTree::Token(Token::Ident("main".into())), TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![] }), TokenTree::Delimited(Delimited { delim: DelimToken::Brace, tts: vec![ - TokenTree::Token(Token::Ident(Ident::from("assert_eq"))), + TokenTree::Token(Token::Ident("assert_eq".into())), TokenTree::Token(Token::Not), TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![ - TokenTree::Token(Token::Ident(Ident::from("foo"))), + TokenTree::Token(Token::Ident("foo".into())), TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: vec![] }), TokenTree::Token(Token::Comma), TokenTree::Token(Token::Literal(Lit::Str("Hello, world!".into(), StrStyle::Cooked))), diff --git a/tests/test_meta_item.rs b/tests/test_meta_item.rs index ceb178cd52..95efaf3505 100644 --- a/tests/test_meta_item.rs +++ b/tests/test_meta_item.rs @@ -3,38 +3,48 @@ use syn::*; #[test] fn test_meta_item_word() { - run_test("#[foo]", MetaItem::Word) + run_test("#[foo]", MetaItem::Word("foo".into())) } #[test] fn test_meta_item_name_value() { - run_test("#[foo = 5]", MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed))) + run_test("#[foo = 5]", MetaItem::NameValue("foo".into(), + Lit::Int(5, IntTy::Unsuffixed))) } #[test] fn test_meta_item_list_lit() { - run_test("#[foo(5)]", MetaItem::List(vec![NestedMetaItem::Literal(Lit::Int(5, IntTy::Unsuffixed))])) + run_test("#[foo(5)]", MetaItem::List("foo".into(), vec![ + NestedMetaItem::Literal(Lit::Int(5, IntTy::Unsuffixed)), + ])) } #[test] fn test_meta_item_list_word() { - run_test("#[foo(bar)]", MetaItem::List(vec![NestedMetaItem::MetaItem(Ident::from("bar"), MetaItem::Word)])) + run_test("#[foo(bar)]", MetaItem::List("foo".into(), vec![ + NestedMetaItem::MetaItem(MetaItem::Word("bar".into())), + ])) } #[test] fn test_meta_item_list_name_value() { - run_test("#[foo(bar = 5)]", MetaItem::List(vec![NestedMetaItem::MetaItem(Ident::from("bar"), MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed)))])) + run_test("#[foo(bar = 5)]", MetaItem::List("foo".into(), vec![ + NestedMetaItem::MetaItem(MetaItem::NameValue("bar".into(), + Lit::Int(5, IntTy::Unsuffixed))), + ])) } #[test] fn test_meta_item_multiple() { - run_test("#[foo(word, name = 5, list(name2 = 6), word2)]", MetaItem::List(vec![ - NestedMetaItem::MetaItem(Ident::from("word"), MetaItem::Word), - NestedMetaItem::MetaItem(Ident::from("name"), MetaItem::NameValue(Lit::Int(5, IntTy::Unsuffixed))), - NestedMetaItem::MetaItem(Ident::from("list"), MetaItem::List(vec![ - NestedMetaItem::MetaItem(Ident::from("name2"), MetaItem::NameValue(Lit::Int(6, IntTy::Unsuffixed))) + run_test("#[foo(word, name = 5, list(name2 = 6), word2)]", MetaItem::List("foo".into(), vec![ + NestedMetaItem::MetaItem(MetaItem::Word("word".into())), + NestedMetaItem::MetaItem(MetaItem::NameValue("name".into(), + Lit::Int(5, IntTy::Unsuffixed))), + NestedMetaItem::MetaItem(MetaItem::List("list".into(), vec![ + NestedMetaItem::MetaItem(MetaItem::NameValue("name2".into(), + Lit::Int(6, IntTy::Unsuffixed))) ])), - NestedMetaItem::MetaItem(Ident::from("word2"), MetaItem::Word), + NestedMetaItem::MetaItem(MetaItem::Word("word2".into())), ])) } From 247706101c9e98acdb0b1c9cd3a225e4bb3f42c6 Mon Sep 17 00:00:00 2001 From: Arnavion Date: Wed, 19 Apr 2017 11:41:12 -0700 Subject: [PATCH 11/12] fixup! Satisfy clippy some more. --- src/attr.rs | 20 ++++++-------------- tests/test_meta_item.rs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 82bc02a747..d10c3ed6b5 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -47,12 +47,10 @@ impl Attribute { TokenTree::Token(Token::Ident(ref ident)) => { if tts.len() >= 3 { - match (&tts[1], &tts[2]) { - (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { + if let TokenTree::Token(Token::Eq) = tts[1] { + if let TokenTree::Token(Token::Literal(ref lit)) = tts[2] { return Some((NestedMetaItem::MetaItem(MetaItem::NameValue(ident.clone(), lit.clone())), &tts[3..])); } - - _ => {} } } @@ -105,12 +103,10 @@ impl Attribute { } if self.tts.len() == 2 { - match (&self.tts[0], &self.tts[1]) { - (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => { + if let TokenTree::Token(Token::Eq) = self.tts[0] { + if let TokenTree::Token(Token::Literal(ref lit)) = self.tts[1] { return Some(MetaItem::NameValue(name.clone(), lit.clone())); } - - _ => {} } } @@ -343,10 +339,8 @@ mod printing { segments[0].parameters.is_empty() && tts.len() == 2 => { - match (&self.tts[0], &self.tts[1]) { - (&TokenTree::Token(Token::Eq), - &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) => - { + if let TokenTree::Token(Token::Eq) = self.tts[0] { + if let TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked))) = self.tts[1] { match style { AttrStyle::Inner if value.starts_with("//!") => { tokens.append(&format!("{}\n", value)); @@ -367,8 +361,6 @@ mod printing { _ => {} } } - - _ => {} } } diff --git a/tests/test_meta_item.rs b/tests/test_meta_item.rs index 95efaf3505..58a8eff532 100644 --- a/tests/test_meta_item.rs +++ b/tests/test_meta_item.rs @@ -3,32 +3,32 @@ use syn::*; #[test] fn test_meta_item_word() { - run_test("#[foo]", MetaItem::Word("foo".into())) + run_test("#[foo]", &MetaItem::Word("foo".into())) } #[test] fn test_meta_item_name_value() { - run_test("#[foo = 5]", MetaItem::NameValue("foo".into(), + run_test("#[foo = 5]", &MetaItem::NameValue("foo".into(), Lit::Int(5, IntTy::Unsuffixed))) } #[test] fn test_meta_item_list_lit() { - run_test("#[foo(5)]", MetaItem::List("foo".into(), vec![ + run_test("#[foo(5)]", &MetaItem::List("foo".into(), vec![ NestedMetaItem::Literal(Lit::Int(5, IntTy::Unsuffixed)), ])) } #[test] fn test_meta_item_list_word() { - run_test("#[foo(bar)]", MetaItem::List("foo".into(), vec![ + run_test("#[foo(bar)]", &MetaItem::List("foo".into(), vec![ NestedMetaItem::MetaItem(MetaItem::Word("bar".into())), ])) } #[test] fn test_meta_item_list_name_value() { - run_test("#[foo(bar = 5)]", MetaItem::List("foo".into(), vec![ + run_test("#[foo(bar = 5)]", &MetaItem::List("foo".into(), vec![ NestedMetaItem::MetaItem(MetaItem::NameValue("bar".into(), Lit::Int(5, IntTy::Unsuffixed))), ])) @@ -36,7 +36,7 @@ fn test_meta_item_list_name_value() { #[test] fn test_meta_item_multiple() { - run_test("#[foo(word, name = 5, list(name2 = 6), word2)]", MetaItem::List("foo".into(), vec![ + run_test("#[foo(word, name = 5, list(name2 = 6), word2)]", &MetaItem::List("foo".into(), vec![ NestedMetaItem::MetaItem(MetaItem::Word("word".into())), NestedMetaItem::MetaItem(MetaItem::NameValue("name".into(), Lit::Int(5, IntTy::Unsuffixed))), @@ -48,7 +48,7 @@ fn test_meta_item_multiple() { ])) } -fn run_test(input: &str, expected: MetaItem) { +fn run_test(input: &str, expected: &MetaItem) { let attr = parse_outer_attr(input).unwrap(); - assert_eq!(expected, attr.meta_item().unwrap()); + assert_eq!(expected, &attr.meta_item().unwrap()); } From f2dada15f6156e6b46ba99344081c2982d30b89b Mon Sep 17 00:00:00 2001 From: Arnavion Date: Thu, 20 Apr 2017 23:55:20 -0700 Subject: [PATCH 12/12] fixup! Attribute paths are mod-style. --- src/attr.rs | 6 ++-- src/ty.rs | 21 ++++++++++++++ tests/test_macro_input.rs | 58 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index d10c3ed6b5..0a2b01616d 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -211,7 +211,7 @@ pub mod parsing { use mac::{Token, TokenTree}; use mac::parsing::token_trees; use synom::space::{block_comment, whitespace}; - use ty::parsing::path; + use ty::parsing::mod_style_path; #[cfg(feature = "full")] named!(pub inner_attr -> Attribute, alt!( @@ -220,7 +220,7 @@ pub mod parsing { punct!("!") >> path_and_tts: delimited!( punct!("["), - tuple!(path, token_trees), + tuple!(mod_style_path, token_trees), punct!("]") ) >> ({ @@ -270,7 +270,7 @@ pub mod parsing { punct!("#") >> path_and_tts: delimited!( punct!("["), - tuple!(path, token_trees), + tuple!(mod_style_path, token_trees), punct!("]") ) >> ({ diff --git a/src/ty.rs b/src/ty.rs index 4055b6a60c..7b7d3afbb1 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -519,6 +519,27 @@ pub mod parsing { ), Into::into) )); + named!(pub mod_style_path -> Path, do_parse!( + global: option!(punct!("::")) >> + segments: separated_nonempty_list!(punct!("::"), mod_style_path_segment) >> + (Path { + global: global.is_some(), + segments: segments, + }) + )); + + named!(mod_style_path_segment -> PathSegment, alt!( + map!(ident, Into::into) + | + map!(alt!( + keyword!("super") + | + keyword!("self") + | + keyword!("Self") + ), Into::into) + )); + named!(type_binding -> TypeBinding, do_parse!( id: ident >> punct!("=") >> diff --git a/tests/test_macro_input.rs b/tests/test_macro_input.rs index 353562a6e5..6f40a00803 100644 --- a/tests/test_macro_input.rs +++ b/tests/test_macro_input.rs @@ -258,3 +258,61 @@ fn test_attr_with_path() { assert!(actual.attrs[0].meta_item().is_none()); } + +#[test] +fn test_attr_with_non_mod_style_path() { + let raw =r#" + #[inert ] + struct S; + "#; + + let expected = MacroInput { + ident: "S".into(), + vis: Visibility::Inherited, + attrs: vec![Attribute { + style: AttrStyle::Outer, + path: Path { global: false, segments: vec!["inert".into()] }, + tts: vec![ + TokenTree::Token(Token::Lt), + TokenTree::Token(Token::Ident("T".into())), + TokenTree::Token(Token::Gt), + ], + is_sugared_doc: false, + }], + generics: Generics::default(), + body: Body::Struct(VariantData::Unit), + }; + + let actual = parse_macro_input(raw).unwrap(); + + assert_eq!(expected, actual); + + assert!(actual.attrs[0].meta_item().is_none()); +} + +#[test] +fn test_attr_with_mod_style_path_with_self() { + let raw =r#" + #[foo::self] + struct S; + "#; + + let expected = MacroInput { + ident: "S".into(), + vis: Visibility::Inherited, + attrs: vec![Attribute { + style: AttrStyle::Outer, + path: Path { global: false, segments: vec!["foo".into(), "self".into()] }, + tts: vec![], + is_sugared_doc: false, + }], + generics: Generics::default(), + body: Body::Struct(VariantData::Unit), + }; + + let actual = parse_macro_input(raw).unwrap(); + + assert_eq!(expected, actual); + + assert!(actual.attrs[0].meta_item().is_none()); +}