feat(quote): Support different types for variables (#5194)

This commit is contained in:
Donny/강동윤 2022-07-13 19:02:00 +09:00 committed by GitHub
parent 902ac55c63
commit c91abb2ca7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 167 additions and 26 deletions

View File

@ -36,6 +36,14 @@ pub struct BindingIdent {
pub type_ann: Option<TsTypeAnn>,
}
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 {

View File

@ -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.
/// ```

View File

@ -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),
);
}

View File

@ -41,7 +41,8 @@ impl_enum!(
PrivateName,
OptChain,
Invalid
]
],
true
);
impl_struct!(ThisExpr, [span]);

View File

@ -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();
}
}

View File

@ -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,

View File

@ -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<VarPos, Vars>,
}
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<String, VarData>;
pub(super) fn prepare_vars(
src: &dyn ToCode,
vars: Punctuated<QuoteVar, Token![,]>,
) -> (Vec<syn::Stmt>, Vars) {
) -> (Vec<syn::Stmt>, AHashMap<VarPos, Vars>) {
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)
}

View File

@ -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<syn::Type>,
#[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<Self> {
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()?,
})