proc_macro/
quote.rs

1//! # Quasiquoter
2//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
3
4//! This quasiquoter uses macros 2.0 hygiene to reliably access
5//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
6
7use crate::{
8    Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
9};
10
11macro_rules! minimal_quote_tt {
12    (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) };
13    ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) };
14    ({$($t:tt)*}) => { Group::new(Delimiter::Brace, minimal_quote!($($t)*)) };
15    (,) => { Punct::new(',', Spacing::Alone) };
16    (.) => { Punct::new('.', Spacing::Alone) };
17    (;) => { Punct::new(';', Spacing::Alone) };
18    (!) => { Punct::new('!', Spacing::Alone) };
19    (<) => { Punct::new('<', Spacing::Alone) };
20    (>) => { Punct::new('>', Spacing::Alone) };
21    (&) => { Punct::new('&', Spacing::Alone) };
22    (=) => { Punct::new('=', Spacing::Alone) };
23    ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
24}
25
26macro_rules! minimal_quote_ts {
27    ((@ $($t:tt)*)) => { $($t)* };
28    (::) => {
29        {
30            let mut c = (
31                TokenTree::from(Punct::new(':', Spacing::Joint)),
32                TokenTree::from(Punct::new(':', Spacing::Alone))
33            );
34            c.0.set_span(Span::def_site());
35            c.1.set_span(Span::def_site());
36            [c.0, c.1].into_iter().collect::<TokenStream>()
37        }
38    };
39    ($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) };
40}
41
42/// Simpler version of the real `quote!` macro, implemented solely
43/// through `macro_rules`, for bootstrapping the real implementation
44/// (see the `quote` function), which does not have access to the
45/// real `quote!` macro due to the `proc_macro` crate not being
46/// able to depend on itself.
47///
48/// Note: supported tokens are a subset of the real `quote!`, but
49/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
50macro_rules! minimal_quote {
51    ($($t:tt)*) => {
52        {
53            #[allow(unused_mut)] // In case the expansion is empty
54            let mut ts = TokenStream::new();
55            $(ToTokens::to_tokens(&minimal_quote_ts!($t), &mut ts);)*
56            ts
57        }
58    };
59}
60
61/// Quote a `TokenStream` into a `TokenStream`.
62/// This is the actual implementation of the `quote!()` proc macro.
63///
64/// It is loaded by the compiler in `register_builtin_macros`.
65#[unstable(feature = "proc_macro_quote", issue = "54722")]
66pub fn quote(stream: TokenStream) -> TokenStream {
67    if stream.is_empty() {
68        return minimal_quote!(crate::TokenStream::new());
69    }
70    let proc_macro_crate = minimal_quote!(crate);
71    let mut after_dollar = false;
72
73    let mut tokens = crate::TokenStream::new();
74    for tree in stream {
75        if after_dollar {
76            after_dollar = false;
77            match tree {
78                TokenTree::Ident(_) => {
79                    minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);)
80                        .to_tokens(&mut tokens);
81                    continue;
82                }
83                TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
84                _ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
85            }
86        } else if let TokenTree::Punct(ref tt) = tree {
87            if tt.as_char() == '$' {
88                after_dollar = true;
89                continue;
90            }
91        }
92
93        match tree {
94            TokenTree::Punct(tt) => {
95                minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(
96                    (@ TokenTree::from(Literal::character(tt.as_char()))),
97                    (@ match tt.spacing() {
98                        Spacing::Alone => minimal_quote!(crate::Spacing::Alone),
99                        Spacing::Joint => minimal_quote!(crate::Spacing::Joint),
100                    }),
101                )), &mut ts);)
102            }
103            TokenTree::Group(tt) => {
104                minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Group(crate::Group::new(
105                    (@ match tt.delimiter() {
106                        Delimiter::Parenthesis => minimal_quote!(crate::Delimiter::Parenthesis),
107                        Delimiter::Brace => minimal_quote!(crate::Delimiter::Brace),
108                        Delimiter::Bracket => minimal_quote!(crate::Delimiter::Bracket),
109                        Delimiter::None => minimal_quote!(crate::Delimiter::None),
110                    }),
111                    (@ quote(tt.stream())),
112                )), &mut ts);)
113            }
114            TokenTree::Ident(tt) => {
115                let literal = tt.to_string();
116                let (literal, ctor) = if let Some(stripped) = literal.strip_prefix("r#") {
117                    (stripped, minimal_quote!(crate::Ident::new_raw))
118                } else {
119                    (literal.as_str(), minimal_quote!(crate::Ident::new))
120                };
121                minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident((@ ctor)(
122                    (@ TokenTree::from(Literal::string(literal))),
123                    (@ quote_span(proc_macro_crate.clone(), tt.span())),
124                )), &mut ts);)
125            }
126            TokenTree::Literal(tt) => {
127                minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Literal({
128                    let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
129                        .parse::<crate::TokenStream>()
130                        .unwrap()
131                        .into_iter();
132                    if let (Some(crate::TokenTree::Literal(mut lit)), None) =
133                        (iter.next(), iter.next())
134                    {
135                        lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
136                        lit
137                    } else {
138                        unreachable!()
139                    }
140                }), &mut ts);)
141            }
142        }
143        .to_tokens(&mut tokens);
144    }
145    if after_dollar {
146        panic!("unexpected trailing `$` in `quote!`");
147    }
148
149    minimal_quote! {
150        {
151            let mut ts = crate::TokenStream::new();
152            (@ tokens)
153            ts
154        }
155    }
156}
157
158/// Quote a `Span` into a `TokenStream`.
159/// This is needed to implement a custom quoter.
160#[unstable(feature = "proc_macro_quote", issue = "54722")]
161pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream {
162    let id = span.save_span();
163    minimal_quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id)))))
164}