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