feat(es/quote): Support variables (#3774)

This commit is contained in:
Donny/강동윤 2022-02-28 19:44:06 +09:00 committed by GitHub
parent 708ecc5970
commit e3c374b53b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 228 additions and 30 deletions

View File

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

View File

@ -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
View File

@ -3134,6 +3134,8 @@ dependencies = [
"swc_common",
"swc_ecma_ast",
"swc_ecma_quote_macros",
"swc_ecma_utils",
"testing",
]
[[package]]

View File

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

View File

@ -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"}

View 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 {}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)]

View File

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

View File

@ -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()),
_ => {}
}