| 1 | use crate::error::Result; |
| 2 | use crate::segment::{self, Segment}; |
| 3 | use proc_macro::{Delimiter, Group, Spacing, Span, TokenStream, TokenTree}; |
| 4 | use std::iter; |
| 5 | use std::mem; |
| 6 | use std::str::FromStr; |
| 7 | |
| 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 | |
| 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 | |
| 143 | fn 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 | |