]> git.lizzy.rs Git - rust.git/blob - crates/ra_hir_expand/src/quote.rs
49155fe6262c234d8bd222a4e13ac75d2dc41b80
[rust.git] / crates / ra_hir_expand / src / quote.rs
1 //! A simplified version of quote-crate like quasi quote macro
2
3 // A helper macro quote macro
4 // FIXME:
5 // 1. Not all puncts are handled
6 // 2. #()* pattern repetition not supported now
7 //    * But we can do it manually, see `test_quote_derive_copy_hack`
8 #[doc(hidden)]
9 #[macro_export]
10 macro_rules! __quote {
11     () => {
12         Vec::<tt::TokenTree>::new()
13     };
14
15     ( @SUBTREE $delim:ident $($tt:tt)* ) => {
16         {
17             let children = $crate::__quote!($($tt)*);
18             let subtree = tt::Subtree {
19                 delimiter: Some(tt::Delimiter {
20                     kind: tt::DelimiterKind::$delim,
21                     id: tt::TokenId::unspecified(),
22                 }),
23                 token_trees: $crate::quote::IntoTt::to_tokens(children),
24             };
25             subtree
26         }
27     };
28
29     ( @PUNCT $first:literal ) => {
30         {
31             vec![
32                 tt::Leaf::Punct(tt::Punct {
33                     char: $first,
34                     spacing: tt::Spacing::Alone,
35                     id: tt::TokenId::unspecified(),
36                 }).into()
37             ]
38         }
39     };
40
41     ( @PUNCT $first:literal, $sec:literal ) => {
42         {
43             vec![
44                 tt::Leaf::Punct(tt::Punct {
45                     char: $first,
46                     spacing: tt::Spacing::Joint,
47                     id: tt::TokenId::unspecified(),
48                 }).into(),
49                 tt::Leaf::Punct(tt::Punct {
50                     char: $sec,
51                     spacing: tt::Spacing::Alone,
52                     id: tt::TokenId::unspecified(),
53                 }).into()
54             ]
55         }
56     };
57
58     // hash variable
59     ( # $first:ident $($tail:tt)* ) => {
60         {
61             let token = $crate::quote::ToTokenTree::to_token($first);
62             let mut tokens = vec![token.into()];
63             let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
64             tokens.append(&mut tail_tokens);
65             tokens
66         }
67     };
68
69     ( ## $first:ident $($tail:tt)* ) => {
70         {
71             let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>();
72             let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
73             tokens.append(&mut tail_tokens);
74             tokens
75         }
76     };
77
78     // Brace
79     ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
80     // Bracket
81     ( [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE Bracket $($tt)*) };
82     // Parenthesis
83     ( ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE Parenthesis $($tt)*) };
84
85     // Literal
86     ( $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt).into()] };
87     // Ident
88     ( $tt:ident ) => {
89         vec![ {
90             tt::Leaf::Ident(tt::Ident {
91                 text: stringify!($tt).into(),
92                 id: tt::TokenId::unspecified(),
93             }).into()
94         }]
95     };
96
97     // Puncts
98     // FIXME: Not all puncts are handled
99     ( -> ) => {$crate::__quote!(@PUNCT '-', '>')};
100     ( & ) => {$crate::__quote!(@PUNCT '&')};
101     ( , ) => {$crate::__quote!(@PUNCT ',')};
102     ( : ) => {$crate::__quote!(@PUNCT ':')};
103     ( :: ) => {$crate::__quote!(@PUNCT ':', ':')};
104     ( . ) => {$crate::__quote!(@PUNCT '.')};
105
106     ( $first:tt $($tail:tt)+ ) => {
107         {
108             let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($first));
109             let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
110
111             tokens.append(&mut tail_tokens);
112             tokens
113         }
114     };
115 }
116
117 /// FIXME:
118 /// It probably should implement in proc-macro
119 #[macro_export]
120 macro_rules! quote {
121     ( $($tt:tt)* ) => {
122         $crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*))
123     }
124 }
125
126 pub(crate) trait IntoTt {
127     fn to_subtree(self) -> tt::Subtree;
128     fn to_tokens(self) -> Vec<tt::TokenTree>;
129 }
130
131 impl IntoTt for Vec<tt::TokenTree> {
132     fn to_subtree(self) -> tt::Subtree {
133         tt::Subtree { delimiter: None, token_trees: self }
134     }
135
136     fn to_tokens(self) -> Vec<tt::TokenTree> {
137         self
138     }
139 }
140
141 impl IntoTt for tt::Subtree {
142     fn to_subtree(self) -> tt::Subtree {
143         self
144     }
145
146     fn to_tokens(self) -> Vec<tt::TokenTree> {
147         vec![tt::TokenTree::Subtree(self)]
148     }
149 }
150
151 pub(crate) trait ToTokenTree {
152     fn to_token(self) -> tt::TokenTree;
153 }
154
155 impl ToTokenTree for tt::TokenTree {
156     fn to_token(self) -> tt::TokenTree {
157         self
158     }
159 }
160
161 impl ToTokenTree for tt::Subtree {
162     fn to_token(self) -> tt::TokenTree {
163         self.into()
164     }
165 }
166
167 macro_rules! impl_to_to_tokentrees {
168     ($($ty:ty => $this:ident $im:block);*) => {
169         $(
170             impl ToTokenTree for $ty {
171                 fn to_token($this) -> tt::TokenTree {
172                     let leaf: tt::Leaf = $im.into();
173                     leaf.into()
174                 }
175             }
176
177             impl ToTokenTree for &$ty {
178                 fn to_token($this) -> tt::TokenTree {
179                     let leaf: tt::Leaf = $im.clone().into();
180                     leaf.into()
181                 }
182             }
183         )*
184     }
185 }
186
187 impl_to_to_tokentrees! {
188     u32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()} };
189     usize => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}};
190     i32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}};
191     tt::Leaf => self { self };
192     tt::Literal => self { self };
193     tt::Ident => self { self };
194     tt::Punct => self { self };
195     &str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}};
196     String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}}
197 }
198
199 #[cfg(test)]
200 mod tests {
201     #[test]
202     fn test_quote_delimiters() {
203         assert_eq!(quote!({}).to_string(), "{}");
204         assert_eq!(quote!(()).to_string(), "()");
205         assert_eq!(quote!([]).to_string(), "[]");
206     }
207
208     #[test]
209     fn test_quote_idents() {
210         assert_eq!(quote!(32).to_string(), "32");
211         assert_eq!(quote!(struct).to_string(), "struct");
212     }
213
214     #[test]
215     fn test_quote_hash_simple_literal() {
216         let a = 20;
217         assert_eq!(quote!(#a).to_string(), "20");
218         let s: String = "hello".into();
219         assert_eq!(quote!(#s).to_string(), "\"hello\"");
220     }
221
222     fn mk_ident(name: &str) -> tt::Ident {
223         tt::Ident { text: name.into(), id: tt::TokenId::unspecified() }
224     }
225
226     #[test]
227     fn test_quote_hash_token_tree() {
228         let a = mk_ident("hello");
229
230         let quoted = quote!(#a);
231         assert_eq!(quoted.to_string(), "hello");
232         let t = format!("{:?}", quoted);
233         assert_eq!(t, "Subtree { delimiter: None, token_trees: [Leaf(Ident(Ident { text: \"hello\", id: TokenId(4294967295) }))] }");
234     }
235
236     #[test]
237     fn test_quote_simple_derive_copy() {
238         let name = mk_ident("Foo");
239
240         let quoted = quote! {
241             impl Clone for #name {
242                 fn clone(&self) -> Self {
243                     Self {}
244                 }
245             }
246         };
247
248         assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}");
249     }
250
251     #[test]
252     fn test_quote_derive_copy_hack() {
253         // Assume the given struct is:
254         // struct Foo {
255         //  name: String,
256         //  id: u32,
257         // }
258         let struct_name = mk_ident("Foo");
259         let fields = [mk_ident("name"), mk_ident("id")];
260         let fields =
261             fields.iter().map(|it| quote!(#it: self.#it.clone(), ).token_trees.clone()).flatten();
262
263         let list = tt::Subtree {
264             delimiter: Some(tt::Delimiter {
265                 kind: tt::DelimiterKind::Brace,
266                 id: tt::TokenId::unspecified(),
267             }),
268             token_trees: fields.collect(),
269         };
270
271         let quoted = quote! {
272             impl Clone for #struct_name {
273                 fn clone(&self) -> Self {
274                     Self #list
275                 }
276             }
277         };
278
279         assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}");
280     }
281 }