From c91abb2ca75ff98e8fad7a4740b6feccec62cd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 13 Jul 2022 19:02:00 +0900 Subject: [PATCH] feat(quote): Support different types for variables (#5194) --- crates/swc_ecma_ast/src/ident.rs | 8 +++ crates/swc_ecma_quote/src/lib.rs | 30 ++++++++- crates/swc_ecma_quote/tests/simple.rs | 18 ++++++ crates/swc_ecma_quote_macros/src/ast/expr.rs | 3 +- crates/swc_ecma_quote_macros/src/ast/id.rs | 4 +- crates/swc_ecma_quote_macros/src/ast/mod.rs | 49 +++++++++++---- crates/swc_ecma_quote_macros/src/ctxt.rs | 66 +++++++++++++++++--- crates/swc_ecma_quote_macros/src/input.rs | 15 ++++- 8 files changed, 167 insertions(+), 26 deletions(-) diff --git a/crates/swc_ecma_ast/src/ident.rs b/crates/swc_ecma_ast/src/ident.rs index 58148731766..0a40457bac2 100644 --- a/crates/swc_ecma_ast/src/ident.rs +++ b/crates/swc_ecma_ast/src/ident.rs @@ -36,6 +36,14 @@ pub struct BindingIdent { pub type_ann: Option, } +impl std::ops::Deref for BindingIdent { + type Target = Ident; + + fn deref(&self) -> &Self::Target { + &self.id + } +} + impl BindingIdent { /// See [Ident#to_id] for documentation. pub fn to_id(&self) -> Id { diff --git a/crates/swc_ecma_quote/src/lib.rs b/crates/swc_ecma_quote/src/lib.rs index a3fb8eee3d3..5a9c8b36bfa 100644 --- a/crates/swc_ecma_quote/src/lib.rs +++ b/crates/swc_ecma_quote/src/lib.rs @@ -43,10 +43,17 @@ mod clone; /// // Tip: Use private_ident!("ref") for real identifiers. /// ``` /// +/// ## Typed variables /// -/// # Examples +/// As this macro generates static AST, it can't substitute variables if an +/// ideitifier is not allowed in such position. In other words, this macro only +/// supports substituting /// -/// ## Quote a variable declaration +/// - Ident +/// - Expr +/// - Pat +/// +/// You can use it like /// /// ```rust /// use swc_common::DUMMY_SP; @@ -54,7 +61,24 @@ mod clone; /// use swc_ecma_quote::quote; /// /// // This will return ast for `const ref = 4;` -/// let _stmt = quote!("const $name = 4;" as Stmt, name = Ident::new("ref".into(), DUMMY_SP)); +/// let _stmt = quote!( +/// "const $name = $val;" as Stmt, +/// name = Ident::new("ref".into(), DUMMY_SP), +/// val: Expr = 4.into(), +/// ); +/// ``` +/// +/// # Examples +/// +/// ## Quote a variable declaration +/// ```rust +/// use swc_common::DUMMY_SP; +/// use swc_ecma_ast::Ident; +/// use swc_ecma_quote::quote; +/// +/// // This will return ast for `const ref = 4;` +/// let _stmt = quote!("const $name = 4;" as Stmt, name = +/// Ident::new("ref".into(), DUMMY_SP)); /// /// // Tip: Use private_ident!("ref") for real identifiers. /// ``` diff --git a/crates/swc_ecma_quote/tests/simple.rs b/crates/swc_ecma_quote/tests/simple.rs index c3fb12db0c8..6f937d5d8d1 100644 --- a/crates/swc_ecma_quote/tests/simple.rs +++ b/crates/swc_ecma_quote/tests/simple.rs @@ -27,3 +27,21 @@ fn quote_example() { name = Ident::new("ref".into(), DUMMY_SP) ); } + +#[test] +fn quote_var_type_expr() { + let _stmt = quote!( + "const $name = $val;" as Stmt, + name = Ident::new("ref".into(), DUMMY_SP), + val: Expr = 4.into(), + ); +} + +#[test] +fn quote_var_type_pat() { + let _stmt = quote!( + "const $name = $val;" as Stmt, + name: Pat = Ident::new("ref".into(), DUMMY_SP).into(), + val: Ident = Ident::new("val".into(), DUMMY_SP), + ); +} diff --git a/crates/swc_ecma_quote_macros/src/ast/expr.rs b/crates/swc_ecma_quote_macros/src/ast/expr.rs index 4f45bc255bb..93cee783b92 100644 --- a/crates/swc_ecma_quote_macros/src/ast/expr.rs +++ b/crates/swc_ecma_quote_macros/src/ast/expr.rs @@ -41,7 +41,8 @@ impl_enum!( PrivateName, OptChain, Invalid - ] + ], + true ); impl_struct!(ThisExpr, [span]); diff --git a/crates/swc_ecma_quote_macros/src/ast/id.rs b/crates/swc_ecma_quote_macros/src/ast/id.rs index 8751dd660f4..2f0397526b9 100644 --- a/crates/swc_ecma_quote_macros/src/ast/id.rs +++ b/crates/swc_ecma_quote_macros/src/ast/id.rs @@ -6,10 +6,8 @@ use crate::ctxt::Ctx; impl ToCode for swc_ecma_ast::Ident { fn to_code(&self, cx: &Ctx) -> syn::Expr { - // TODO: Check for variables. - if let Some(var_name) = self.sym.strip_prefix('$') { - if let Some(var) = cx.vars.get(var_name) { + if let Some(var) = cx.var(crate::ctxt::VarPos::Ident, var_name) { return var.get_expr(); } } diff --git a/crates/swc_ecma_quote_macros/src/ast/mod.rs b/crates/swc_ecma_quote_macros/src/ast/mod.rs index 2341ebde006..bbc5ec5ddae 100644 --- a/crates/swc_ecma_quote_macros/src/ast/mod.rs +++ b/crates/swc_ecma_quote_macros/src/ast/mod.rs @@ -15,21 +15,44 @@ macro_rules! fail_todo { }; } +macro_rules! impl_enum_body { + ($E:ident, $s:expr, $cx:expr,[ $($v:ident),* ]) => { + match $s { + $( + $E::$v(inner) => pmutil::q!( + Vars { + val: crate::ast::ToCode::to_code(inner, $cx), + }, + { swc_ecma_quote::swc_ecma_ast::$E::$v(val) } + ) + .parse(), + )* + } + }; +} + macro_rules! impl_enum { ($E:ident, [ $($v:ident),* ]) => { impl crate::ast::ToCode for $E { fn to_code(&self, cx: &crate::ctxt::Ctx) -> syn::Expr { - match self { - $( - $E::$v(inner) => pmutil::q!( - Vars { - val: crate::ast::ToCode::to_code(inner, cx), - }, - { swc_ecma_quote::swc_ecma_ast::$E::$v(val) } - ) - .parse(), - )* + impl_enum_body!($E, self, cx, [ $($v),* ]) + } + } + }; + + + ($E:ident, [ $($v:ident),* ], true) => { + impl crate::ast::ToCode for $E { + fn to_code(&self, cx: &crate::ctxt::Ctx) -> syn::Expr { + if let Self::Ident(i) = self { + if let Some(var_name) = i.sym.strip_prefix('$') { + if let Some(var) = cx.var(crate::ctxt::VarPos::$E, var_name) { + return var.get_expr(); + } + } } + + impl_enum_body!($E, self, cx, [ $($v),* ]) } } }; @@ -117,7 +140,11 @@ impl ToCode for Span { impl_enum!(ModuleItem, [ModuleDecl, Stmt]); -impl_enum!(Pat, [Ident, Array, Rest, Object, Assign, Invalid, Expr]); +impl_enum!( + Pat, + [Ident, Array, Rest, Object, Assign, Invalid, Expr], + true +); impl_enum!(Lit, [Str, Bool, Null, Num, BigInt, Regex, JSXText]); impl_enum!( ClassMember, diff --git a/crates/swc_ecma_quote_macros/src/ctxt.rs b/crates/swc_ecma_quote_macros/src/ctxt.rs index 16fba44ff99..da7e6cbc302 100644 --- a/crates/swc_ecma_quote_macros/src/ctxt.rs +++ b/crates/swc_ecma_quote_macros/src/ctxt.rs @@ -4,17 +4,32 @@ use std::cell::RefCell; use pmutil::q; use swc_common::collections::AHashMap; -use syn::{parse_quote, punctuated::Punctuated, ExprPath, ExprReference, Token}; +use swc_macros_common::call_site; +use syn::{parse_quote, punctuated::Punctuated, ExprPath, ExprReference, Ident, Token}; use crate::{ast::ToCode, input::QuoteVar}; #[derive(Debug)] pub(crate) struct Ctx { - pub(crate) vars: Vars, + pub(crate) vars: AHashMap, +} + +impl Ctx { + pub fn var(&self, ty: VarPos, var_name: &str) -> Option<&VarData> { + self.vars.get(&ty)?.get(var_name) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum VarPos { + Ident, + Expr, + Pat, } #[derive(Debug)] pub struct VarData { + pos: VarPos, is_counting: bool, /// How many times this variable should be cloned. 0 for variables used only @@ -65,9 +80,9 @@ pub type Vars = AHashMap; pub(super) fn prepare_vars( src: &dyn ToCode, vars: Punctuated, -) -> (Vec, Vars) { +) -> (Vec, AHashMap) { let mut stmts = vec![]; - let mut init_map = Vars::default(); + let mut init_map = AHashMap::<_, Vars>::default(); for var in vars { let value = var.value; @@ -75,11 +90,38 @@ pub(super) fn prepare_vars( let ident = var.name.clone(); let ident_str = ident.to_string(); + let pos = match var.ty { + Some(syn::Type::Path(syn::TypePath { + qself: None, + path: + syn::Path { + leading_colon: None, + segments, + }, + })) => { + let segment = segments.first().unwrap(); + match segment.ident.to_string().as_str() { + "Ident" => VarPos::Ident, + "Expr" => VarPos::Expr, + "Pat" => VarPos::Pat, + _ => panic!("Invalid type: {}", segment.ident), + } + } + None => VarPos::Ident, + _ => { + panic!( + "Var type should be one of: Ident, Expr, Pat; got {:?}", + var.ty + ) + } + }; + let var_ident = syn::Ident::new(&format!("quote_var_{}", ident), ident.span()); - let old = init_map.insert( + let old = init_map.entry(pos).or_default().insert( ident_str.clone(), VarData { + pos, is_counting: true, clone: Default::default(), ident: var_ident.clone(), @@ -90,8 +132,16 @@ pub(super) fn prepare_vars( panic!("Duplicate variable name: {}", ident_str); } + let type_name = Ident::new( + match pos { + VarPos::Ident => "Ident", + VarPos::Expr => "Expr", + VarPos::Pat => "Pat", + }, + call_site(), + ); stmts.push(parse_quote! { - let #var_ident = #value; + let #var_ident: swc_ecma_quote::swc_ecma_ast::#type_name = #value; }); } @@ -101,7 +151,9 @@ pub(super) fn prepare_vars( src.to_code(&cx); // We are done - cx.vars.iter_mut().for_each(|(k, v)| v.is_counting = false); + cx.vars + .iter_mut() + .for_each(|(k, v)| v.iter_mut().for_each(|(_, v)| v.is_counting = false)); (stmts, cx.vars) } diff --git a/crates/swc_ecma_quote_macros/src/input.rs b/crates/swc_ecma_quote_macros/src/input.rs index da05158874e..dbb5e60e0c7 100644 --- a/crates/swc_ecma_quote_macros/src/input.rs +++ b/crates/swc_ecma_quote_macros/src/input.rs @@ -15,6 +15,9 @@ pub(super) struct QuoteInput { pub(super) struct QuoteVar { pub name: syn::Ident, + /// Defaults to `swc_ecma_ast::Ident` + pub ty: Option, + #[allow(unused)] pub eq_token: Token![=], pub value: syn::Expr, @@ -44,8 +47,18 @@ impl Parse for QuoteInput { impl Parse for QuoteVar { fn parse(input: ParseStream) -> syn::Result { + let name = input.parse()?; + + let ty = if input.peek(Token![:]) { + let _: Token![:] = input.parse()?; + Some(input.parse()?) + } else { + None + }; + Ok(Self { - name: input.parse()?, + name, + ty, eq_token: input.parse()?, value: input.parse()?, })