1use crate::error::Result;
2use crate::segment::{self, Segment};
3use proc_macro::{Delimiter, Group, Spacing, Span, TokenStream, TokenTree};
4use std::iter;
5use std::mem;
6use std::str::FromStr;
7
8pub fn expand_attr(
9 attr: TokenStream,
10 span: Span,
11 contains_paste: &mut bool,
12) -> Result<TokenStream> {
13 let mut tokens = attr.clone().into_iter();
14 let mut leading_colons = 0; // $(::)?
15 let mut leading_path = 0; // $($ident)::+
16
17 let mut token;
18 let group = loop {
19 token = tokens.next();
20 match token {
21 // colon after `$(:)?`
22 Some(TokenTree::Punct(ref punct))
23 if punct.as_char() == ':' && leading_colons < 2 && leading_path == 0 =>
24 {
25 leading_colons += 1;
26 }
27 // ident after `$(::)? $($ident ::)*`
28 Some(TokenTree::Ident(_)) if leading_colons != 1 && leading_path % 3 == 0 => {
29 leading_path += 1;
30 }
31 // colon after `$(::)? $($ident ::)* $ident $(:)?`
32 Some(TokenTree::Punct(ref punct)) if punct.as_char() == ':' && leading_path % 3 > 0 => {
33 leading_path += 1;
34 }
35 // eq+value after `$(::)? $($ident)::+`
36 Some(TokenTree::Punct(ref punct))
37 if punct.as_char() == '=' && leading_path % 3 == 1 =>
38 {
39 let mut count = 0;
40 if tokens.inspect(|_| count += 1).all(|tt| is_stringlike(&tt)) && count > 1 {
41 *contains_paste = true;
42 let leading = leading_colons + leading_path;
43 return do_paste_name_value_attr(attr, span, leading);
44 }
45 return Ok(attr);
46 }
47 // parens after `$(::)? $($ident)::+`
48 Some(TokenTree::Group(ref group))
49 if group.delimiter() == Delimiter::Parenthesis && leading_path % 3 == 1 =>
50 {
51 break group;
52 }
53 // bail out
54 _ => return Ok(attr),
55 }
56 };
57
58 // There can't be anything else after the first group in a valid attribute.
59 if tokens.next().is_some() {
60 return Ok(attr);
61 }
62
63 let mut group_contains_paste = false;
64 let mut expanded = TokenStream::new();
65 let mut nested_attr = TokenStream::new();
66 for tt in group.stream() {
67 match &tt {
68 TokenTree::Punct(punct) if punct.as_char() == ',' => {
69 expanded.extend(expand_attr(
70 nested_attr,
71 group.span(),
72 &mut group_contains_paste,
73 )?);
74 expanded.extend(iter::once(tt));
75 nested_attr = TokenStream::new();
76 }
77 _ => nested_attr.extend(iter::once(tt)),
78 }
79 }
80
81 if !nested_attr.is_empty() {
82 expanded.extend(expand_attr(
83 nested_attr,
84 group.span(),
85 &mut group_contains_paste,
86 )?);
87 }
88
89 if group_contains_paste {
90 *contains_paste = true;
91 let mut group = Group::new(Delimiter::Parenthesis, expanded);
92 group.set_span(span);
93 Ok(attr
94 .into_iter()
95 // Just keep the initial ident in `#[ident(...)]`.
96 .take(leading_colons + leading_path)
97 .chain(iter::once(TokenTree::Group(group)))
98 .collect())
99 } else {
100 Ok(attr)
101 }
102}
103
104fn do_paste_name_value_attr(attr: TokenStream, span: Span, leading: usize) -> Result<TokenStream> {
105 let mut expanded = TokenStream::new();
106 let mut tokens = attr.into_iter().peekable();
107 expanded.extend(tokens.by_ref().take(leading + 1)); // `doc =`
108
109 let mut segments = segment::parse(&mut tokens)?;
110
111 for segment in &mut segments {
112 if let Segment::String(string) = segment {
113 if let Some(open_quote) = string.value.find('"') {
114 if open_quote == 0 {
115 string.value.truncate(string.value.len() - 1);
116 string.value.remove(0);
117 } else {
118 let begin = open_quote + 1;
119 let end = string.value.rfind('"').unwrap();
120 let raw_string = mem::replace(&mut string.value, String::new());
121 for ch in raw_string[begin..end].chars() {
122 string.value.extend(ch.escape_default());
123 }
124 }
125 }
126 }
127 }
128
129 let mut lit = segment::paste(&segments)?;
130 lit.insert(0, '"');
131 lit.push('"');
132
133 let mut lit = TokenStream::from_str(&lit)
134 .unwrap()
135 .into_iter()
136 .next()
137 .unwrap();
138 lit.set_span(span);
139 expanded.extend(iter::once(lit));
140 Ok(expanded)
141}
142
143fn is_stringlike(token: &TokenTree) -> bool {
144 match token {
145 TokenTree::Ident(_) => true,
146 TokenTree::Literal(literal: &Literal) => {
147 let repr: String = literal.to_string();
148 !repr.starts_with('b') && !repr.starts_with('\'')
149 }
150 TokenTree::Group(group: &Group) => {
151 if group.delimiter() != Delimiter::None {
152 return false;
153 }
154 let mut inner: IntoIter = group.stream().into_iter();
155 match inner.next() {
156 Some(first: TokenTree) => inner.next().is_none() && is_stringlike(&first),
157 None => false,
158 }
159 }
160 TokenTree::Punct(punct: &Punct) => {
161 punct.as_char() == '\'' || punct.as_char() == ':' && punct.spacing() == Spacing::Alone
162 }
163 }
164}
165