Skip to main content

hir_expand/builtin/
quote.rs

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