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}