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