1#![doc(html_root_url = "https://docs.rs/paste/1.0.15")]
146#![allow(
147 clippy::derive_partial_eq_without_eq,
148 clippy::doc_markdown,
149 clippy::match_same_arms,
150 clippy::module_name_repetitions,
151 clippy::needless_doctest_main,
152 clippy::too_many_lines
153)]
154
155extern crate proc_macro;
156
157mod attr;
158mod error;
159mod segment;
160
161use crate::attr::expand_attr;
162use crate::error::{Error, Result};
163use crate::segment::Segment;
164use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
165use std::char;
166use std::iter;
167use std::panic;
168
169#[proc_macro]
170pub fn paste(input: TokenStream) -> TokenStream {
171 let mut contains_paste = false;
172 let flatten_single_interpolation = true;
173 match expand(
174 input.clone(),
175 &mut contains_paste,
176 flatten_single_interpolation,
177 ) {
178 Ok(expanded) => {
179 if contains_paste {
180 expanded
181 } else {
182 input
183 }
184 }
185 Err(err) => err.to_compile_error(),
186 }
187}
188
189#[doc(hidden)]
190#[proc_macro]
191pub fn item(input: TokenStream) -> TokenStream {
192 paste(input)
193}
194
195#[doc(hidden)]
196#[proc_macro]
197pub fn expr(input: TokenStream) -> TokenStream {
198 paste(input)
199}
200
201fn expand(
202 input: TokenStream,
203 contains_paste: &mut bool,
204 flatten_single_interpolation: bool,
205) -> Result<TokenStream> {
206 let mut expanded = TokenStream::new();
207 let mut lookbehind = Lookbehind::Other;
208 let mut prev_none_group = None::<Group>;
209 let mut tokens = input.into_iter().peekable();
210 loop {
211 let token = tokens.next();
212 if let Some(group) = prev_none_group.take() {
213 if match (&token, tokens.peek()) {
214 (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => {
215 fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint
216 }
217 _ => false,
218 } {
219 expanded.extend(group.stream());
220 *contains_paste = true;
221 } else {
222 expanded.extend(iter::once(TokenTree::Group(group)));
223 }
224 }
225 match token {
226 Some(TokenTree::Group(group)) => {
227 let delimiter = group.delimiter();
228 let content = group.stream();
229 let span = group.span();
230 if delimiter == Delimiter::Bracket && is_paste_operation(&content) {
231 let segments = parse_bracket_as_segments(content, span)?;
232 let pasted = segment::paste(&segments)?;
233 let tokens = pasted_to_tokens(pasted, span)?;
234 expanded.extend(tokens);
235 *contains_paste = true;
236 } else if flatten_single_interpolation
237 && delimiter == Delimiter::None
238 && is_single_interpolation_group(&content)
239 {
240 expanded.extend(content);
241 *contains_paste = true;
242 } else {
243 let mut group_contains_paste = false;
244 let is_attribute = delimiter == Delimiter::Bracket
245 && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang);
246 let mut nested = expand(
247 content,
248 &mut group_contains_paste,
249 flatten_single_interpolation && !is_attribute,
250 )?;
251 if is_attribute {
252 nested = expand_attr(nested, span, &mut group_contains_paste)?;
253 }
254 let group = if group_contains_paste {
255 let mut group = Group::new(delimiter, nested);
256 group.set_span(span);
257 *contains_paste = true;
258 group
259 } else {
260 group.clone()
261 };
262 if delimiter != Delimiter::None {
263 expanded.extend(iter::once(TokenTree::Group(group)));
264 } else if lookbehind == Lookbehind::DoubleColon {
265 expanded.extend(group.stream());
266 *contains_paste = true;
267 } else {
268 prev_none_group = Some(group);
269 }
270 }
271 lookbehind = Lookbehind::Other;
272 }
273 Some(TokenTree::Punct(punct)) => {
274 lookbehind = match punct.as_char() {
275 ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon,
276 ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon,
277 '#' => Lookbehind::Pound,
278 '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang,
279 _ => Lookbehind::Other,
280 };
281 expanded.extend(iter::once(TokenTree::Punct(punct)));
282 }
283 Some(other) => {
284 lookbehind = Lookbehind::Other;
285 expanded.extend(iter::once(other));
286 }
287 None => return Ok(expanded),
288 }
289 }
290}
291
292#[derive(PartialEq)]
293enum Lookbehind {
294 JointColon,
295 DoubleColon,
296 Pound,
297 PoundBang,
298 Other,
299}
300
301fn is_single_interpolation_group(input: &TokenStream) -> bool {
303 #[derive(PartialEq)]
304 enum State {
305 Init,
306 Ident,
307 Literal,
308 Apostrophe,
309 Lifetime,
310 Colon1,
311 Colon2,
312 }
313
314 let mut state = State::Init;
315 for tt in input.clone() {
316 state = match (state, &tt) {
317 (State::Init, TokenTree::Ident(_)) => State::Ident,
318 (State::Init, TokenTree::Literal(_)) => State::Literal,
319 (State::Init, TokenTree::Punct(punct)) if punct.as_char() == '\'' => State::Apostrophe,
320 (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime,
321 (State::Ident, TokenTree::Punct(punct))
322 if punct.as_char() == ':' && punct.spacing() == Spacing::Joint =>
323 {
324 State::Colon1
325 }
326 (State::Colon1, TokenTree::Punct(punct))
327 if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
328 {
329 State::Colon2
330 }
331 (State::Colon2, TokenTree::Ident(_)) => State::Ident,
332 _ => return false,
333 };
334 }
335
336 state == State::Ident || state == State::Literal || state == State::Lifetime
337}
338
339fn is_paste_operation(input: &TokenStream) -> bool {
340 let mut tokens = input.clone().into_iter();
341
342 match &tokens.next() {
343 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
344 _ => return false,
345 }
346
347 let mut has_token = false;
348 loop {
349 match &tokens.next() {
350 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
351 return has_token && tokens.next().is_none();
352 }
353 Some(_) => has_token = true,
354 None => return false,
355 }
356 }
357}
358
359fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> {
360 let mut tokens = input.into_iter().peekable();
361
362 match &tokens.next() {
363 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
364 Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")),
365 None => return Err(Error::new(scope, "expected `[< ... >]`")),
366 }
367
368 let mut segments = segment::parse(&mut tokens)?;
369
370 match &tokens.next() {
371 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {}
372 Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")),
373 None => return Err(Error::new(scope, "expected `[< ... >]`")),
374 }
375
376 if let Some(unexpected) = tokens.next() {
377 return Err(Error::new(
378 unexpected.span(),
379 "unexpected input, expected `[< ... >]`",
380 ));
381 }
382
383 for segment in &mut segments {
384 if let Segment::String(string) = segment {
385 if string.value.starts_with("'\\u{") {
386 let hex = &string.value[4..string.value.len() - 2];
387 if let Ok(unsigned) = u32::from_str_radix(hex, 16) {
388 if let Some(ch) = char::from_u32(unsigned) {
389 string.value.clear();
390 string.value.push(ch);
391 continue;
392 }
393 }
394 }
395 if string.value.contains(&['#', '\\', '.', '+'][..])
396 || string.value.starts_with("b'")
397 || string.value.starts_with("b\"")
398 || string.value.starts_with("br\"")
399 {
400 return Err(Error::new(string.span, "unsupported literal"));
401 }
402 let mut range = 0..string.value.len();
403 if string.value.starts_with("r\"") {
404 range.start += 2;
405 range.end -= 1;
406 } else if string.value.starts_with(&['"', '\''][..]) {
407 range.start += 1;
408 range.end -= 1;
409 }
410 string.value = string.value[range].replace('-', "_");
411 }
412 }
413
414 Ok(segments)
415}
416
417fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> {
418 let mut tokens = TokenStream::new();
419
420 #[cfg(not(no_literal_fromstr))]
421 {
422 use proc_macro::{LexError, Literal};
423 use std::str::FromStr;
424
425 if pasted.starts_with(|ch: char| ch.is_ascii_digit()) {
426 let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) {
427 Ok(Ok(literal)) => TokenTree::Literal(literal),
428 Ok(Err(LexError { .. })) | Err(_) => {
429 return Err(Error::new(
430 span,
431 &format!("`{:?}` is not a valid literal", pasted),
432 ));
433 }
434 };
435 tokens.extend(iter::once(literal));
436 return Ok(tokens);
437 }
438 }
439
440 if pasted.starts_with('\'') {
441 let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
442 apostrophe.set_span(span);
443 tokens.extend(iter::once(apostrophe));
444 pasted.remove(0);
445 }
446
447 let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) {
448 Ok(ident) => TokenTree::Ident(ident),
449 Err(_) => {
450 return Err(Error::new(
451 span,
452 &format!("`{:?}` is not a valid identifier", pasted),
453 ));
454 }
455 };
456
457 tokens.extend(iter::once(ident));
458 Ok(tokens)
459}