]> git.lizzy.rs Git - rust.git/blob - src/libproc_macro/quote.rs
Rollup merge of #53545 - FelixMcFelix:fix-50865-beta, r=petrochenkov
[rust.git] / src / libproc_macro / quote.rs
1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! # Quasiquoter
12 //! This file contains the implementation internals of the quasiquoter provided by `quote!`.
13
14 //! This quasiquoter uses macros 2.0 hygiene to reliably access
15 //! items from `proc_macro`, to build a `proc_macro::TokenStream`.
16
17 use {Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
18
19 macro_rules! quote_tt {
20     (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) };
21     ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) };
22     ({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) };
23     (,) => { Punct::new(',', Spacing::Alone) };
24     (.) => { Punct::new('.', Spacing::Alone) };
25     (:) => { Punct::new(':', Spacing::Alone) };
26     (;) => { Punct::new(';', Spacing::Alone) };
27     (!) => { Punct::new('!', Spacing::Alone) };
28     (<) => { Punct::new('<', Spacing::Alone) };
29     (>) => { Punct::new('>', Spacing::Alone) };
30     (&) => { Punct::new('&', Spacing::Alone) };
31     (=) => { Punct::new('=', Spacing::Alone) };
32     ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
33 }
34
35 macro_rules! quote_ts {
36     ((@ $($t:tt)*)) => { $($t)* };
37     (::) => {
38         [
39             TokenTree::from(Punct::new(':', Spacing::Joint)),
40             TokenTree::from(Punct::new(':', Spacing::Alone)),
41         ].iter()
42             .cloned()
43             .map(|mut x| {
44                 x.set_span(Span::def_site());
45                 x
46             })
47             .collect::<TokenStream>()
48     };
49     ($t:tt) => { TokenTree::from(quote_tt!($t)) };
50 }
51
52 /// Simpler version of the real `quote!` macro, implemented solely
53 /// through `macro_rules`, for bootstrapping the real implementation
54 /// (see the `quote` function), which does not have access to the
55 /// real `quote!` macro due to the `proc_macro` crate not being
56 /// able to depend on itself.
57 ///
58 /// Note: supported tokens are a subset of the real `quote!`, but
59 /// unquoting is different: instead of `$x`, this uses `(@ expr)`.
60 macro_rules! quote {
61     () => { TokenStream::new() };
62     ($($t:tt)*) => {
63         [
64             $(TokenStream::from(quote_ts!($t)),)*
65         ].iter().cloned().collect::<TokenStream>()
66     };
67 }
68
69 /// Quote a `TokenStream` into a `TokenStream`.
70 /// This is the actual `quote!()` proc macro.
71 ///
72 /// It is manually loaded in `CStore::load_macro_untracked`.
73 #[unstable(feature = "proc_macro_quote", issue = "38356")]
74 pub fn quote(stream: TokenStream) -> TokenStream {
75     if stream.is_empty() {
76         return quote!(::TokenStream::new());
77     }
78     let mut after_dollar = false;
79     let tokens = stream
80         .into_iter()
81         .filter_map(|tree| {
82             if after_dollar {
83                 after_dollar = false;
84                 match tree {
85                     TokenTree::Ident(_) => {
86                         return Some(quote!(Into::<::TokenStream>::into(
87                         Clone::clone(&(@ tree))),));
88                     }
89                     TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
90                     _ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
91                 }
92             } else if let TokenTree::Punct(ref tt) = tree {
93                 if tt.as_char() == '$' {
94                     after_dollar = true;
95                     return None;
96                 }
97             }
98
99             Some(quote!(::TokenStream::from((@ match tree {
100                 TokenTree::Punct(tt) => quote!(::TokenTree::Punct(::Punct::new(
101                     (@ TokenTree::from(Literal::character(tt.as_char()))),
102                     (@ match tt.spacing() {
103                         Spacing::Alone => quote!(::Spacing::Alone),
104                         Spacing::Joint => quote!(::Spacing::Joint),
105                     }),
106                 ))),
107                 TokenTree::Group(tt) => quote!(::TokenTree::Group(::Group::new(
108                     (@ match tt.delimiter() {
109                         Delimiter::Parenthesis => quote!(::Delimiter::Parenthesis),
110                         Delimiter::Brace => quote!(::Delimiter::Brace),
111                         Delimiter::Bracket => quote!(::Delimiter::Bracket),
112                         Delimiter::None => quote!(::Delimiter::None),
113                     }),
114                     (@ quote(tt.stream())),
115                 ))),
116                 TokenTree::Ident(tt) => quote!(::TokenTree::Ident(::Ident::new(
117                     (@ TokenTree::from(Literal::string(&tt.to_string()))),
118                     (@ quote_span(tt.span())),
119                 ))),
120                 TokenTree::Literal(tt) => quote!(::TokenTree::Literal({
121                     let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
122                         .parse::<::TokenStream>()
123                         .unwrap()
124                         .into_iter();
125                     if let (Some(::TokenTree::Literal(mut lit)), None) =
126                         (iter.next(), iter.next())
127                     {
128                         lit.set_span((@ quote_span(tt.span())));
129                         lit
130                     } else {
131                         unreachable!()
132                     }
133                 }))
134             })),))
135         })
136         .collect::<TokenStream>();
137
138     if after_dollar {
139         panic!("unexpected trailing `$` in `quote!`");
140     }
141
142     quote!([(@ tokens)].iter().cloned().collect::<::TokenStream>())
143 }
144
145 /// Quote a `Span` into a `TokenStream`.
146 /// This is needed to implement a custom quoter.
147 #[unstable(feature = "proc_macro_quote", issue = "38356")]
148 pub fn quote_span(_: Span) -> TokenStream {
149     quote!(::Span::def_site())
150 }