Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/builtin/quote.rs')
| -rw-r--r-- | crates/hir-expand/src/builtin/quote.rs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/crates/hir-expand/src/builtin/quote.rs b/crates/hir-expand/src/builtin/quote.rs new file mode 100644 index 0000000000..5c33f817f9 --- /dev/null +++ b/crates/hir-expand/src/builtin/quote.rs @@ -0,0 +1,335 @@ +//! A simplified version of quote-crate like quasi quote macro +#![allow(clippy::crate_in_macro_def)] + +use intern::{sym, Symbol}; +use span::Span; +use tt::IdentIsRaw; + +use crate::name::Name; + +pub(crate) fn dollar_crate(span: Span) -> tt::Ident<Span> { + tt::Ident { sym: sym::dollar_crate.clone(), span, is_raw: tt::IdentIsRaw::No } +} + +// A helper macro quote macro +// FIXME: +// 1. Not all puncts are handled +// 2. #()* pattern repetition not supported now +// * But we can do it manually, see `test_quote_derive_copy_hack` +#[doc(hidden)] +macro_rules! quote_impl__ { + ($span:ident) => { + Vec::<$crate::tt::TokenTree>::new() + }; + + ( @SUBTREE($span:ident) $delim:ident $($tt:tt)* ) => { + { + let children = $crate::builtin::quote::__quote!($span $($tt)*); + $crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::$delim, + open: $span, + close: $span, + }, + token_trees: $crate::builtin::quote::IntoTt::to_tokens(children).into_boxed_slice(), + } + } + }; + + ( @PUNCT($span:ident) $first:literal ) => { + { + vec![ + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $first, + spacing: crate::tt::Spacing::Alone, + span: $span, + }).into() + ] + } + }; + + ( @PUNCT($span:ident) $first:literal, $sec:literal ) => { + { + vec![ + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $first, + spacing: crate::tt::Spacing::Joint, + span: $span, + }).into(), + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $sec, + spacing: crate::tt::Spacing::Alone, + span: $span, + }).into() + ] + } + }; + + // hash variable + ($span:ident # $first:ident $($tail:tt)* ) => { + { + let token = $crate::builtin::quote::ToTokenTree::to_token($first, $span); + let mut tokens = vec![token.into()]; + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + + ($span:ident ## $first:ident $($tail:tt)* ) => { + { + let mut tokens = $first.into_iter().map(|it| $crate::builtin::quote::ToTokenTree::to_token(it, $span)).collect::<Vec<crate::tt::TokenTree>>(); + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + + // Brace + ($span:ident { $($tt:tt)* } ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Brace $($tt)*) }; + // Bracket + ($span:ident [ $($tt:tt)* ] ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Bracket $($tt)*) }; + // Parenthesis + ($span:ident ( $($tt:tt)* ) ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Parenthesis $($tt)*) }; + + // Literal + ($span:ident $tt:literal ) => { vec![$crate::builtin::quote::ToTokenTree::to_token($tt, $span).into()] }; + // Ident + ($span:ident $tt:ident ) => { + vec![ { + crate::tt::Leaf::Ident(crate::tt::Ident { + sym: intern::Symbol::intern(stringify!($tt)), + span: $span, + is_raw: tt::IdentIsRaw::No, + }).into() + }] + }; + + // Puncts + // FIXME: Not all puncts are handled + ($span:ident -> ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '-', '>')}; + ($span:ident & ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '&')}; + ($span:ident , ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ',')}; + ($span:ident : ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ':')}; + ($span:ident ; ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ';')}; + ($span:ident :: ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ':', ':')}; + ($span:ident . ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '.')}; + ($span:ident < ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '<')}; + ($span:ident > ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '>')}; + ($span:ident ! ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '!')}; + + ($span:ident $first:tt $($tail:tt)+ ) => { + { + let mut tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $first )); + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + + tokens.append(&mut tail_tokens); + tokens + } + }; +} +pub(super) use quote_impl__ as __quote; + +/// FIXME: +/// It probably should implement in proc-macro +macro_rules! quote_impl { + ($span:ident=> $($tt:tt)* ) => { + $crate::builtin::quote::IntoTt::to_subtree($crate::builtin::quote::__quote!($span $($tt)*), $span) + } +} +pub(super) use quote_impl as quote; + +pub(crate) trait IntoTt { + fn to_subtree(self, span: Span) -> crate::tt::Subtree; + fn to_tokens(self) -> Vec<crate::tt::TokenTree>; +} + +impl IntoTt for Vec<crate::tt::TokenTree> { + fn to_subtree(self, span: Span) -> crate::tt::Subtree { + crate::tt::Subtree { + delimiter: crate::tt::Delimiter::invisible_spanned(span), + token_trees: self.into_boxed_slice(), + } + } + + fn to_tokens(self) -> Vec<crate::tt::TokenTree> { + self + } +} + +impl IntoTt for crate::tt::Subtree { + fn to_subtree(self, _: Span) -> crate::tt::Subtree { + self + } + + fn to_tokens(self) -> Vec<crate::tt::TokenTree> { + vec![crate::tt::TokenTree::Subtree(self)] + } +} + +pub(crate) trait ToTokenTree { + fn to_token(self, span: Span) -> crate::tt::TokenTree; +} + +impl ToTokenTree for crate::tt::TokenTree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { + self + } +} + +impl ToTokenTree for crate::tt::Subtree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { + self.into() + } +} + +macro_rules! impl_to_to_tokentrees { + ($($span:ident: $ty:ty => $this:ident $im:block;)*) => { + $( + impl ToTokenTree for $ty { + fn to_token($this, $span: Span) -> crate::tt::TokenTree { + let leaf: crate::tt::Leaf = $im.into(); + leaf.into() + } + } + )* + } +} + +impl<T: ToTokenTree + Clone> ToTokenTree for &T { + fn to_token(self, span: Span) -> crate::tt::TokenTree { + self.clone().to_token(span) + } +} + +impl_to_to_tokentrees! { + span: u32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: usize => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: i32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: bool => self { crate::tt::Ident{sym: if self { sym::true_.clone() } else { sym::false_.clone() }, span, is_raw: tt::IdentIsRaw::No } }; + _span: crate::tt::Leaf => self { self }; + _span: crate::tt::Literal => self { self }; + _span: crate::tt::Ident => self { self }; + _span: crate::tt::Punct => self { self }; + span: &str => self { crate::tt::Literal{symbol: Symbol::intern(self), span, kind: tt::LitKind::Str, suffix: None }}; + span: String => self { crate::tt::Literal{symbol: Symbol::intern(&self), span, kind: tt::LitKind::Str, suffix: None }}; + span: Name => self { + let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); + crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } + }; + span: Symbol => self { + let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); + crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } + }; +} + +#[cfg(test)] +mod tests { + use crate::tt; + use ::tt::IdentIsRaw; + use expect_test::expect; + use intern::Symbol; + use span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; + use syntax::{TextRange, TextSize}; + + use super::quote; + + const DUMMY: tt::Span = tt::Span { + range: TextRange::empty(TextSize::new(0)), + anchor: SpanAnchor { + file_id: span::EditionedFileId::new( + span::FileId::from_raw(0xe4e4e), + span::Edition::CURRENT, + ), + ast_id: ROOT_ERASED_FILE_AST_ID, + }, + ctx: SyntaxContextId::ROOT, + }; + + #[test] + fn test_quote_delimiters() { + assert_eq!(quote!(DUMMY =>{}).to_string(), "{}"); + assert_eq!(quote!(DUMMY =>()).to_string(), "()"); + assert_eq!(quote!(DUMMY =>[]).to_string(), "[]"); + } + + #[test] + fn test_quote_idents() { + assert_eq!(quote!(DUMMY =>32).to_string(), "32"); + assert_eq!(quote!(DUMMY =>struct).to_string(), "struct"); + } + + #[test] + fn test_quote_hash_simple_literal() { + let a = 20; + assert_eq!(quote!(DUMMY =>#a).to_string(), "20"); + let s: String = "hello".into(); + assert_eq!(quote!(DUMMY =>#s).to_string(), "\"hello\""); + } + + fn mk_ident(name: &str) -> crate::tt::Ident { + let (is_raw, s) = IdentIsRaw::split_from_symbol(name); + crate::tt::Ident { sym: Symbol::intern(s), span: DUMMY, is_raw } + } + + #[test] + fn test_quote_hash_token_tree() { + let a = mk_ident("hello"); + + let quoted = quote!(DUMMY =>#a); + assert_eq!(quoted.to_string(), "hello"); + let t = format!("{quoted:#?}"); + expect![[r#" + SUBTREE $$ 937550:[email protected]#0 937550:[email protected]#0 + IDENT hello 937550:[email protected]#0"#]] + .assert_eq(&t); + } + + #[test] + fn test_quote_simple_derive_copy() { + let name = mk_ident("Foo"); + + let quoted = quote! {DUMMY => + impl Clone for #name { + fn clone(&self) -> Self { + Self {} + } + } + }; + + assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}"); + } + + #[test] + fn test_quote_derive_copy_hack() { + // Assume the given struct is: + // struct Foo { + // name: String, + // id: u32, + // } + let struct_name = mk_ident("Foo"); + let fields = [mk_ident("name"), mk_ident("id")]; + let fields = fields + .iter() + .flat_map(|it| quote!(DUMMY =>#it: self.#it.clone(), ).token_trees.into_vec()); + + let list = crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::Brace, + open: DUMMY, + close: DUMMY, + }, + token_trees: fields.collect(), + }; + + let quoted = quote! {DUMMY => + impl Clone for #struct_name { + fn clone(&self) -> Self { + Self #list + } + } + }; + + assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}"); + } +} |