• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::error::Result;
2 use crate::segment::{self, Segment};
3 use proc_macro::{Delimiter, Group, Span, TokenStream, TokenTree};
4 use std::iter;
5 use std::mem;
6 use std::str::FromStr;
7 
expand_attr( attr: TokenStream, span: Span, contains_paste: &mut bool, ) -> Result<TokenStream>8 pub 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 
do_paste_name_value_attr(attr: TokenStream, span: Span, leading: usize) -> Result<TokenStream>104 fn 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 
is_stringlike(token: &TokenTree) -> bool143 fn is_stringlike(token: &TokenTree) -> bool {
144     match token {
145         TokenTree::Ident(_) => true,
146         TokenTree::Literal(literal) => {
147             let repr = literal.to_string();
148             !repr.starts_with('b') && !repr.starts_with('\'')
149         }
150         TokenTree::Group(group) => {
151             if group.delimiter() != Delimiter::None {
152                 return false;
153             }
154             let mut inner = group.stream().into_iter();
155             match inner.next() {
156                 Some(first) => inner.next().is_none() && is_stringlike(&first),
157                 None => false,
158             }
159         }
160         TokenTree::Punct(punct) => punct.as_char() == '\'' || punct.as_char() == ':',
161     }
162 }
163