swc_ecma_quote_macros/
ctxt.rs

1#![allow(unused)]
2
3use std::cell::RefCell;
4
5use rustc_hash::FxHashMap;
6use swc_macros_common::call_site;
7use syn::{parse_quote, punctuated::Punctuated, ExprPath, ExprReference, Ident, Token};
8
9use crate::{ast::ToCode, input::QuoteVar};
10
11#[derive(Debug)]
12pub(crate) struct Ctx {
13    pub(crate) vars: FxHashMap<VarPos, Vars>,
14}
15
16impl Ctx {
17    pub fn var(&self, ty: VarPos, var_name: &str) -> Option<&VarData> {
18        self.vars.get(&ty)?.get(var_name)
19    }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub enum VarPos {
24    Ident,
25    Expr,
26    Pat,
27    AssignTarget,
28    Str,
29}
30
31#[derive(Debug)]
32pub struct VarData {
33    pos: VarPos,
34    is_counting: bool,
35
36    /// How many times this variable should be cloned. 0 for variables used only
37    /// once.
38    clone: RefCell<usize>,
39
40    ident: syn::Ident,
41}
42
43impl VarData {
44    pub fn get_expr(&self) -> syn::Expr {
45        if self.is_counting {
46            *self.clone.borrow_mut() += 1;
47            return self.expr_for_var_ref();
48        }
49
50        let use_clone = {
51            let mut b = self.clone.borrow_mut();
52            let val = *b;
53            if val > 0 {
54                *b -= 1;
55                val != 1
56            } else {
57                false
58            }
59        };
60
61        if use_clone {
62            let var_ref_expr = self.expr_for_var_ref();
63
64            parse_quote!(swc_core::quote::ImplicitClone::clone_quote_var(&#var_ref_expr))
65        } else {
66            self.expr_for_var_ref()
67        }
68    }
69
70    fn expr_for_var_ref(&self) -> syn::Expr {
71        syn::Expr::Path(ExprPath {
72            attrs: Default::default(),
73            qself: Default::default(),
74            path: self.ident.clone().into(),
75        })
76    }
77}
78
79pub type Vars = FxHashMap<String, VarData>;
80
81pub(super) fn prepare_vars(
82    src: &dyn ToCode,
83    vars: Punctuated<QuoteVar, Token![,]>,
84) -> (Vec<syn::Stmt>, FxHashMap<VarPos, Vars>) {
85    let mut stmts = Vec::new();
86    let mut init_map = FxHashMap::<_, Vars>::default();
87
88    for var in vars {
89        let value = var.value;
90
91        let ident = var.name.clone();
92        let ident_str = ident.to_string();
93
94        let pos = match var.ty {
95            Some(syn::Type::Path(syn::TypePath {
96                qself: None,
97                path:
98                    syn::Path {
99                        leading_colon: None,
100                        segments,
101                    },
102            })) => {
103                let segment = segments.first().unwrap();
104                match segment.ident.to_string().as_str() {
105                    "Ident" => VarPos::Ident,
106                    "Expr" => VarPos::Expr,
107                    "Pat" => VarPos::Pat,
108                    "Str" => VarPos::Str,
109                    "AssignTarget" => VarPos::AssignTarget,
110                    _ => panic!("Invalid type: {:?}", segment.ident),
111                }
112            }
113            None => VarPos::Ident,
114            _ => {
115                panic!(
116                    "Var type should be one of: Ident, Expr, Pat; got {:?}",
117                    var.ty
118                )
119            }
120        };
121
122        let var_ident = syn::Ident::new(&format!("quote_var_{ident}"), ident.span());
123
124        let old = init_map.entry(pos).or_default().insert(
125            ident_str.clone(),
126            VarData {
127                pos,
128                is_counting: true,
129                clone: Default::default(),
130                ident: var_ident.clone(),
131            },
132        );
133
134        if let Some(old) = old {
135            panic!("Duplicate variable name: {ident_str}");
136        }
137
138        let type_name = Ident::new(
139            match pos {
140                VarPos::Ident => "Ident",
141                VarPos::Expr => "Expr",
142                VarPos::Pat => "Pat",
143                VarPos::AssignTarget => "AssignTarget",
144                VarPos::Str => "Str",
145            },
146            call_site(),
147        );
148        stmts.push(parse_quote! {
149            let #var_ident: swc_core::ecma::ast::#type_name = #value;
150        });
151    }
152
153    // Use `ToCode` to count how many times each variable is used.
154    let mut cx = Ctx { vars: init_map };
155
156    src.to_code(&cx);
157
158    // We are done
159    cx.vars
160        .iter_mut()
161        .for_each(|(k, v)| v.iter_mut().for_each(|(_, v)| v.is_counting = false));
162
163    (stmts, cx.vars)
164}