Skip to content
This repository has been archived by the owner on Oct 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #59 from dtolnay/path
Browse files Browse the repository at this point in the history
Handle attribute with non-ident path
  • Loading branch information
dtolnay authored Dec 10, 2020
2 parents a3e4ace + aca6368 commit 11ad3b2
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
62 changes: 42 additions & 20 deletions src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,50 @@ pub fn expand_attr(
contains_paste: &mut bool,
) -> Result<TokenStream> {
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);
Expand Down Expand Up @@ -71,18 +93,18 @@ 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 {
Ok(attr)
}
}

fn do_paste_name_value_attr(attr: TokenStream, span: Span) -> Result<TokenStream> {
fn do_paste_name_value_attr(attr: TokenStream, span: Span, leading: usize) -> Result<TokenStream> {
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)?;

Expand Down
9 changes: 9 additions & 0 deletions tests/macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "paste-test-suite"
version = "0.0.0"
edition = "2018"
publish = false

[lib]
path = "lib.rs"
proc-macro = true
25 changes: 25 additions & 0 deletions tests/macros/lib.rs
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 19 additions & 0 deletions tests/test_attr.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down

0 comments on commit 11ad3b2

Please sign in to comment.