diff --git a/Cargo.toml b/Cargo.toml index e0b2223..63cb4c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" proc-macro = true [dev-dependencies] +paste-test-suite = { version = "0", path = "tests/macros" } rustversion = "1.0" trybuild = "1.0" diff --git a/src/attr.rs b/src/attr.rs index 2d23730..be64a79 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -11,28 +11,50 @@ pub fn expand_attr( contains_paste: &mut bool, ) -> Result { let mut tokens = attr.clone().into_iter(); - match tokens.next() { - Some(TokenTree::Ident(..)) => {} - _ => return Ok(attr), - } + let mut leading_colons = 0; // $(::)? + let mut leading_path = 0; // $($ident)::+ - let group = match tokens.next() { - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => { - let mut count = 0; - if tokens.inspect(|_| count += 1).all(|tt| is_stringlike(&tt)) && count > 1 { - *contains_paste = true; - return do_paste_name_value_attr(attr, span); + let mut token; + let group = loop { + token = tokens.next(); + match token { + // colon after `$(:)?` + Some(TokenTree::Punct(ref punct)) + if punct.as_char() == ':' && leading_colons < 2 && leading_path == 0 => + { + leading_colons += 1; + } + // ident after `$(::)? $($ident ::)*` + Some(TokenTree::Ident(_)) if leading_colons != 1 && leading_path % 3 == 0 => { + leading_path += 1; + } + // colon after `$(::)? $($ident ::)* $ident $(:)?` + Some(TokenTree::Punct(ref punct)) if punct.as_char() == ':' && leading_path % 3 > 0 => { + leading_path += 1; + } + // eq+value after `$(::)? $($ident)::+` + Some(TokenTree::Punct(ref punct)) + if punct.as_char() == '=' && leading_path % 3 == 1 => + { + let mut count = 0; + if tokens.inspect(|_| count += 1).all(|tt| is_stringlike(&tt)) && count > 1 { + *contains_paste = true; + let leading = leading_colons + leading_path; + return do_paste_name_value_attr(attr, span, leading); + } + return Ok(attr); } - return Ok(attr); + // parens after `$(::)? $($ident)::+` + Some(TokenTree::Group(ref group)) + if group.delimiter() == Delimiter::Parenthesis && leading_path % 3 == 1 => + { + break group; + } + // bail out + _ => return Ok(attr), } - Some(TokenTree::Group(group)) => group, - _ => return Ok(attr), }; - if group.delimiter() != Delimiter::Parenthesis { - return Ok(attr); - } - // There can't be anything else after the first group in a valid attribute. if tokens.next().is_some() { return Ok(attr); @@ -71,7 +93,7 @@ pub fn expand_attr( Ok(attr .into_iter() // Just keep the initial ident in `#[ident(...)]`. - .take(1) + .take(leading_colons + leading_path) .chain(iter::once(TokenTree::Group(group))) .collect()) } else { @@ -79,10 +101,10 @@ pub fn expand_attr( } } -fn do_paste_name_value_attr(attr: TokenStream, span: Span) -> Result { +fn do_paste_name_value_attr(attr: TokenStream, span: Span, leading: usize) -> Result { let mut expanded = TokenStream::new(); let mut tokens = attr.into_iter().peekable(); - expanded.extend(tokens.by_ref().take(2)); // `doc =` + expanded.extend(tokens.by_ref().take(leading + 1)); // `doc =` let mut segments = segment::parse(&mut tokens)?; diff --git a/tests/macros/Cargo.toml b/tests/macros/Cargo.toml new file mode 100644 index 0000000..ba2ce2d --- /dev/null +++ b/tests/macros/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "paste-test-suite" +version = "0.0.0" +edition = "2018" +publish = false + +[lib] +path = "lib.rs" +proc-macro = true diff --git a/tests/macros/lib.rs b/tests/macros/lib.rs new file mode 100644 index 0000000..b49a5b4 --- /dev/null +++ b/tests/macros/lib.rs @@ -0,0 +1,25 @@ +extern crate proc_macro; + +use proc_macro::{TokenStream, TokenTree}; + +#[proc_macro_attribute] +pub fn paste_test(args: TokenStream, input: TokenStream) -> TokenStream { + let mut iter = args.clone().into_iter(); + match iter.next() { + Some(TokenTree::Ident(_)) => {} + _ => panic!("{}", args), + } + match iter.next() { + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => {} + _ => panic!("{}", args), + } + match iter.next() { + Some(TokenTree::Literal(ref literal)) if literal.to_string().starts_with('"') => {} + _ => panic!("{}", args), + } + match iter.next() { + None => {} + _ => panic!("{}", args), + } + input +} diff --git a/tests/test_attr.rs b/tests/test_attr.rs index fd67e2a..01abbfe 100644 --- a/tests/test_attr.rs +++ b/tests/test_attr.rs @@ -1,4 +1,23 @@ use paste::paste; +use paste_test_suite::paste_test; + +#[test] +fn test_attr() { + paste! { + #[paste_test(k = "val" "ue")] + struct A; + + #[paste_test_suite::paste_test(k = "val" "ue")] + struct B; + + #[::paste_test_suite::paste_test(k = "val" "ue")] + struct C; + } + + let _ = A; + let _ = B; + let _ = C; +} #[test] fn test_paste_cfg() {