swc_ecma_transforms_optimization/simplify/
const_propagation.rs

1#![allow(clippy::borrowed_box)]
2
3use rustc_hash::FxHashMap;
4use swc_common::util::take::Take;
5use swc_ecma_ast::*;
6use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
7
8/// This pass is kind of inliner, but it's far faster.
9pub fn constant_propagation() -> impl 'static + Pass + VisitMut {
10    visit_mut_pass(ConstPropagation::default())
11}
12
13#[derive(Default)]
14struct ConstPropagation<'a> {
15    scope: Scope<'a>,
16}
17#[derive(Default)]
18struct Scope<'a> {
19    parent: Option<&'a Scope<'a>>,
20    /// Stores only inlinable constant variables.
21    vars: FxHashMap<Id, Box<Expr>>,
22}
23
24impl<'a> Scope<'a> {
25    fn new(parent: &'a Scope<'a>) -> Self {
26        Self {
27            parent: Some(parent),
28            vars: Default::default(),
29        }
30    }
31
32    fn find_var(&self, id: &Id) -> Option<&Box<Expr>> {
33        if let Some(v) = self.vars.get(id) {
34            return Some(v);
35        }
36
37        self.parent.and_then(|parent| parent.find_var(id))
38    }
39}
40
41impl VisitMut for ConstPropagation<'_> {
42    noop_visit_mut_type!(fail);
43
44    /// No-op
45    fn visit_mut_assign_expr(&mut self, _: &mut AssignExpr) {}
46
47    fn visit_mut_export_named_specifier(&mut self, n: &mut ExportNamedSpecifier) {
48        let id = match &n.orig {
49            ModuleExportName::Ident(ident) => ident.to_id(),
50            ModuleExportName::Str(..) => return,
51            #[cfg(swc_ast_unknown)]
52            _ => panic!("unable to access unknown nodes"),
53        };
54        if let Some(expr) = self.scope.find_var(&id) {
55            if let Expr::Ident(v) = &**expr {
56                let orig = n.orig.clone();
57                n.orig = ModuleExportName::Ident(v.clone());
58
59                if n.exported.is_none() {
60                    n.exported = Some(orig);
61                }
62            }
63        }
64
65        match &n.exported {
66            Some(ModuleExportName::Ident(exported)) => match &n.orig {
67                ModuleExportName::Ident(orig) => {
68                    if exported.sym == orig.sym && exported.ctxt == orig.ctxt {
69                        n.exported = None;
70                    }
71                }
72                ModuleExportName::Str(..) => {}
73                #[cfg(swc_ast_unknown)]
74                _ => panic!("unable to access unknown nodes"),
75            },
76            Some(ModuleExportName::Str(..)) => {}
77            #[cfg(swc_ast_unknown)]
78            Some(_) => panic!("unable to access unknown nodes"),
79            None => {}
80        }
81    }
82
83    fn visit_mut_expr(&mut self, e: &mut Expr) {
84        if let Expr::Ident(i) = e {
85            if let Some(expr) = self.scope.find_var(&i.to_id()) {
86                *e = *expr.clone();
87                return;
88            }
89        }
90
91        e.visit_mut_children_with(self);
92    }
93
94    /// Although span hygiene is magic, bundler creates invalid code in aspect
95    /// of span hygiene. (The bundled code can have two variables with
96    /// identical name with each other, with respect to span hygiene.)
97    ///
98    /// We avoid bugs caused by the bundler's wrong behavior by
99    /// scoping variables.
100    fn visit_mut_function(&mut self, n: &mut Function) {
101        let scope = Scope::new(&self.scope);
102        let mut v = ConstPropagation { scope };
103        n.visit_mut_children_with(&mut v);
104    }
105
106    fn visit_mut_prop(&mut self, p: &mut Prop) {
107        p.visit_mut_children_with(self);
108
109        if let Prop::Shorthand(i) = p {
110            if let Some(expr) = self.scope.find_var(&i.to_id()) {
111                *p = Prop::KeyValue(KeyValueProp {
112                    key: PropName::Ident(i.take().into()),
113                    value: expr.clone(),
114                });
115            }
116        }
117    }
118
119    fn visit_mut_var_decl(&mut self, var: &mut VarDecl) {
120        var.decls.visit_mut_with(self);
121
122        if let VarDeclKind::Const = var.kind {
123            for decl in &var.decls {
124                if let Pat::Ident(name) = &decl.name {
125                    if let Some(init) = &decl.init {
126                        match &**init {
127                            Expr::Lit(Lit::Bool(..))
128                            | Expr::Lit(Lit::Num(..))
129                            | Expr::Lit(Lit::Null(..)) => {
130                                self.scope.vars.insert(name.to_id(), init.clone());
131                            }
132
133                            Expr::Ident(init)
134                                if name.span.is_dummy()
135                                    || var.span.is_dummy()
136                                    || init.span.is_dummy() =>
137                            {
138                                // This check is required to prevent breaking some codes.
139                                if let Some(value) = self.scope.vars.get(&init.to_id()).cloned() {
140                                    self.scope.vars.insert(name.to_id(), value);
141                                } else {
142                                    self.scope.vars.insert(name.to_id(), init.clone().into());
143                                }
144                            }
145                            _ => {}
146                        }
147                    }
148                }
149            }
150        }
151    }
152}