Skip to main content

hir_expand/builtin/
quote.rs

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