mirror of
https://github.com/swc-project/swc.git
synced 2024-11-23 09:38:16 +03:00
feat(es/quote): Support variables (#3774)
This commit is contained in:
parent
708ecc5970
commit
e3c374b53b
4
.github/workflows/bot.yml
vendored
4
.github/workflows/bot.yml
vendored
@ -115,3 +115,7 @@ jobs:
|
||||
if: steps.bump.outcome == 'success' && steps.bump.conclusion == 'success'
|
||||
run: |
|
||||
cargo mono publish --no-verify
|
||||
cargo mono publish --no-verify
|
||||
cargo mono publish --no-verify
|
||||
cargo mono publish --no-verify
|
||||
cargo mono publish --no-verify
|
||||
|
6
.github/workflows/cargo.yml
vendored
6
.github/workflows/cargo.yml
vendored
@ -181,6 +181,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- crate: swc_ecma_preset_env
|
||||
os: windows-latest
|
||||
- crate: swc_ecma_quote
|
||||
os: ubuntu-latest
|
||||
- crate: swc_ecma_quote_macros
|
||||
os: ubuntu-latest
|
||||
- crate: swc_ecma_transforms
|
||||
os: ubuntu-latest
|
||||
check: |
|
||||
@ -266,6 +270,8 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- crate: swc_timer
|
||||
os: ubuntu-latest
|
||||
- crate: swc_trace_macro
|
||||
os: ubuntu-latest
|
||||
- crate: swc_visit
|
||||
os: ubuntu-latest
|
||||
- crate: swc_visit_macros
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3134,6 +3134,8 @@ dependencies = [
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_quote_macros",
|
||||
"swc_ecma_utils",
|
||||
"testing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -63,12 +63,14 @@ impl<'a, I: Tokens> Parser<I> {
|
||||
Ok(stmts.into_vec())
|
||||
}
|
||||
|
||||
/// Parse a statement but not a declaration.
|
||||
pub fn parse_stmt(&mut self, top_level: bool) -> PResult<Stmt> {
|
||||
trace_cur!(self, parse_stmt);
|
||||
self.parse_stmt_like(false, top_level)
|
||||
}
|
||||
|
||||
fn parse_stmt_list_item(&mut self, top_level: bool) -> PResult<Stmt> {
|
||||
/// Parse a statement and maybe a declaration.
|
||||
pub fn parse_stmt_list_item(&mut self, top_level: bool) -> PResult<Stmt> {
|
||||
trace_cur!(self, parse_stmt_list_item);
|
||||
self.parse_stmt_like(true, top_level)
|
||||
}
|
||||
|
@ -15,3 +15,5 @@ swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
|
||||
swc_common = {version = "0.17.9", path = "../swc_common"}
|
||||
swc_ecma_ast = {version = "0.68.3", path = "../swc_ecma_ast"}
|
||||
swc_ecma_quote_macros = {version = "0.1.0", path = "../swc_ecma_quote_macros"}
|
||||
swc_ecma_utils = {version = "0.69.0", path = "../swc_ecma_utils"}
|
||||
testing = {version = "0.18.0", path = "../testing"}
|
||||
|
35
crates/swc_ecma_quote/src/clone.rs
Normal file
35
crates/swc_ecma_quote/src/clone.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! Module for implicitly copyable types.
|
||||
|
||||
use swc_atoms::JsWord;
|
||||
use swc_ecma_ast::*;
|
||||
|
||||
use self::private::Sealed;
|
||||
|
||||
/// Not a public API.
|
||||
///
|
||||
/// Implemented for types which will be implicitly cloned when used as a
|
||||
/// variable in [crate::quote] macro calls.
|
||||
pub trait ImplicitClone: Clone + Sealed {
|
||||
fn clone_quote_var(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_for {
|
||||
($T:ty) => {
|
||||
impl ImplicitClone for $T {}
|
||||
impl Sealed for $T {}
|
||||
};
|
||||
}
|
||||
|
||||
impl_for!(Id);
|
||||
impl_for!(Ident);
|
||||
impl_for!(JsWord);
|
||||
|
||||
impl_for!(Str);
|
||||
impl_for!(Number);
|
||||
impl_for!(Bool);
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
@ -8,6 +8,11 @@ pub extern crate swc_ecma_ast;
|
||||
#[doc(hidden)]
|
||||
pub extern crate swc_ecma_quote_macros;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::clone::ImplicitClone;
|
||||
|
||||
mod clone;
|
||||
|
||||
/// # Supported output types
|
||||
///
|
||||
/// - `Expr`
|
||||
@ -22,15 +27,20 @@ pub extern crate swc_ecma_quote_macros;
|
||||
///
|
||||
/// # Variable substitution
|
||||
///
|
||||
/// (**Not implemented**)
|
||||
///
|
||||
/// If an identifier starts with `$`, it is substituted with the value of the
|
||||
/// parameter passed.
|
||||
///
|
||||
/// e.g.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// quote!("const $name = 4;" as Stmt, name = private_ident!("ref"))
|
||||
/// ```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.
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
@ -38,13 +48,15 @@ pub extern crate swc_ecma_quote_macros;
|
||||
///
|
||||
/// ## Quote a variable declaration
|
||||
///
|
||||
/// (**Not implemented**)
|
||||
///
|
||||
/// ```rust
|
||||
/// use swc_ecma_ast::*;
|
||||
/// use swc_common::DUMMY_SP;
|
||||
/// use swc_ecma_ast::Ident;
|
||||
/// use swc_ecma_quote::quote;
|
||||
///
|
||||
/// let stmt = quote!("const $name = 4;" as Stmt, name = private_ident!("ref"));
|
||||
/// // 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.
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! quote {
|
||||
@ -58,11 +70,11 @@ macro_rules! quote {
|
||||
/// This is an alias for [quote], but without `as Box<Expr>`.
|
||||
#[macro_export]
|
||||
macro_rules! quote_expr {
|
||||
($($tt1:tt)*) => {{
|
||||
$crate::quote!($($tt1)* as Box<Expr>)
|
||||
($src:tt) => {{
|
||||
$crate::quote!($src as Box<Expr>)
|
||||
}};
|
||||
|
||||
($($tt1:tt)*, $($tt2:tt)*) => {{
|
||||
$crate::quote!($($tt1)* as Box<Expr>, $($tt2)*)
|
||||
($src:tt, $($tt2:tt)*) => {{
|
||||
$crate::quote!($src as Box<Expr>, $($tt2)*)
|
||||
}};
|
||||
}
|
||||
|
@ -1,6 +1,29 @@
|
||||
use swc_ecma_quote::quote_expr;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::Ident;
|
||||
use swc_ecma_quote::{quote, quote_expr};
|
||||
use swc_ecma_utils::private_ident;
|
||||
|
||||
#[test]
|
||||
fn quote_expr_call_1() {
|
||||
let _expr = quote_expr!("call(arg1, typeof arg2, arg3)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote_expr_var_cloned() {
|
||||
testing::run_test2(false, |_cm, _handler| {
|
||||
let id = private_ident!("_ref");
|
||||
|
||||
let _expr = quote_expr!("call($my_id, typeof arg2, $my_id)", my_id = id);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote_example() {
|
||||
let _stmt = quote!(
|
||||
"const $name = 4;" as Stmt,
|
||||
name = Ident::new("ref".into(), DUMMY_SP)
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,12 @@ 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) {
|
||||
return var.get_expr();
|
||||
}
|
||||
}
|
||||
|
||||
q!(
|
||||
Vars {
|
||||
sym_value: self.sym.to_code(cx),
|
||||
|
@ -42,7 +42,7 @@ impl ToCode for f64 {
|
||||
fn to_code(&self, _: &Ctx) -> syn::Expr {
|
||||
syn::Expr::Lit(ExprLit {
|
||||
attrs: Default::default(),
|
||||
lit: syn::Lit::Float(LitFloat::new(&self.to_string(), Span::call_site())),
|
||||
lit: syn::Lit::Float(LitFloat::new(&format!("{}f64", self), Span::call_site())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use pmutil::q;
|
||||
use swc_common::collections::AHashMap;
|
||||
use swc_ecma_ast::Ident;
|
||||
use syn::{parse_quote, punctuated::Punctuated, ExprPath, ExprReference, Token};
|
||||
|
||||
use crate::{ast::ToCode, input::QuoteVar};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Ctx {
|
||||
@ -10,11 +15,93 @@ pub(crate) struct Ctx {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VarData {
|
||||
is_counting: bool,
|
||||
|
||||
/// How many times this variable should be cloned. 0 for variables used only
|
||||
/// once.
|
||||
clone: usize,
|
||||
clone: RefCell<usize>,
|
||||
|
||||
ident: Ident,
|
||||
ident: syn::Ident,
|
||||
}
|
||||
|
||||
impl VarData {
|
||||
pub fn get_expr(&self) -> syn::Expr {
|
||||
if self.is_counting {
|
||||
*self.clone.borrow_mut() += 1;
|
||||
return self.expr_for_var_ref();
|
||||
}
|
||||
|
||||
let use_clone = {
|
||||
let mut b = self.clone.borrow_mut();
|
||||
let val = *b;
|
||||
if val > 0 {
|
||||
*b -= 1;
|
||||
val != 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if use_clone {
|
||||
let var_ref_expr = self.expr_for_var_ref();
|
||||
|
||||
parse_quote!(swc_ecma_quote::ImplicitClone::clone_quote_var(&#var_ref_expr))
|
||||
} else {
|
||||
self.expr_for_var_ref()
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_for_var_ref(&self) -> syn::Expr {
|
||||
syn::Expr::Path(ExprPath {
|
||||
attrs: Default::default(),
|
||||
qself: Default::default(),
|
||||
path: self.ident.clone().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type Vars = AHashMap<String, VarData>;
|
||||
|
||||
pub(super) fn prepare_vars(
|
||||
src: &dyn ToCode,
|
||||
vars: Punctuated<QuoteVar, Token![,]>,
|
||||
) -> (Vec<syn::Stmt>, Vars) {
|
||||
let mut stmts = vec![];
|
||||
let mut init_map = Vars::default();
|
||||
|
||||
for var in vars {
|
||||
let value = var.value;
|
||||
|
||||
let ident = var.name.clone();
|
||||
let ident_str = ident.to_string();
|
||||
|
||||
let var_ident = syn::Ident::new(&format!("quote_var_{}", ident), ident.span());
|
||||
|
||||
let old = init_map.insert(
|
||||
ident_str.clone(),
|
||||
VarData {
|
||||
is_counting: true,
|
||||
clone: Default::default(),
|
||||
ident: var_ident.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(old) = old {
|
||||
panic!("Duplicate variable name: {}", ident_str);
|
||||
}
|
||||
|
||||
stmts.push(parse_quote! {
|
||||
let #var_ident = #value;
|
||||
});
|
||||
}
|
||||
|
||||
// Use `ToCode` to count how many times each variable is used.
|
||||
let mut cx = Ctx { vars: init_map };
|
||||
|
||||
src.to_code(&cx);
|
||||
|
||||
// We are done
|
||||
cx.vars.iter_mut().for_each(|(k, v)| v.is_counting = false);
|
||||
|
||||
(stmts, cx.vars)
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ pub(super) struct QuoteInput {
|
||||
pub vars: Option<(Token![,], Punctuated<QuoteVar, Token![,]>)>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(super) struct QuoteVar {
|
||||
pub name: syn::Ident,
|
||||
#[allow(unused)]
|
||||
|
@ -1,10 +1,16 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::iter::once;
|
||||
|
||||
use quote::ToTokens;
|
||||
use syn::{Block, ExprBlock};
|
||||
|
||||
use crate::{ast::ToCode, ctxt::Ctx, input::QuoteInput, ret_type::parse_input_type};
|
||||
use crate::{
|
||||
ast::ToCode,
|
||||
ctxt::{prepare_vars, Ctx},
|
||||
input::QuoteInput,
|
||||
ret_type::parse_input_type,
|
||||
};
|
||||
|
||||
mod ast;
|
||||
mod builder;
|
||||
@ -27,14 +33,28 @@ pub fn internal_quote(input: proc_macro::TokenStream) -> proc_macro::TokenStream
|
||||
parse_input_type(&src.value(), &output_type).expect("failed to parse input type");
|
||||
|
||||
let vars = vars.map(|v| v.1);
|
||||
let map = HashMap::default();
|
||||
|
||||
if let Some(vars) = vars {
|
||||
if !vars.is_empty() {
|
||||
todo!("quote! macro does not support variables yet")
|
||||
}
|
||||
}
|
||||
let (stmts, vars) = if let Some(vars) = vars {
|
||||
prepare_vars(&ret_type, vars)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let cx = Ctx { vars: map };
|
||||
ret_type.to_code(&cx).to_token_stream().into()
|
||||
let cx = Ctx { vars };
|
||||
|
||||
let expr_for_ast_creation = ret_type.to_code(&cx);
|
||||
|
||||
syn::Expr::Block(ExprBlock {
|
||||
attrs: Default::default(),
|
||||
label: Default::default(),
|
||||
block: Block {
|
||||
brace_token: Default::default(),
|
||||
stmts: stmts
|
||||
.into_iter()
|
||||
.chain(once(syn::Stmt::Expr(expr_for_ast_creation)))
|
||||
.collect(),
|
||||
},
|
||||
})
|
||||
.to_token_stream()
|
||||
.into()
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ pub(crate) fn parse_input_type(input_str: &str, ty: &Type) -> Result<BoxWrapper,
|
||||
match &*ident.to_string() {
|
||||
"Expr" => return parse(input_str, &mut |p| p.parse_expr().map(|v| *v)),
|
||||
"Pat" => return parse(input_str, &mut |p| p.parse_pat()),
|
||||
"Stmt" => return parse(input_str, &mut |p| p.parse_stmt(true)),
|
||||
"Stmt" => return parse(input_str, &mut |p| p.parse_stmt_list_item(true)),
|
||||
"ModuleItem" => return parse(input_str, &mut |p| p.parse_module_item()),
|
||||
_ => {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user