swc_ecma_minifier/compress/optimize/
util.rs

1use std::{
2    borrow::Borrow,
3    mem::take,
4    ops::{Deref, DerefMut},
5};
6
7use rustc_hash::{FxHashMap, FxHashSet};
8use swc_atoms::{Atom, Wtf8Atom};
9use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP};
10use swc_ecma_ast::*;
11use swc_ecma_transforms_base::perf::{Parallel, ParallelExt};
12use swc_ecma_utils::{collect_decls, contains_this_expr, ExprCtx, ExprExt, Remapper};
13use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
14use tracing::debug;
15
16use super::{Ctx, Optimizer};
17use crate::HEAVY_TASK_PARALLELS;
18
19impl<'b> Optimizer<'b> {
20    pub(super) fn normalize_expr(&mut self, e: &mut Expr) {
21        match e {
22            Expr::Seq(seq) => {
23                for e in &mut seq.exprs {
24                    self.normalize_expr(e);
25                }
26
27                if seq.exprs.len() == 1 {
28                    *e = *seq.exprs.take().into_iter().next().unwrap();
29                    self.normalize_expr(e);
30                    return;
31                }
32
33                if seq.exprs.iter().any(|v| v.is_seq()) {
34                    let mut new = Vec::new();
35
36                    for e in seq.exprs.take() {
37                        match *e {
38                            Expr::Seq(s) => {
39                                new.extend(s.exprs);
40                            }
41                            _ => new.push(e),
42                        }
43                    }
44
45                    seq.exprs = new;
46                }
47            }
48
49            Expr::Cond(cond) => {
50                self.normalize_expr(&mut cond.test);
51                self.normalize_expr(&mut cond.cons);
52                self.normalize_expr(&mut cond.alt);
53            }
54
55            Expr::Assign(a) => {
56                self.normalize_expr(&mut a.right);
57            }
58
59            _ => {}
60        }
61    }
62
63    pub(super) fn access_numeric_property<'e>(
64        &mut self,
65        _expr: &'e mut Expr,
66        _idx: usize,
67    ) -> Option<&'e mut Expr> {
68        None
69    }
70
71    /// Check for `/** @const */`.
72    pub(super) fn has_const_ann(&self, ctxt: SyntaxContext) -> bool {
73        ctxt.has_mark(self.marks.const_ann)
74    }
75
76    /// Check for `/*#__NOINLINE__*/`
77    pub(super) fn has_noinline(&self, ctxt: SyntaxContext) -> bool {
78        ctxt.has_mark(self.marks.noinline)
79    }
80
81    /// RAII guard to change context temporarically
82    pub(super) fn with_ctx(&mut self, mut ctx: Ctx) -> WithCtx<'_, 'b> {
83        let mut scope_ctxt = ctx.scope;
84
85        if self.ctx.scope != scope_ctxt {
86            if scope_ctxt.clone().remove_mark() == self.marks.fake_block {
87                scope_ctxt.remove_mark();
88            }
89            ctx.scope = scope_ctxt;
90
91            #[cfg(debug_assertions)]
92            {
93                self.data.scopes.get(&scope_ctxt).unwrap_or_else(|| {
94                    panic!("scope not found: {:?}; {:#?}", scope_ctxt, self.data.scopes)
95                });
96            }
97        }
98
99        let orig_ctx = std::mem::replace(&mut self.ctx, ctx);
100        WithCtx {
101            reducer: self,
102            orig_ctx,
103        }
104    }
105
106    pub(super) fn try_remove_label(&mut self, s: &mut LabeledStmt) {
107        if !self.options.dead_code {
108            return;
109        }
110
111        let mut analyer = LabelAnalyzer {
112            label: s.label.sym.clone(),
113            ..Default::default()
114        };
115
116        match &mut *s.body {
117            Stmt::For(ForStmt { body, .. })
118            | Stmt::ForIn(ForInStmt { body, .. })
119            | Stmt::ForOf(ForOfStmt { body, .. })
120            | Stmt::While(WhileStmt { body, .. })
121            | Stmt::DoWhile(DoWhileStmt { body, .. }) => {
122                analyer.top_breakable = true;
123                body.visit_mut_with(&mut analyer)
124            }
125            Stmt::Switch(SwitchStmt { cases, .. }) => {
126                analyer.top_breakable = true;
127                cases.visit_mut_with(&mut analyer)
128            }
129            _ => s.body.visit_mut_with(&mut analyer),
130        };
131
132        if analyer.count == 0 {
133            let _label = s.label.take();
134            self.changed = true;
135            report_change!("Removing label `{}`", _label);
136        }
137    }
138}
139
140pub(super) struct WithCtx<'a, 'b> {
141    reducer: &'a mut Optimizer<'b>,
142    orig_ctx: Ctx,
143}
144
145impl<'b> Deref for WithCtx<'_, 'b> {
146    type Target = Optimizer<'b>;
147
148    fn deref(&self) -> &Self::Target {
149        self.reducer
150    }
151}
152
153impl DerefMut for WithCtx<'_, '_> {
154    fn deref_mut(&mut self) -> &mut Self::Target {
155        self.reducer
156    }
157}
158
159impl Drop for WithCtx<'_, '_> {
160    fn drop(&mut self) {
161        self.reducer.ctx = self.orig_ctx.clone();
162    }
163}
164
165pub(crate) fn extract_class_side_effect(
166    expr_ctx: ExprCtx,
167    c: &mut Class,
168) -> Option<Vec<&mut Box<Expr>>> {
169    let mut res = Vec::new();
170    if let Some(e) = &mut c.super_class {
171        if e.may_have_side_effects(expr_ctx) {
172            res.push(e);
173        }
174    }
175
176    for m in &mut c.body {
177        match m {
178            ClassMember::Method(ClassMethod {
179                key: PropName::Computed(key),
180                ..
181            }) => {
182                if key.expr.may_have_side_effects(expr_ctx) {
183                    res.push(&mut key.expr);
184                }
185            }
186
187            ClassMember::ClassProp(p) => {
188                if let PropName::Computed(key) = &mut p.key {
189                    if key.expr.may_have_side_effects(expr_ctx) {
190                        res.push(&mut key.expr);
191                    }
192                }
193
194                if let Some(v) = &mut p.value {
195                    if p.is_static && v.may_have_side_effects(expr_ctx) {
196                        if contains_this_expr(v) {
197                            return None;
198                        }
199                        res.push(v);
200                    }
201                }
202            }
203            ClassMember::PrivateProp(PrivateProp {
204                value: Some(v),
205                is_static: true,
206                ..
207            }) => {
208                if v.may_have_side_effects(expr_ctx) {
209                    if contains_this_expr(v) {
210                        return None;
211                    }
212                    res.push(v);
213                }
214            }
215
216            _ => {}
217        }
218    }
219
220    Some(res)
221}
222
223pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {
224    !matches!(e, Expr::Lit(..) | Expr::Unary(..))
225}
226
227/// A visitor responsible for inlining special kind of variables and removing
228/// (some) unused variables. Due to the order of visit, the main visitor cannot
229/// handle all edge cases and this type is the complement for it.
230#[derive(Clone, Copy)]
231pub(crate) struct Finalizer<'a> {
232    pub simple_functions: &'a FxHashMap<Id, Box<Expr>>,
233    pub lits: &'a FxHashMap<Id, Box<Expr>>,
234    pub lits_for_cmp: &'a FxHashMap<Id, Box<Expr>>,
235    pub lits_for_array_access: &'a FxHashMap<Id, Box<Expr>>,
236    pub hoisted_props: &'a FxHashMap<(Id, Wtf8Atom), Ident>,
237
238    pub vars_to_remove: &'a FxHashSet<Id>,
239
240    pub changed: bool,
241}
242
243impl Parallel for Finalizer<'_> {
244    fn create(&self) -> Self {
245        *self
246    }
247
248    fn merge(&mut self, other: Self) {
249        self.changed |= other.changed;
250    }
251}
252
253impl Finalizer<'_> {
254    fn var(&mut self, i: &Id, mode: FinalizerMode) -> Option<Box<Expr>> {
255        let mut e = match mode {
256            FinalizerMode::Callee => {
257                let mut value = self.simple_functions.get(i).cloned()?;
258                let mut cache = FxHashMap::default();
259                let mut remap = FxHashMap::default();
260                let bindings: FxHashSet<Id> = collect_decls(&*value);
261                let new_mark = Mark::new();
262
263                // at this point, var usage no longer matter
264                for id in bindings {
265                    let new_ctxt = cache
266                        .entry(id.1)
267                        .or_insert_with(|| id.1.apply_mark(new_mark));
268
269                    let new_ctxt = *new_ctxt;
270
271                    remap.insert(id, new_ctxt);
272                }
273
274                if !remap.is_empty() {
275                    let mut remapper = Remapper::new(&remap);
276                    value.visit_mut_with(&mut remapper);
277                }
278
279                value
280            }
281            FinalizerMode::ComparisonWithLit => self.lits_for_cmp.get(i).cloned()?,
282            FinalizerMode::MemberAccess => self.lits_for_array_access.get(i).cloned()?,
283        };
284
285        e.visit_mut_children_with(self);
286
287        match &*e {
288            Expr::Ident(Ident { sym, .. }) if &**sym == "eval" => Some(
289                SeqExpr {
290                    span: DUMMY_SP,
291                    exprs: vec![0.into(), e],
292                }
293                .into(),
294            ),
295            _ => Some(e),
296        }
297    }
298
299    fn check(&mut self, e: &mut Expr, mode: FinalizerMode) {
300        if let Expr::Ident(i) = e {
301            if let Some(new) = self.var(&i.to_id(), mode) {
302                debug!("multi-replacer: Replaced `{}`", i);
303                self.changed = true;
304
305                *e = *new;
306            }
307        }
308    }
309}
310
311#[derive(Debug, Clone, Copy)]
312enum FinalizerMode {
313    Callee,
314    ComparisonWithLit,
315    MemberAccess,
316}
317
318impl VisitMut for Finalizer<'_> {
319    noop_visit_mut_type!(fail);
320
321    fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
322        e.visit_mut_children_with(self);
323
324        match e.op {
325            op!("===") | op!("!==") | op!("==") | op!("!=") => {
326                //
327                if e.left.is_lit() {
328                    self.check(&mut e.right, FinalizerMode::ComparisonWithLit);
329                } else if e.right.is_lit() {
330                    self.check(&mut e.left, FinalizerMode::ComparisonWithLit);
331                }
332            }
333            _ => {}
334        }
335    }
336
337    fn visit_mut_callee(&mut self, e: &mut Callee) {
338        e.visit_mut_children_with(self);
339
340        if let Callee::Expr(e) = e {
341            self.check(e, FinalizerMode::Callee);
342        }
343    }
344
345    fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
346        self.maybe_par(*HEAVY_TASK_PARALLELS, members, |v, member| {
347            member.visit_mut_with(v);
348        });
349    }
350
351    fn visit_mut_expr(&mut self, n: &mut Expr) {
352        match n {
353            Expr::Ident(i) => {
354                if let Some(expr) = self.lits.get(&i.to_id()) {
355                    *n = *expr.clone();
356                    return;
357                }
358            }
359            Expr::Member(e) => {
360                if let Expr::Ident(obj) = &*e.obj {
361                    let sym = match &e.prop {
362                        MemberProp::Ident(i) => i.sym.borrow(),
363                        MemberProp::Computed(e) => match &*e.expr {
364                            Expr::Ident(ident) => ident.sym.borrow(),
365                            Expr::Lit(Lit::Str(s)) => &s.value,
366                            _ => return,
367                        },
368                        _ => return,
369                    };
370
371                    if let Some(ident) = self.hoisted_props.get(&(obj.to_id(), sym.clone())) {
372                        self.changed = true;
373                        *n = ident.clone().into();
374                        return;
375                    }
376                }
377            }
378            _ => {}
379        }
380
381        n.visit_mut_children_with(self);
382    }
383
384    fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
385        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
386            n.visit_mut_with(v);
387        });
388    }
389
390    fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
391        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
392            n.visit_mut_with(v);
393        });
394    }
395
396    fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
397        e.visit_mut_children_with(self);
398
399        if let MemberProp::Computed(ref mut prop) = e.prop {
400            if let Expr::Lit(Lit::Num(..)) = &*prop.expr {
401                self.check(&mut e.obj, FinalizerMode::MemberAccess);
402            }
403        }
404    }
405
406    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
407        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
408            n.visit_mut_with(v);
409        });
410    }
411
412    fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
413        n.visit_mut_children_with(self);
414
415        if let Some(VarDeclOrExpr::VarDecl(v)) = n {
416            if v.decls.is_empty() {
417                *n = None;
418            }
419        }
420    }
421
422    fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
423        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
424            n.visit_mut_with(v);
425        });
426    }
427
428    fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
429        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
430            n.visit_mut_with(v);
431        });
432    }
433
434    fn visit_mut_stmt(&mut self, n: &mut Stmt) {
435        n.visit_mut_children_with(self);
436
437        if let Stmt::Decl(Decl::Var(v)) = n {
438            if v.decls.is_empty() {
439                n.take();
440            }
441        }
442    }
443
444    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
445        self.maybe_par(*HEAVY_TASK_PARALLELS, n, |v, n| {
446            n.visit_mut_with(v);
447        });
448    }
449
450    fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
451        n.visit_mut_children_with(self);
452
453        if n.init.is_none() {
454            if let Pat::Ident(i) = &n.name {
455                if self.vars_to_remove.contains(&i.to_id()) {
456                    n.name.take();
457                }
458            }
459        }
460    }
461
462    fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
463        n.visit_mut_children_with(self);
464
465        n.retain(|v| !v.name.is_invalid());
466    }
467
468    fn visit_mut_prop(&mut self, n: &mut Prop) {
469        n.visit_mut_children_with(self);
470
471        if let Prop::Shorthand(i) = n {
472            if let Some(expr) = self.lits.get(&i.to_id()) {
473                let key = prop_name_from_ident(i.take());
474                *n = Prop::KeyValue(KeyValueProp {
475                    key,
476                    value: expr.clone(),
477                });
478                self.changed = true;
479            }
480        }
481    }
482}
483
484pub(crate) struct NormalMultiReplacer<'a> {
485    pub vars: &'a mut FxHashMap<Id, Box<Expr>>,
486    pub changed: bool,
487    should_consume: bool,
488}
489
490impl<'a> NormalMultiReplacer<'a> {
491    /// `worked` will be changed to `true` if any replacement is done
492    pub fn new(vars: &'a mut FxHashMap<Id, Box<Expr>>, should_consume: bool) -> Self {
493        NormalMultiReplacer {
494            vars,
495            should_consume,
496            changed: false,
497        }
498    }
499
500    fn var(&mut self, i: &Id) -> Option<Box<Expr>> {
501        let mut e = self.vars.remove(i)?;
502
503        e.visit_mut_children_with(self);
504
505        let e = if self.should_consume {
506            e
507        } else {
508            let new_e = e.clone();
509            self.vars.insert(i.clone(), e);
510
511            new_e
512        };
513
514        match &*e {
515            Expr::Ident(Ident { sym, .. }) if &**sym == "eval" => Some(
516                SeqExpr {
517                    span: DUMMY_SP,
518                    exprs: vec![0.into(), e],
519                }
520                .into(),
521            ),
522            _ => Some(e),
523        }
524    }
525}
526
527impl VisitMut for NormalMultiReplacer<'_> {
528    noop_visit_mut_type!(fail);
529
530    fn visit_mut_expr(&mut self, e: &mut Expr) {
531        if self.vars.is_empty() {
532            return;
533        }
534        e.visit_mut_children_with(self);
535
536        if self.vars.is_empty() {
537            return;
538        }
539
540        if let Expr::Ident(i) = e {
541            if let Some(new) = self.var(&i.to_id()) {
542                debug!("multi-replacer: Replaced `{}`", i);
543                self.changed = true;
544
545                *e = *new;
546            }
547        }
548    }
549
550    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
551        if self.vars.is_empty() {
552            return;
553        }
554        items.visit_mut_children_with(self);
555
556        #[cfg(feature = "debug")]
557        if !self.vars.is_empty() {
558            let keys = self.vars.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>();
559            debug!("Dropping {:?}", keys);
560        }
561    }
562
563    fn visit_mut_prop(&mut self, p: &mut Prop) {
564        p.visit_mut_children_with(self);
565
566        if let Prop::Shorthand(i) = p {
567            if let Some(value) = self.var(&i.to_id()) {
568                debug!("multi-replacer: Replaced `{}` as shorthand", i);
569                self.changed = true;
570
571                let key = prop_name_from_ident(i.take());
572                *p = Prop::KeyValue(KeyValueProp { key, value });
573            }
574        }
575    }
576
577    fn visit_mut_stmt(&mut self, node: &mut Stmt) {
578        if self.vars.is_empty() {
579            return;
580        }
581
582        node.visit_mut_children_with(self);
583    }
584}
585
586pub(crate) fn replace_id_with_expr<N>(node: &mut N, from: Id, to: Box<Expr>) -> Option<Box<Expr>>
587where
588    N: VisitMutWith<ExprReplacer>,
589{
590    let mut v = ExprReplacer { from, to: Some(to) };
591    node.visit_mut_with(&mut v);
592
593    v.to
594}
595
596pub(crate) struct ExprReplacer {
597    from: Id,
598    to: Option<Box<Expr>>,
599}
600
601impl ExprReplacer {
602    fn take(&mut self) -> Option<Box<Expr>> {
603        let e = self.to.take()?;
604
605        match &*e {
606            Expr::Ident(Ident { sym, .. }) if &**sym == "eval" => Some(
607                SeqExpr {
608                    span: DUMMY_SP,
609                    exprs: vec![0.into(), e],
610                }
611                .into(),
612            ),
613            _ => Some(e),
614        }
615    }
616}
617
618impl VisitMut for ExprReplacer {
619    noop_visit_mut_type!(fail);
620
621    fn visit_mut_expr(&mut self, e: &mut Expr) {
622        e.visit_mut_children_with(self);
623
624        if let Expr::Ident(i) = e {
625            if self.from.0 == i.sym && self.from.1 == i.ctxt {
626                if let Some(new) = self.take() {
627                    *e = *new;
628                } else {
629                    unreachable!("`{}` is already taken", i)
630                }
631            }
632        }
633    }
634
635    fn visit_mut_prop(&mut self, p: &mut Prop) {
636        p.visit_mut_children_with(self);
637
638        if let Prop::Shorthand(i) = p {
639            if self.from.0 == i.sym && self.from.1 == i.ctxt {
640                let value = if let Some(new) = self.take() {
641                    new
642                } else {
643                    unreachable!("`{}` is already taken", i)
644                };
645                let key = prop_name_from_ident(i.take());
646                *p = Prop::KeyValue(KeyValueProp { key, value });
647            }
648        }
649    }
650}
651
652#[derive(Debug, Default, PartialEq, Eq)]
653pub(super) struct SynthesizedStmts(Vec<Stmt>);
654
655impl SynthesizedStmts {
656    pub fn take_stmts(&mut self) -> Vec<Stmt> {
657        take(&mut self.0)
658    }
659}
660
661impl std::ops::Deref for SynthesizedStmts {
662    type Target = Vec<Stmt>;
663
664    fn deref(&self) -> &Self::Target {
665        &self.0
666    }
667}
668
669impl SynthesizedStmts {
670    pub fn push(&mut self, stmt: Stmt) {
671        self.0.push(stmt);
672    }
673
674    pub fn extend(&mut self, stmts: impl IntoIterator<Item = Stmt>) {
675        self.0.extend(stmts);
676    }
677
678    pub fn append(&mut self, other: &mut SynthesizedStmts) {
679        self.0.append(&mut other.0);
680    }
681}
682
683impl std::ops::DerefMut for SynthesizedStmts {
684    fn deref_mut(&mut self) -> &mut Self::Target {
685        &mut self.0
686    }
687}
688
689impl Take for SynthesizedStmts {
690    fn dummy() -> Self {
691        Self(Take::dummy())
692    }
693}
694
695impl Drop for SynthesizedStmts {
696    fn drop(&mut self) {
697        if !self.0.is_empty() {
698            if !std::thread::panicking() {
699                panic!("We should not drop synthesized stmts");
700            }
701        }
702    }
703}
704
705#[derive(Default)]
706struct LabelAnalyzer {
707    label: Atom,
708    /// If top level is a normal block, labelled break must be preserved
709    top_breakable: bool,
710    count: usize,
711    break_layer: usize,
712    continue_layer: usize,
713}
714
715impl LabelAnalyzer {
716    fn visit_mut_loop(&mut self, n: &mut impl VisitMutWith<LabelAnalyzer>) {
717        self.break_layer += 1;
718        self.continue_layer += 1;
719
720        n.visit_mut_children_with(self);
721
722        self.break_layer -= 1;
723        self.continue_layer -= 1;
724    }
725}
726
727impl VisitMut for LabelAnalyzer {
728    fn visit_mut_function(&mut self, _: &mut Function) {}
729
730    fn visit_mut_class(&mut self, _: &mut Class) {}
731
732    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
733
734    fn visit_mut_object_lit(&mut self, _: &mut ObjectLit) {}
735
736    fn visit_mut_for_stmt(&mut self, n: &mut ForStmt) {
737        self.visit_mut_loop(n)
738    }
739
740    fn visit_mut_for_in_stmt(&mut self, n: &mut ForInStmt) {
741        self.visit_mut_loop(n)
742    }
743
744    fn visit_mut_for_of_stmt(&mut self, n: &mut ForOfStmt) {
745        self.visit_mut_loop(n)
746    }
747
748    fn visit_mut_while_stmt(&mut self, n: &mut WhileStmt) {
749        self.visit_mut_loop(n)
750    }
751
752    fn visit_mut_do_while_stmt(&mut self, n: &mut DoWhileStmt) {
753        self.visit_mut_loop(n)
754    }
755
756    fn visit_mut_switch_stmt(&mut self, n: &mut SwitchStmt) {
757        self.break_layer += 1;
758
759        n.visit_mut_children_with(self);
760
761        self.break_layer -= 1;
762    }
763
764    fn visit_mut_break_stmt(&mut self, n: &mut BreakStmt) {
765        if let Some(lb) = &n.label {
766            if lb.sym == self.label {
767                if self.break_layer > 0 || !self.top_breakable {
768                    self.count += 1;
769                } else {
770                    n.label = None
771                }
772            }
773        }
774    }
775
776    fn visit_mut_continue_stmt(&mut self, n: &mut ContinueStmt) {
777        if let Some(lb) = &n.label {
778            if lb.sym == self.label {
779                if self.continue_layer > 0 {
780                    self.count += 1;
781                } else {
782                    n.label = None
783                }
784            }
785        }
786    }
787}
788
789pub fn get_ids_of_pat(pat: &Pat) -> Vec<Id> {
790    fn append(pat: &Pat, ids: &mut Vec<Id>) {
791        match pat {
792            Pat::Ident(binding_ident) => ids.push(binding_ident.id.to_id()),
793            Pat::Array(array_pat) => {
794                for pat in array_pat.elems.iter().flatten() {
795                    append(pat, ids);
796                }
797            }
798            Pat::Rest(rest_pat) => append(&rest_pat.arg, ids),
799            Pat::Object(object_pat) => {
800                for pat in &object_pat.props {
801                    match pat {
802                        ObjectPatProp::KeyValue(key_value_pat_prop) => {
803                            append(&key_value_pat_prop.value, ids)
804                        }
805                        ObjectPatProp::Assign(assign_pat_prop) => {
806                            ids.push(assign_pat_prop.key.to_id())
807                        }
808                        ObjectPatProp::Rest(rest_pat) => append(&rest_pat.arg, ids),
809                        #[cfg(swc_ast_unknown)]
810                        _ => panic!("unable to access unknown nodes"),
811                    }
812                }
813            }
814            Pat::Assign(assign_pat) => append(&assign_pat.left, ids),
815            Pat::Invalid(_) | Pat::Expr(_) => {}
816            #[cfg(swc_ast_unknown)]
817            _ => panic!("unable to access unknown nodes"),
818        }
819    }
820
821    let mut idents = vec![];
822    append(pat, &mut idents);
823    idents
824}
825
826/// Creates a PropName for a shorthand property, handling the special case of
827/// `__proto__`. When the property name is `__proto__`, it must be converted to
828/// a computed property to preserve JavaScript semantics.
829fn prop_name_from_ident(ident: Ident) -> PropName {
830    if ident.sym == "__proto__" {
831        PropName::Computed(ComputedPropName {
832            span: ident.span,
833            expr: Box::new(Expr::Lit(Lit::Str(Str {
834                span: ident.span,
835                value: ident.sym.clone().into(),
836                raw: None,
837            }))),
838        })
839    } else {
840        ident.into()
841    }
842}