pastey/lib.rs
1#![doc = include_str!("../README.md")]
2#![allow(
3 clippy::derive_partial_eq_without_eq,
4 clippy::doc_markdown,
5 clippy::match_same_arms,
6 clippy::module_name_repetitions,
7 clippy::needless_doctest_main,
8 clippy::too_many_lines
9)]
10
11extern crate proc_macro;
12
13mod attr;
14mod error;
15mod segment;
16
17use crate::attr::expand_attr;
18use crate::error::{Error, Result};
19use crate::segment::Segment;
20use proc_macro::{
21 Delimiter, Group, Ident, LexError, Literal, Punct, Spacing, Span, TokenStream, TokenTree,
22};
23use std::char;
24use std::iter;
25use std::panic;
26use std::str::FromStr;
27
28#[proc_macro]
29pub fn paste(input: TokenStream) -> TokenStream {
30 let mut contains_paste = false;
31 let flatten_single_interpolation = true;
32 match expand(
33 input.clone(),
34 &mut contains_paste,
35 flatten_single_interpolation,
36 ) {
37 Ok(expanded) => {
38 if contains_paste {
39 expanded
40 } else {
41 input
42 }
43 }
44 Err(err) => err.to_compile_error(),
45 }
46}
47
48#[doc(hidden)]
49#[proc_macro]
50pub fn item(input: TokenStream) -> TokenStream {
51 paste(input)
52}
53
54#[doc(hidden)]
55#[proc_macro]
56pub fn expr(input: TokenStream) -> TokenStream {
57 paste(input)
58}
59
60fn expand(
61 input: TokenStream,
62 contains_paste: &mut bool,
63 flatten_single_interpolation: bool,
64) -> Result<TokenStream> {
65 let mut expanded = TokenStream::new();
66 let mut lookbehind = Lookbehind::Other;
67 let mut prev_none_group = None::<Group>;
68 let mut tokens = input.into_iter().peekable();
69 loop {
70 let token = tokens.next();
71 if let Some(group) = prev_none_group.take() {
72 if match (&token, tokens.peek()) {
73 (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => {
74 fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint
75 }
76 _ => false,
77 } {
78 expanded.extend(group.stream());
79 *contains_paste = true;
80 } else {
81 expanded.extend(iter::once(TokenTree::Group(group)));
82 }
83 }
84 match token {
85 Some(TokenTree::Group(group)) => {
86 let delimiter = group.delimiter();
87 let content = group.stream();
88 let span = group.span();
89 if delimiter == Delimiter::Bracket && is_paste_operation(&content) {
90 let segments = parse_bracket_as_segments(content, span)?;
91 let pasted = segment::paste(&segments)?;
92 let tokens = pasted_to_tokens(pasted, span)?;
93 expanded.extend(tokens);
94 *contains_paste = true;
95 } else if flatten_single_interpolation
96 && delimiter == Delimiter::None
97 && is_single_interpolation_group(&content)
98 {
99 expanded.extend(content);
100 *contains_paste = true;
101 } else {
102 let mut group_contains_paste = false;
103 let is_attribute = delimiter == Delimiter::Bracket
104 && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang);
105 let mut nested = expand(
106 content,
107 &mut group_contains_paste,
108 flatten_single_interpolation && !is_attribute,
109 )?;
110 if is_attribute {
111 nested = expand_attr(nested, span, &mut group_contains_paste)?;
112 }
113 let group = if group_contains_paste {
114 let mut group = Group::new(delimiter, nested);
115 group.set_span(span);
116 *contains_paste = true;
117 group
118 } else {
119 group.clone()
120 };
121 if delimiter != Delimiter::None {
122 expanded.extend(iter::once(TokenTree::Group(group)));
123 } else if lookbehind == Lookbehind::DoubleColon {
124 expanded.extend(group.stream());
125 *contains_paste = true;
126 } else {
127 prev_none_group = Some(group);
128 }
129 }
130 lookbehind = Lookbehind::Other;
131 }
132 Some(TokenTree::Punct(punct)) => {
133 lookbehind = match punct.as_char() {
134 ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon,
135 ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon,
136 '#' => Lookbehind::Pound,
137 '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang,
138 _ => Lookbehind::Other,
139 };
140 expanded.extend(iter::once(TokenTree::Punct(punct)));
141 }
142 Some(other) => {
143 lookbehind = Lookbehind::Other;
144 expanded.extend(iter::once(other));
145 }
146 None => return Ok(expanded),
147 }
148 }
149}
150
151#[derive(PartialEq)]
152enum Lookbehind {
153 JointColon,
154 DoubleColon,
155 Pound,
156 PoundBang,
157 Other,
158}
159
160// https://github.com/dtolnay/paste/issues/26
161fn is_single_interpolation_group(input: &TokenStream) -> bool {
162 #[derive(PartialEq)]
163 enum State {
164 Init,
165 Ident,
166 Literal,
167 Apostrophe,
168 Lifetime,
169 Colon1,
170 Colon2,
171 }
172
173 let mut state = State::Init;
174 for tt in input.clone() {
175 state = match (state, &tt) {
176 (State::Init, TokenTree::Ident(_)) => State::Ident,
177 (State::Init, TokenTree::Literal(_)) => State::Literal,
178 (State::Init, TokenTree::Punct(punct)) if punct.as_char() == '\'' => State::Apostrophe,
179 (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime,
180 (State::Ident, TokenTree::Punct(punct))
181 if punct.as_char() == ':' && punct.spacing() == Spacing::Joint =>
182 {
183 State::Colon1
184 }
185 (State::Colon1, TokenTree::Punct(punct))
186 if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
187 {
188 State::Colon2
189 }
190 (State::Colon2, TokenTree::Ident(_)) => State::Ident,
191 _ => return false,
192 };
193 }
194
195 state == State::Ident || state == State::Literal || state == State::Lifetime
196}
197
198fn is_paste_operation(input: &TokenStream) -> bool {
199 let mut tokens = input.clone().into_iter();
200
201 match &tokens.next() {
202 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
203 _ => return false,
204 }
205
206 let mut has_token = false;
207 loop {
208 match &tokens.next() {
209 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
210 return has_token && tokens.next().is_none();
211 }
212 Some(_) => has_token = true,
213 None => return false,
214 }
215 }
216}
217
218fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> {
219 let mut tokens = input.into_iter().peekable();
220
221 match &tokens.next() {
222 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
223 Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")),
224 None => return Err(Error::new(scope, "expected `[< ... >]`")),
225 }
226
227 let mut segments = segment::parse(&mut tokens)?;
228
229 match &tokens.next() {
230 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {}
231 Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")),
232 None => return Err(Error::new(scope, "expected `[< ... >]`")),
233 }
234
235 if let Some(unexpected) = tokens.next() {
236 return Err(Error::new(
237 unexpected.span(),
238 "unexpected input, expected `[< ... >]`",
239 ));
240 }
241
242 for segment in &mut segments {
243 if let Segment::String(string) = segment {
244 if string.value.starts_with("'\\u{") {
245 let hex = &string.value[4..string.value.len() - 2];
246 if let Ok(unsigned) = u32::from_str_radix(hex, 16) {
247 if let Some(ch) = char::from_u32(unsigned) {
248 string.value.clear();
249 string.value.push(ch);
250 continue;
251 }
252 }
253 }
254 if string.value.contains(&['\\', '.', '+'][..])
255 || string.value.starts_with("b'")
256 || string.value.starts_with("b\"")
257 || string.value.starts_with("br\"")
258 {
259 return Err(Error::new(string.span, "unsupported literal"));
260 }
261 let mut range = 0..string.value.len();
262 if string.value.starts_with("r\"") {
263 range.start += 2;
264 range.end -= 1;
265 } else if string.value.starts_with(&['"', '\''][..]) {
266 range.start += 1;
267 range.end -= 1;
268 }
269 string.value = string.value[range].replace('-', "_");
270 }
271 }
272
273 Ok(segments)
274}
275
276fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> {
277 let mut raw_mode = false;
278 let mut tokens = TokenStream::new();
279
280 if pasted.starts_with(|ch: char| ch.is_ascii_digit()) {
281 let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) {
282 Ok(Ok(literal)) => TokenTree::Literal(literal),
283 Ok(Err(LexError { .. })) | Err(_) => {
284 return Err(Error::new(
285 span,
286 &format!("`{:?}` is not a valid literal", pasted),
287 ));
288 }
289 };
290 tokens.extend(iter::once(literal));
291 return Ok(tokens);
292 }
293
294 if pasted.starts_with('\'') {
295 let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
296 apostrophe.set_span(span);
297 tokens.extend(iter::once(apostrophe));
298 pasted.remove(0);
299 }
300
301 if pasted.starts_with("r#") {
302 raw_mode = true;
303 }
304
305 let ident = match panic::catch_unwind(|| {
306 if raw_mode {
307 let mut spasted = pasted.clone();
308 spasted.remove(0);
309 spasted.remove(0);
310 Ident::new_raw(&spasted, span)
311 } else {
312 Ident::new(&pasted, span)
313 }
314 }) {
315 Ok(ident) => TokenTree::Ident(ident),
316 Err(_) => {
317 return Err(Error::new(
318 span,
319 &format!("`{:?}` is not a valid identifier", pasted),
320 ));
321 }
322 };
323
324 tokens.extend(iter::once(ident));
325 Ok(tokens)
326}
327
328#[cfg(doctest)]
329#[doc(hidden)]
330mod doc_tests {
331 /// ```
332 /// use pastey::paste;
333 /// let arr: [u8; 3] = paste!([1u8, 2, 3]);
334 /// ```
335 fn test_non_paste_bracket_returns_input() {}
336
337 /// ```
338 /// use pastey::paste;
339 /// macro_rules! m {
340 /// ($id:ident) => { paste! { fn $id() {} } }
341 /// }
342 /// m!(doc_flatten_fn);
343 /// doc_flatten_fn();
344 /// ```
345 fn test_flatten_single_ident_group() {}
346
347 /// ```
348 /// use pastey::paste;
349 /// macro_rules! m {
350 /// ($life:lifetime) => {
351 /// paste! { struct DocRef<$life>(pub &$life ()); }
352 /// }
353 /// }
354 /// m!('a);
355 /// ```
356 fn test_flatten_lifetime_group() {}
357
358 /// ```
359 /// use pastey::paste;
360 /// macro_rules! m {
361 /// ($t:path) => { paste! { type DocPathAlias = $t; } }
362 /// }
363 /// m!(std::string::String);
364 /// let _: DocPathAlias = String::new();
365 /// ```
366 fn test_flatten_path_group() {}
367
368 /// ```
369 /// use pastey::paste;
370 /// macro_rules! m {
371 /// ($lit:literal) => { paste! { const DOC_LIT: u8 = $lit; } }
372 /// }
373 /// m!(99u8);
374 /// ```
375 fn test_flatten_literal_group() {}
376
377 /// ```
378 /// use pastey::paste;
379 /// paste! { let _: std::string::String = String::new(); }
380 /// ```
381 fn test_double_colon_none_group() {}
382
383 /// ```
384 /// use pastey::paste;
385 /// macro_rules! m {
386 /// ($t:ty) => {
387 /// paste! {
388 /// pub const NONE_GROUP_TY_STR: &str = stringify!($t::method);
389 /// }
390 /// }
391 /// }
392 /// m!(Vec<u8>);
393 /// ```
394 fn test_none_group_complex_type_before_double_colon() {}
395
396 /// ```
397 /// use pastey::paste;
398 /// paste! {
399 /// mod doc_inner_mod {
400 /// #![allow(dead_code)]
401 /// pub struct DocInner;
402 /// }
403 /// }
404 /// ```
405 fn test_inner_mod() {}
406
407 /// ```
408 /// use pastey::paste;
409 /// macro_rules! allow_lint {
410 /// ($lint:ident) => {
411 /// paste! {
412 /// #[allow(clippy::$lint)]
413 /// pub struct DocLintStruct;
414 /// }
415 /// }
416 /// }
417 /// allow_lint!(pedantic);
418 /// ```
419 fn test_double_colon_none_group_in_attr() {}
420
421 /// ```
422 /// use pastey::paste;
423 /// paste! { const _: &str = stringify!([<'\u{48}' ello>]); }
424 /// ```
425 fn test_char_unicode_escape_in_paste() {}
426
427 /// ```
428 /// use pastey::paste;
429 /// paste! { const _: &str = stringify!([<r"hel" lo>]); }
430 /// ```
431 fn test_raw_string_in_paste() {}
432
433 /// ```
434 /// use pastey::paste;
435 /// paste! { struct DocLifeRef<[<'a>]>(pub &[<'a>] ()); }
436 /// ```
437 fn test_lifetime_paste_tokens() {}
438
439 /// ```
440 /// use pastey::paste;
441 /// paste! { #[allow(non_camel_case_types)] struct [<# loop>]; }
442 /// ```
443 fn test_raw_mode_paste_tokens() {}
444
445 /// ```
446 /// use pastey::paste;
447 /// paste! { const _: &str = stringify!([<'\u{41}' bcde>]); }
448 /// ```
449 fn test_char_unicode_paste_tokens() {}
450
451 /// ```
452 /// use pastey::paste;
453 /// macro_rules! m {
454 /// ($t:ty) => {
455 /// paste! { let _: $t = std::string::String::new(); }
456 /// }
457 /// }
458 /// m!(String);
459 /// ```
460 fn test_none_group_not_followed_by_double_colon() {}
461
462 /// ```compile_fail
463 /// use pastey::paste;
464 /// paste! { const _: u32 = [<99 invalid>]; }
465 /// ```
466 fn test_error_invalid_literal_with_trailing_tokens() {}
467
468 /// ```compile_fail
469 /// use pastey::paste;
470 /// paste! { const _: &str = [<@invalid>]; }
471 /// ```
472 fn test_error_invalid_identifier_special_char() {}
473
474 /// ```compile_fail
475 /// use pastey::paste;
476 /// paste! { const _: &str = [identifier]; }
477 /// ```
478 fn test_error_expected_bracket_with_angle() {}
479
480 /// ```compile_fail
481 /// use pastey::paste;
482 /// paste! { const _: &str = [<identifier]; }
483 /// ```
484 fn test_error_expected_closing_angle() {}
485
486 /// ```compile_fail
487 /// use pastey::paste;
488 /// paste! { const _: &str = [<identifier> extra]; }
489 /// ```
490 fn test_error_unexpected_token_after_closing_angle() {}
491
492 /// ```compile_fail
493 /// use pastey::paste;
494 /// paste! { const _: &str = [<"byte\\string">]; }
495 /// ```
496 fn test_error_unsupported_escaped_string() {}
497
498 /// ```compile_fail
499 /// use pastey::paste;
500 /// paste! { const _: &str = [<b"byte_string">]; }
501 /// ```
502 fn test_error_unsupported_byte_string() {}
503
504 /// ```compile_fail
505 /// use pastey::paste;
506 /// paste! { const _: &str = [<br"raw_byte">]; }
507 /// ```
508 fn test_error_unsupported_raw_byte_string() {}
509
510 /// ```compile_fail
511 /// use pastey::paste;
512 /// paste! { const _: &str = [<"string.with.dots">]; }
513 /// ```
514 fn test_error_unsupported_string_with_dots() {}
515
516 /// ```compile_fail
517 /// use pastey::paste;
518 /// paste! { const _: &str = [<"string+plus">]; }
519 /// ```
520 fn test_error_unsupported_string_with_plus() {}
521
522 /// ```compile_fail
523 /// use pastey::paste;
524 /// paste! { const _: &str = [<>]; }
525 /// ```
526 fn test_error_expected_content_in_brackets() {}
527
528 /// ```compile_fail
529 /// use pastey::paste;
530 /// paste! { const _: &str = [<a..b>]; }
531 /// ```
532 fn test_error_invalid_numeric_literal() {}
533
534 /// ```compile_fail
535 /// use pastey::paste;
536 /// paste! { const _: &str = [< >]; }
537 /// ```
538 fn test_error_no_tokens_in_paste() {}
539
540 /// ```compile_fail
541 /// use pastey::paste;
542 /// paste! { const _: &str = [<'static>]; }
543 /// ```
544 fn test_error_lifetime_without_identifier() {}
545
546 /// ```compile_fail
547 /// use pastey::paste;
548 /// macro_rules! m {
549 /// ($x:ident) => {
550 /// paste! { const _: u32 = [<$x>]; }
551 /// }
552 /// }
553 /// m!(123);
554 /// ```
555 fn test_error_invalid_ident_starting_with_number() {}
556
557 /// ```compile_fail
558 /// use pastey::paste;
559 /// paste! { const _: &str = [<"terminated>]; }
560 /// ```
561 fn test_error_terminated_string_literal() {}
562
563 /// ```compile_fail
564 /// use pastey::paste;
565 /// paste! { let x = [<y z>]; }
566 /// ```
567 fn test_error_multiple_tokens_non_paste() {}
568
569 /// ```compile_fail
570 /// use pastey::paste;
571 /// paste! { let x: i32 = [<not_a_number>]; }
572 /// ```
573 fn test_error_paste_result_type_mismatch() {}
574
575 /// ```compile_fail
576 /// use pastey::paste;
577 /// paste! { const _: &str = [<'a 'b>]; }
578 /// ```
579 fn test_error_multiple_lifetimes() {}
580
581 /// ```compile_fail
582 /// use pastey::paste;
583 /// paste! { const _: &str = [<42 56>]; }
584 /// ```
585 fn test_error_multiple_numeric_tokens() {}
586
587 /// ```compile_fail
588 /// use pastey::paste;
589 /// macro_rules! concat_idents {
590 /// ($a:ident, $b:ident) => { paste! { const TEST: u32 = [<method_ $a>](); } }
591 /// }
592 /// concat_idents!(test, func);
593 /// ```
594 fn test_error_method_call_on_paste_result() {}
595
596 /// ```compile_fail
597 /// use pastey::paste;
598 /// paste! { const _ : &str = [<r# if>]; }
599 /// ```
600 fn test_error_raw_keyword_identifier() {}
601
602 /// ```compile_fail
603 /// use pastey::paste;
604 /// macro_rules! m {
605 /// () => { paste! { const _: u32 = [<>]; } }
606 /// }
607 /// m!();
608 /// ```
609 fn test_error_empty_macro_result() {}
610
611 /// ```compile_fail
612 /// use pastey::paste;
613 /// paste! { const _: &str = [<-identifier>]; }
614 /// ```
615 fn test_error_hyphen_at_start() {}
616
617 /// ```compile_fail
618 /// use pastey::paste;
619 /// paste! { const _: &str = [<'123>]; }
620 /// ```
621 fn test_error_apostrophe_with_number() {}
622
623 /// ```compile_fail
624 /// use pastey::paste;
625 /// macro_rules! m {
626 /// ($lit:literal) => {
627 /// paste! { const _: &str = stringify!([<$lit>]); }
628 /// }
629 /// }
630 /// m!("both" "strings");
631 /// ```
632 fn test_error_invalid_literal_argument() {}
633
634 /// ```compile_fail
635 /// use pastey::paste;
636 /// paste! { const _: &str = [<'\u{D800}>]; }
637 /// ```
638 fn test_error_invalid_unicode_escape() {}
639
640 /// ```compile_fail
641 /// use pastey::paste;
642 /// paste! { const _: u32 = [<0x>]; }
643 /// ```
644 fn test_error_incomplete_hex_literal() {}
645
646 /// ```compile_fail
647 /// use pastey::paste;
648 /// paste! { const _: u32 = [<0b>]; }
649 /// ```
650 fn test_error_incomplete_binary_literal() {}
651
652 /// ```compile_fail
653 /// use pastey::paste;
654 /// paste! { const _: f64 = [<1..5>]; }
655 /// ```
656 fn test_error_range_literal_invalid() {}
657
658 /// ```compile_fail
659 /// use pastey::paste;
660 /// paste! { const _: &str = [<if>]; }
661 /// ```
662 fn test_error_keyword_as_identifier() {}
663
664 /// ```compile_fail
665 /// use pastey::paste;
666 /// paste! { const _: &str = [<fn>]; }
667 /// ```
668 fn test_error_fn_keyword() {}
669
670 /// ```compile_fail
671 /// use pastey::paste;
672 /// paste! { const _: &str = [<match>]; }
673 /// ```
674 fn test_error_match_keyword() {}
675
676 /// ```compile_fail
677 /// use pastey::paste;
678 /// paste! { const _: &str = [<{inner}>]; }
679 /// ```
680 fn test_error_brace_group_in_paste() {}
681
682 /// ```compile_fail
683 /// use pastey::paste;
684 /// paste! { const _: &str = [<(parts)>]; }
685 /// ```
686 fn test_error_paren_group_in_paste() {}
687
688 /// ```compile_fail
689 /// use pastey::paste;
690 /// macro_rules! test {
691 /// () => { paste! { const _: &str = [<&&invalid>]; } }
692 /// }
693 /// test!();
694 /// ```
695 fn test_error_double_ampersand() {}
696
697 /// ```compile_fail
698 /// use pastey::paste;
699 /// macro_rules! test {
700 /// () => { paste! { const _: &str = [<||invalid>]; } }
701 /// }
702 /// test!();
703 /// ```
704 fn test_error_double_pipe() {}
705
706 /// ```compile_fail
707 /// use pastey::paste;
708 /// paste! { const _: &str = [<@#$>]; }
709 /// ```
710 fn test_error_random_special_chars() {}
711
712 /// ```compile_fail
713 /// use pastey::paste;
714 /// paste! { let value: i32 = [<test>]; }
715 /// ```
716 fn test_error_undefined_identifier_value() {}
717
718 /// ```compile_fail
719 /// use pastey::paste;
720 /// paste! { fn doc_err_in_brace() { let _ = [<@>]; } }
721 /// ```
722 fn test_error_invalid_paste_inside_brace_group() {}
723
724 /// ```compile_fail
725 /// use pastey::paste;
726 /// paste! { const _: u32 = [<0 x>]; }
727 /// ```
728 fn test_error_pasted_incomplete_hex_literal() {}
729
730 /// ```compile_fail
731 /// use pastey::paste;
732 /// paste! { #[cfg(test) trailing_token] fn f() {} }
733 /// ```
734 fn test_error_attr_trailing_tokens_after_paren_group() {}
735
736 /// ```compile_fail
737 /// use pastey::paste;
738 /// paste! { struct [<Foo::+Bar>]; }
739 /// ```
740 fn test_error_invalid_colon_sequence() {}
741
742 /// ```compile_fail
743 /// use pastey::paste;
744 /// paste! { fn [<Bar:::Test>]() {} }
745 /// ```
746 fn test_error_invalid_colon_patterns() {}
747
748 /// ```compile_fail
749 /// use pastey::paste;
750 /// paste! { fn [*invalid>]() {} }
751 /// ```
752 fn test_error_bracket_first_token_not_less_than() {}
753
754 /// ```compile_fail
755 /// use pastey::paste;
756 /// paste! { fn [<test]() {} }
757 /// ```
758 fn test_error_bracket_closing_token_not_greater_than() {}
759
760 /// ```compile_fail
761 /// use pastey::paste;
762 /// paste! { fn [<test>extra]() {} }
763 /// ```
764 fn test_error_extra_tokens_after_bracket() {}
765
766 /// ```compile_fail
767 /// use pastey::paste;
768 /// paste! { fn [<test:replace('\u{XY}', 'A')>]() {} }
769 /// ```
770 fn test_error_invalid_hex_unicode() {}
771
772 /// ```
773 /// use pastey::paste;
774 /// macro_rules! test_replace {
775 /// ($a:expr, $b:expr) => {
776 /// paste! { concat!(stringify!($a), stringify!($b)) }
777 /// }
778 /// }
779 /// let result = test_replace!(x, "y");
780 /// ```
781 fn test_macro_group_flattening() {}
782
783 /// ```
784 /// use pastey::paste;
785 /// paste! { fn [<foo_bar>]() {} }
786 /// foo_bar();
787 /// ```
788 fn test_valid_paste_operation() {}
789
790 /// ```
791 /// use pastey::paste;
792 /// paste! { use std::[<vec>]; }
793 /// let v = Vec::<i32>::new();
794 /// assert_eq!(v.len(), 0);
795 /// ```
796 fn test_valid_path_with_double_colon() {}
797
798 /// ```
799 /// use pastey::paste;
800 /// macro_rules! concat_name {
801 /// ($name:ident) => {
802 /// paste! { const [<$name:lower>]: &str = stringify!($name); }
803 /// }
804 /// }
805 /// concat_name!(TEST);
806 /// ```
807 fn test_valid_modifiers() {}
808
809 /// ```
810 /// use pastey::paste;
811 /// macro_rules! test_ident_colon {
812 /// ($param:ident: $ty:ident) => {
813 /// paste! { fn [<test_$param>]($param: $ty) {} }
814 /// }
815 /// }
816 /// test_ident_colon!(x: i32);
817 /// test_x(42i32);
818 /// ```
819 fn test_type_annotation() {}
820}