mirror of
https://github.com/swc-project/swc.git
synced 2024-11-28 02:29:04 +03:00
feat(quote): Support different types for variables (#5194)
This commit is contained in:
parent
902ac55c63
commit
c91abb2ca7
@ -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 {
|
||||
|
@ -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.
|
||||
/// ```
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ impl_enum!(
|
||||
PrivateName,
|
||||
OptChain,
|
||||
Invalid
|
||||
]
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
impl_struct!(ThisExpr, [span]);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()?,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user