swc_ecma_minifier/compress/optimize/
sequences.rs

1use std::{iter::once, mem::take};
2
3use rustc_hash::FxHashSet;
4use swc_common::{pass::Either, util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_usage_analyzer::{
7    alias::{try_collect_infects_from, AccessKind, AliasConfig},
8    util::is_global_var_with_pure_property_access,
9};
10use swc_ecma_utils::{
11    contains_arguments, contains_this_expr, prepend_stmts, ExprExt, StmtLike, Type, Value,
12};
13use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
14#[cfg(feature = "debug")]
15use tracing::{span, Level};
16
17use super::{is_pure_undefined, Optimizer};
18#[cfg(feature = "debug")]
19use crate::debug::dump;
20use crate::{
21    compress::{
22        optimize::{unused::PropertyAccessOpts, util::replace_id_with_expr, BitCtx},
23        util::{is_directive, is_ident_used_by, replace_expr},
24    },
25    option::CompressOptions,
26    program_data::{ScopeData, VarUsageInfoFlags},
27    util::{
28        idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, IdentUsageCollector,
29        ModuleItemExt,
30    },
31};
32
33/// Methods related to the option `sequences`. All methods are noop if
34/// `sequences` is false.
35impl Optimizer<'_> {
36    ///
37    /// # Example
38    ///
39    ///
40    /// ## Input
41    ///
42    /// ```ts
43    /// x = 5;
44    /// if (y) z();
45    /// x = 5;
46    /// for (i = 0; i < 5; i++) console.log(i);
47    /// x = 5;
48    /// for (; i < 5; i++) console.log(i);
49    /// x = 5;
50    /// switch (y) {
51    /// }
52    /// x = 5;
53    /// with (obj) {
54    /// }
55    /// ```
56    ///
57    /// ## Output
58    /// ```ts
59    /// if (x = 5, y) z();
60    /// for(x = 5, i = 0; i < 5; i++)console.log(i);
61    /// for(x = 5; i < 5; i++)console.log(i);
62    /// switch(x = 5, y){
63    /// }
64    /// with (x = 5, obj);
65    /// ```
66    pub(super) fn make_sequences<T>(&mut self, stmts: &mut Vec<T>)
67    where
68        T: StmtLike,
69    {
70        if !self.options.sequences() {
71            log_abort!("sequences: make_sequence for statements is disabled");
72            return;
73        }
74        if self.ctx.bit_ctx.contains(BitCtx::InAsm) {
75            log_abort!("sequences: asm.js is not supported");
76            return;
77        }
78
79        {
80            let can_work =
81                stmts
82                    .windows(2)
83                    .any(|stmts| match (stmts[0].as_stmt(), stmts[1].as_stmt()) {
84                        (Some(l @ Stmt::Expr(..)), Some(r)) => {
85                            if is_directive(l) || is_directive(r) {
86                                return false;
87                            }
88
89                            // If an expression contains `in` and following statement is for loop,
90                            // we should not merge it.
91
92                            // TODO: Check for `in`
93
94                            match r {
95                                Stmt::Expr(..)
96                                | Stmt::If(..)
97                                | Stmt::Switch(..)
98                                | Stmt::With(..)
99                                | Stmt::Return(ReturnStmt { arg: Some(..), .. })
100                                | Stmt::Throw(ThrowStmt { .. })
101                                | Stmt::For(ForStmt { init: None, .. })
102                                | Stmt::For(ForStmt {
103                                    init: Some(VarDeclOrExpr::Expr(..)),
104                                    ..
105                                })
106                                | Stmt::ForIn(..)
107                                | Stmt::ForOf(..) => true,
108
109                                Stmt::Decl(Decl::Var(v))
110                                    if matches!(
111                                        &**v,
112                                        VarDecl {
113                                            kind: VarDeclKind::Var,
114                                            ..
115                                        }
116                                    ) =>
117                                {
118                                    v.decls.iter().all(|vd| vd.init.is_none())
119                                }
120
121                                Stmt::Decl(Decl::Fn(..)) => true,
122
123                                _ => false,
124                            }
125                        }
126                        _ => false,
127                    });
128
129            if !can_work {
130                return;
131            }
132
133            if stmts.len() == 2 {
134                match stmts[1].as_stmt() {
135                    Some(Stmt::Decl(Decl::Var(v)))
136                        if matches!(
137                            &**v,
138                            VarDecl {
139                                kind: VarDeclKind::Var,
140                                ..
141                            },
142                        ) =>
143                    {
144                        if v.decls.iter().all(|vd| vd.init.is_none()) {
145                            return;
146                        }
147                    }
148
149                    Some(Stmt::Decl(Decl::Fn(..))) => return,
150
151                    _ => {}
152                }
153            }
154        }
155
156        report_change!("sequences: Compressing statements as a sequences");
157
158        self.changed = true;
159        let mut exprs = Vec::new();
160        // This is bigger than required.
161        let mut new_stmts = Vec::with_capacity(stmts.len());
162
163        for stmt in stmts.take() {
164            match stmt.try_into_stmt() {
165                Ok(stmt) => {
166                    if is_directive(&stmt) {
167                        new_stmts.push(T::from(stmt));
168                        continue;
169                    }
170                    // If
171                    match stmt {
172                        Stmt::Expr(stmt) => {
173                            exprs.push(stmt.expr);
174                        }
175
176                        Stmt::If(mut stmt) => {
177                            stmt.test.prepend_exprs(take(&mut exprs));
178                            new_stmts.push(T::from(stmt.into()));
179                        }
180
181                        Stmt::Switch(mut stmt) => {
182                            stmt.discriminant.prepend_exprs(take(&mut exprs));
183
184                            new_stmts.push(T::from(stmt.into()));
185                        }
186
187                        Stmt::With(mut stmt) => {
188                            stmt.obj.prepend_exprs(take(&mut exprs));
189
190                            new_stmts.push(T::from(stmt.into()));
191                        }
192
193                        Stmt::Return(mut stmt @ ReturnStmt { arg: Some(..), .. }) => {
194                            if let Some(e) = stmt.arg.as_deref_mut() {
195                                e.prepend_exprs(take(&mut exprs));
196                            }
197
198                            new_stmts.push(T::from(stmt.into()));
199                        }
200
201                        Stmt::Throw(mut stmt) => {
202                            stmt.arg.prepend_exprs(take(&mut exprs));
203
204                            new_stmts.push(T::from(stmt.into()));
205                        }
206
207                        Stmt::For(mut stmt @ ForStmt { init: None, .. })
208                        | Stmt::For(
209                            mut stmt @ ForStmt {
210                                init: Some(VarDeclOrExpr::Expr(..)),
211                                ..
212                            },
213                        ) => {
214                            match &mut stmt.init {
215                                Some(VarDeclOrExpr::Expr(e)) => {
216                                    if exprs.iter().all(|expr| {
217                                        matches!(
218                                            &**expr,
219                                            Expr::Assign(AssignExpr { op: op!("="), .. })
220                                        )
221                                    }) {
222                                        let ids_used_by_exprs =
223                                            idents_used_by_ignoring_nested(&exprs);
224
225                                        let ids_used_by_first_expr =
226                                            idents_used_by_ignoring_nested(&*e.first_expr_mut());
227
228                                        let has_conflict = ids_used_by_exprs
229                                            .iter()
230                                            .any(|id| ids_used_by_first_expr.contains(id));
231
232                                        // I(kdy1) don't know why we need this, but terser appends
233                                        // instead of prependig if initializer is (exactly)
234                                        //
235                                        // "identifier" = "literal".
236                                        //
237                                        // Note that only the form above makes terser to append.
238                                        //
239                                        // When I tested in by changing input multiple times, terser
240                                        // seems to be aware of side effects.
241                                        //
242                                        // Maybe there exists an optimization related to it in v8.
243                                        if let Expr::Assign(AssignExpr {
244                                            op: op!("="),
245                                            left,
246                                            right,
247                                            ..
248                                        }) = e.first_expr_mut()
249                                        {
250                                            if !has_conflict
251                                                && left.as_ident().is_some()
252                                                && match &**right {
253                                                    Expr::Lit(Lit::Regex(..)) => false,
254                                                    Expr::Lit(..) => true,
255                                                    _ => false,
256                                                }
257                                            {
258                                                let seq = e.force_seq();
259                                                let extra =
260                                                    seq.exprs.drain(1..).collect::<Vec<_>>();
261                                                seq.exprs.extend(take(&mut exprs));
262                                                seq.exprs.extend(extra);
263
264                                                if seq.exprs.len() == 1 {
265                                                    stmt.init = Some(VarDeclOrExpr::Expr(
266                                                        seq.exprs.pop().unwrap(),
267                                                    ));
268                                                }
269
270                                                new_stmts.push(T::from(stmt.into()));
271
272                                                continue;
273                                            }
274                                        }
275                                    }
276                                    e.prepend_exprs(take(&mut exprs));
277                                }
278                                None => {
279                                    if exprs.is_empty() {
280                                        stmt.init = None;
281                                    } else {
282                                        stmt.init = Some(VarDeclOrExpr::Expr(Expr::from_exprs(
283                                            take(&mut exprs),
284                                        )))
285                                    }
286                                }
287                                _ => {
288                                    unreachable!()
289                                }
290                            }
291                            new_stmts.push(T::from(stmt.into()));
292                        }
293
294                        Stmt::ForIn(mut stmt) => {
295                            stmt.right.prepend_exprs(take(&mut exprs));
296
297                            new_stmts.push(T::from(stmt.into()));
298                        }
299
300                        Stmt::ForOf(mut stmt) => {
301                            stmt.right.prepend_exprs(take(&mut exprs));
302
303                            new_stmts.push(T::from(stmt.into()));
304                        }
305
306                        Stmt::Decl(Decl::Var(var))
307                            if matches!(
308                                &*var,
309                                VarDecl {
310                                    kind: VarDeclKind::Var,
311                                    ..
312                                }
313                            ) && var.decls.iter().all(|v| v.init.is_none()) =>
314                        {
315                            new_stmts.push(T::from(var.into()));
316                        }
317
318                        Stmt::Decl(Decl::Fn(..)) => {
319                            new_stmts.push(T::from(stmt));
320                        }
321
322                        _ => {
323                            if !exprs.is_empty() {
324                                new_stmts.push(T::from(
325                                    ExprStmt {
326                                        span: DUMMY_SP,
327                                        expr: Expr::from_exprs(take(&mut exprs)),
328                                    }
329                                    .into(),
330                                ))
331                            }
332
333                            new_stmts.push(T::from(stmt));
334                        }
335                    }
336                }
337                Err(item) => {
338                    if !exprs.is_empty() {
339                        new_stmts.push(T::from(
340                            ExprStmt {
341                                span: DUMMY_SP,
342                                expr: Expr::from_exprs(take(&mut exprs)),
343                            }
344                            .into(),
345                        ))
346                    }
347
348                    new_stmts.push(item);
349                }
350            }
351        }
352
353        if !exprs.is_empty() {
354            new_stmts.push(T::from(
355                ExprStmt {
356                    span: DUMMY_SP,
357                    expr: Expr::from_exprs(take(&mut exprs)),
358                }
359                .into(),
360            ))
361        }
362
363        *stmts = new_stmts;
364    }
365
366    /// Lift sequence expressions in an assign expression.
367    ///
368    /// - `(a = (f, 4)) => (f, a = 4)`
369    pub(super) fn lift_seqs_of_assign(&mut self, e: &mut Expr) {
370        if !self.options.sequences() {
371            return;
372        }
373
374        if let Expr::Assign(AssignExpr {
375            op: op!("="),
376            left,
377            right,
378            span,
379        }) = e
380        {
381            let left_can_lift = match left {
382                AssignTarget::Simple(SimpleAssignTarget::Ident(i))
383                    if i.id.ctxt != self.ctx.expr_ctx.unresolved_ctxt =>
384                {
385                    true
386                }
387
388                AssignTarget::Simple(SimpleAssignTarget::Member(MemberExpr {
389                    obj,
390                    prop: MemberProp::Ident(_) | MemberProp::PrivateName(_),
391                    ..
392                })) => {
393                    if let Some(id) = obj.as_ident() {
394                        if let Some(usage) = self.data.vars.get(&id.to_id()) {
395                            id.ctxt != self.ctx.expr_ctx.unresolved_ctxt
396                                && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
397                        } else {
398                            false
399                        }
400                    } else {
401                        false
402                    }
403                }
404                _ => false,
405            };
406
407            if !left_can_lift {
408                return;
409            }
410
411            if let Expr::Seq(seq) = &mut **right {
412                // Do we really need this?
413                if seq.exprs.is_empty() || seq.exprs.len() <= 1 {
414                    return;
415                }
416                report_change!("sequences: Lifting Assign");
417                self.changed = true;
418                if let Some(last) = seq.exprs.last_mut() {
419                    **last = AssignExpr {
420                        span: *span,
421                        op: op!("="),
422                        left: left.take(),
423                        right: last.take(),
424                    }
425                    .into()
426                }
427
428                *e = *right.take()
429            }
430        }
431    }
432
433    #[allow(unused)]
434    pub(super) fn optimize_with_extras<T, F>(&mut self, stmts: &mut Vec<T>, mut op: F)
435    where
436        F: FnMut(&mut Vec<T>),
437        T: ModuleItemExt,
438    {
439        let old_prepend = self.prepend_stmts.take();
440        let old_append = self.append_stmts.take();
441
442        op(stmts);
443
444        if !self.prepend_stmts.is_empty() {
445            prepend_stmts(stmts, self.prepend_stmts.drain(..).map(T::from));
446        }
447
448        if !self.append_stmts.is_empty() {
449            stmts.extend(self.append_stmts.drain(..).map(T::from));
450        }
451
452        self.prepend_stmts = old_prepend;
453        self.append_stmts = old_append;
454    }
455
456    fn seq_exprs_of<'a>(
457        &mut self,
458        s: &'a mut Stmt,
459        options: &CompressOptions,
460    ) -> Option<Either<impl Iterator<Item = Mergable<'a>>, std::iter::Once<Mergable<'a>>>> {
461        Some(match s {
462            Stmt::Expr(e) => {
463                if self.options.sequences()
464                    || self.options.collapse_vars
465                    || self.options.side_effects
466                {
467                    Either::Right(once(Mergable::Expr(&mut e.expr)))
468                } else {
469                    return None;
470                }
471            }
472            Stmt::Decl(Decl::Var(v)) => {
473                if options.reduce_vars || options.collapse_vars {
474                    Either::Left(v.decls.iter_mut().map(Mergable::Var))
475                } else {
476                    return None;
477                }
478            }
479            Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => {
480                Either::Right(once(Mergable::Expr(arg)))
481            }
482
483            Stmt::If(s) if options.sequences() => Either::Right(once(Mergable::Expr(&mut s.test))),
484
485            Stmt::Switch(s) if options.sequences() => {
486                Either::Right(once(Mergable::Expr(&mut s.discriminant)))
487            }
488
489            Stmt::For(s) if options.sequences() => {
490                if let Some(VarDeclOrExpr::Expr(e)) = &mut s.init {
491                    Either::Right(once(Mergable::Expr(e)))
492                } else {
493                    return None;
494                }
495            }
496
497            Stmt::ForOf(s) if options.sequences() => {
498                Either::Right(once(Mergable::Expr(&mut s.right)))
499            }
500
501            Stmt::ForIn(s) if options.sequences() => {
502                Either::Right(once(Mergable::Expr(&mut s.right)))
503            }
504
505            Stmt::Throw(s) if options.sequences() => {
506                Either::Right(once(Mergable::Expr(&mut s.arg)))
507            }
508
509            Stmt::Decl(Decl::Fn(f)) => {
510                // Check for side effects
511
512                Either::Right(once(Mergable::FnDecl(f)))
513            }
514
515            _ => return None,
516        })
517    }
518
519    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
520    pub(super) fn merge_sequences_in_stmts<T>(&mut self, stmts: &mut Vec<T>, will_terminate: bool)
521    where
522        T: ModuleItemExt,
523    {
524        if !self.options.sequences() && !self.options.collapse_vars && !self.options.reduce_vars {
525            log_abort!("sequences: [x] Disabled");
526            return;
527        }
528
529        if self.ctx.is_top_level_for_block_level_vars() && !self.options.top_level() {
530            log_abort!("sequences: [x] Top level");
531            return;
532        }
533
534        if self
535            .data
536            .scopes
537            .get(&self.ctx.scope)
538            .unwrap()
539            .contains(ScopeData::HAS_EVAL_CALL)
540        {
541            log_abort!("sequences: Eval call");
542            return;
543        }
544
545        fn is_end(s: Option<&Stmt>) -> bool {
546            matches!(
547                s,
548                Some(
549                    Stmt::If(..)
550                        | Stmt::Throw(..)
551                        | Stmt::Return(..)
552                        | Stmt::Switch(..)
553                        | Stmt::For(..)
554                        | Stmt::ForIn(..)
555                        | Stmt::ForOf(..)
556                ) | None
557            )
558        }
559
560        let mut exprs = Vec::new();
561        let mut buf = Vec::new();
562
563        for stmt in stmts.iter_mut() {
564            let is_end = is_end(stmt.as_stmt());
565            let can_skip = match stmt.as_stmt() {
566                Some(Stmt::Decl(Decl::Fn(..))) => true,
567                _ => false,
568            };
569
570            let items = if let Some(stmt) = stmt.as_stmt_mut() {
571                self.seq_exprs_of(stmt, self.options)
572            } else {
573                None
574            };
575            if let Some(items) = items {
576                buf.extend(items)
577            } else {
578                exprs.push(take(&mut buf));
579
580                if !can_skip {
581                    continue;
582                }
583            }
584            if is_end {
585                exprs.push(take(&mut buf));
586            }
587        }
588
589        if will_terminate {
590            buf.push(Mergable::Drop);
591        }
592        exprs.push(buf);
593
594        #[cfg(feature = "debug")]
595        let _tracing = {
596            let buf_len = exprs.iter().map(|v| v.len()).collect::<Vec<_>>();
597            Some(
598                tracing::span!(
599                    Level::TRACE,
600                    "merge_sequences_in_stmts",
601                    items_len = tracing::field::debug(&buf_len)
602                )
603                .entered(),
604            )
605        };
606
607        for mut exprs in exprs {
608            let _ = self.merge_sequences_in_exprs(&mut exprs);
609        }
610
611        stmts.retain_mut(|stmt| {
612            if let Some(Stmt::Expr(es)) = stmt.as_stmt_mut() {
613                if let Expr::Seq(e) = &mut *es.expr {
614                    e.exprs.retain(|e| !e.is_invalid());
615                    if e.exprs.len() == 1 {
616                        es.expr = e.exprs.pop().unwrap();
617                        return true;
618                    }
619                }
620            }
621
622            match stmt.as_stmt_mut() {
623                Some(Stmt::Decl(Decl::Var(v))) => {
624                    v.decls.retain(|decl| {
625                        // We dropped variable declarations using sequential inlining
626                        if matches!(decl.name, Pat::Invalid(..)) {
627                            return false;
628                        }
629                        !matches!(decl.init.as_deref(), Some(Expr::Invalid(..)))
630                    });
631
632                    !v.decls.is_empty()
633                }
634                Some(Stmt::Decl(Decl::Fn(f))) => !f.ident.is_dummy(),
635                Some(Stmt::Expr(s)) if s.expr.is_invalid() => false,
636
637                _ => true,
638            }
639        });
640    }
641
642    pub(super) fn normalize_sequences(&self, seq: &mut SeqExpr) {
643        for e in &mut seq.exprs {
644            if let Expr::Seq(e) = &mut **e {
645                self.normalize_sequences(&mut *e);
646            }
647        }
648
649        if seq.exprs.iter().any(|v| v.is_seq()) {
650            let mut new = Vec::new();
651
652            for e in seq.exprs.take() {
653                match *e {
654                    Expr::Seq(s) => {
655                        new.extend(s.exprs);
656                    }
657                    _ => new.push(e),
658                }
659            }
660
661            seq.exprs = new;
662        }
663    }
664
665    pub(super) fn merge_sequences_in_seq_expr(&mut self, e: &mut SeqExpr) {
666        self.normalize_sequences(e);
667
668        if self
669            .data
670            .scopes
671            .get(&self.ctx.scope)
672            .unwrap()
673            .contains(ScopeData::HAS_EVAL_CALL)
674        {
675            log_abort!("sequences: Eval call");
676            return;
677        }
678
679        #[cfg(feature = "debug")]
680        let _tracing = {
681            let e_str = dump(&*e, false);
682
683            Some(
684                span!(
685                    Level::ERROR,
686                    "merge_sequences_in_seq_expr",
687                    seq_expr = &*e_str
688                )
689                .entered(),
690            )
691        };
692
693        if !self.options.sequences() && !self.options.collapse_vars && !e.span.is_dummy() {
694            log_abort!("sequences: Disabled && no mark");
695            return;
696        }
697
698        let mut exprs = e
699            .exprs
700            .iter_mut()
701            .map(|e| &mut **e)
702            .map(Mergable::Expr)
703            .collect();
704
705        let _ = self.merge_sequences_in_exprs(&mut exprs);
706
707        // As we don't have Mergable::Var here, we don't need to check for dropped
708        // variables.
709
710        e.exprs.retain(|e| !e.is_invalid());
711    }
712
713    /// Calls `merge_sequential_expr`.
714    ///
715    ///
716    /// TODO(kdy1): Check for side effects and call merge_sequential_expr more
717    /// if expressions between a and b are side-effect-free.
718    fn merge_sequences_in_exprs(&mut self, exprs: &mut Vec<Mergable>) -> Result<(), ()> {
719        #[cfg(feature = "debug")]
720        let _tracing = {
721            Some(
722                tracing::span!(Level::TRACE, "merge_sequences_in_exprs", len = exprs.len())
723                    .entered(),
724            )
725        };
726
727        let mut merge_seq_cache = MergeSequenceCache::new(exprs.len());
728        loop {
729            let mut changed = false;
730            for a_idx in 0..exprs.len().saturating_sub(1) {
731                for b_idx in (a_idx + 1)..exprs.len() {
732                    let (a1, a2) = exprs.split_at_mut(a_idx + 1);
733                    let a = a1.last_mut().unwrap();
734                    let b = &mut a2[b_idx - a_idx - 1];
735
736                    if self.options.unused && self.options.sequences() {
737                        if let (Mergable::Var(av), Mergable::Var(bv)) = (&mut *a, &mut *b) {
738                            // We try dropping variable assignments first.
739
740                            // Currently, we only drop variable declarations if they have the same
741                            // name.
742                            if let (Pat::Ident(an), Pat::Ident(bn)) = (&av.name, &bv.name) {
743                                if an.ctxt == bn.ctxt && an.sym == bn.sym {
744                                    // We need to preserve side effect of `av.init`
745
746                                    match bv.init.as_deref_mut() {
747                                        Some(b_init) => {
748                                            if is_ident_used_by(an, b_init) {
749                                                log_abort!(
750                                                    "We can't duplicated binding because \
751                                                     initializer uses the previous declaration of \
752                                                     the variable"
753                                                );
754                                                break;
755                                            }
756
757                                            if let Some(a_init) = av.init.take() {
758                                                let b_seq = b_init.force_seq();
759                                                b_seq.exprs.insert(0, a_init);
760                                                merge_seq_cache.invalidate(a_idx);
761                                                merge_seq_cache.invalidate(b_idx);
762
763                                                self.changed = true;
764                                                report_change!(
765                                                    "Moving initializer sequentially as they have \
766                                                     a same name"
767                                                );
768                                                av.name.take();
769                                                break;
770                                            } else {
771                                                self.changed = true;
772                                                report_change!(
773                                                    "Dropping the previous var declaration of {} \
774                                                     which does not have an initializer",
775                                                    an.id
776                                                );
777                                                av.name.take();
778                                                break;
779                                            }
780                                        }
781                                        None => {
782                                            // As variable name is same, we can move initializer
783
784                                            // Th code below
785                                            //
786                                            //      var a = 5;
787                                            //      var a;
788                                            //
789                                            //      console.log(a)
790                                            //
791                                            // prints 5
792                                            bv.init = av.init.take();
793                                            merge_seq_cache.invalidate(a_idx);
794                                            merge_seq_cache.invalidate(b_idx);
795                                            self.changed = true;
796                                            report_change!(
797                                                "Moving initializer to the next variable \
798                                                 declaration as they have the same name"
799                                            );
800                                            av.name.take();
801                                            break;
802                                        }
803                                    }
804                                }
805                            }
806                        }
807                    }
808
809                    // Merge sequentially
810
811                    match b {
812                        Mergable::Var(b) => {
813                            match b.init.as_deref_mut() {
814                                Some(b) => {
815                                    if !merge_seq_cache.is_top_retain(self, a, a_idx)
816                                        && self.merge_sequential_expr(a, b)?
817                                    {
818                                        changed = true;
819                                        merge_seq_cache.invalidate(a_idx);
820                                        merge_seq_cache.invalidate(b_idx);
821                                        break;
822                                    }
823                                }
824                                None => {
825                                    if let Mergable::Expr(Expr::Assign(a_exp)) = a {
826                                        if let (Some(a_id), Some(b_id)) =
827                                            (a_exp.left.as_ident(), b.name.as_ident())
828                                        {
829                                            if a_id.id.eq_ignore_span(&b_id.id)
830                                            && a_exp.op == op!("=")
831                                            && self
832                                                .data
833                                                .vars
834                                                .get(&a_id.id.to_id())
835                                                .map(|u| {
836                                                    !u.flags.intersects(
837                                                        VarUsageInfoFlags::INLINE_PREVENTED.union(VarUsageInfoFlags::DECLARED_AS_FN_EXPR)
838                                                    )
839                                                })
840                                                .unwrap_or(false)
841                                        {
842                                            changed = true;
843                                            report_change!("merge assign and var decl");
844                                            b.init = Some(a_exp.right.take());
845                                            merge_seq_cache.invalidate(a_idx);
846                                            merge_seq_cache.invalidate(b_idx);
847
848                                            if let Mergable::Expr(e) = a {
849                                                e.take();
850                                            }
851
852                                            break;
853                                        }
854                                        }
855                                    }
856
857                                    continue;
858                                }
859                            }
860                        }
861                        Mergable::Expr(b) => {
862                            if !merge_seq_cache.is_top_retain(self, a, a_idx)
863                                && self.merge_sequential_expr(a, b)?
864                            {
865                                changed = true;
866                                merge_seq_cache.invalidate(a_idx);
867                                merge_seq_cache.invalidate(b_idx);
868                                break;
869                            }
870                        }
871                        Mergable::FnDecl(..) => continue,
872                        Mergable::Drop => {
873                            if self.drop_mergable_seq(a)? {
874                                changed = true;
875                                merge_seq_cache.invalidate(a_idx);
876                                merge_seq_cache.invalidate(b_idx);
877                                break;
878                            }
879                        }
880                    }
881
882                    // This logic is required to handle
883                    //
884                    // var b;
885                    // (function () {
886                    //     function f() {
887                    //         a++;
888                    //     }
889                    //     f();
890                    //     var c = f();
891                    //     var a = void 0;
892                    //     c || (b = a);
893                    // })();
894                    // console.log(b);
895                    //
896                    //
897                    // at the code above, c cannot be shifted to `c` in `c || (b = a)`
898                    //
899
900                    match a {
901                        Mergable::Var(VarDeclarator {
902                            init: Some(init), ..
903                        }) => {
904                            if !self.is_skippable_for_seq(None, init) {
905                                break;
906                            }
907                        }
908                        Mergable::Expr(Expr::Assign(a)) => {
909                            if let Some(a) = a.left.as_simple() {
910                                if !self.is_simple_assign_target_skippable_for_seq(None, a) {
911                                    break;
912                                }
913                            }
914
915                            if !self.is_skippable_for_seq(None, &a.right) {
916                                break;
917                            }
918                        }
919
920                        _ => {}
921                    }
922
923                    match b {
924                        Mergable::Var(e2) => {
925                            if let Some(e2) = &e2.init {
926                                if !self.is_skippable_for_seq(Some(a), e2) {
927                                    break;
928                                }
929                            }
930
931                            if let Some(id) = a.ident() {
932                                if merge_seq_cache.is_ident_used_by(id, &**e2, b_idx) {
933                                    break;
934                                }
935                            }
936                        }
937                        Mergable::Expr(e2) => {
938                            if !self.is_skippable_for_seq(Some(a), e2) {
939                                break;
940                            }
941
942                            if let Some(id) = a.ident() {
943                                if merge_seq_cache.is_ident_used_by(id, &**e2, b_idx) {
944                                    break;
945                                }
946                            }
947                        }
948
949                        // Function declaration is side-effect free.
950                        //
951                        // TODO(kdy1): Paramters with default value can have side effect. But this
952                        // is very unrealistic in real-world code, so I'm
953                        // postponing handling for it.
954                        Mergable::FnDecl(f) => {
955                            if f.function
956                                .params
957                                .iter()
958                                .any(|p| !self.is_pat_skippable_for_seq(Some(a), &p.pat))
959                            {
960                                break;
961                            }
962                        }
963
964                        Mergable::Drop => break,
965                    }
966                }
967            }
968
969            if !changed {
970                break;
971            }
972        }
973
974        Ok(())
975    }
976
977    fn is_pat_skippable_for_seq(&mut self, a: Option<&Mergable>, p: &Pat) -> bool {
978        match p {
979            Pat::Ident(_) => true,
980            Pat::Invalid(_) => false,
981
982            Pat::Array(p) => {
983                for elem in p.elems.iter().flatten() {
984                    if !self.is_pat_skippable_for_seq(a, elem) {
985                        return false;
986                    }
987                }
988
989                true
990            }
991            Pat::Rest(p) => {
992                if !self.is_pat_skippable_for_seq(a, &p.arg) {
993                    return false;
994                }
995
996                true
997            }
998            Pat::Object(p) => {
999                for prop in &p.props {
1000                    match prop {
1001                        ObjectPatProp::KeyValue(KeyValuePatProp { value, key, .. }) => {
1002                            if let PropName::Computed(key) = key {
1003                                if !self.is_skippable_for_seq(a, &key.expr) {
1004                                    return false;
1005                                }
1006                            }
1007
1008                            if !self.is_pat_skippable_for_seq(a, value) {
1009                                return false;
1010                            }
1011                        }
1012                        ObjectPatProp::Assign(AssignPatProp { .. }) => return false,
1013                        ObjectPatProp::Rest(RestPat { arg, .. }) => {
1014                            if !self.is_pat_skippable_for_seq(a, arg) {
1015                                return false;
1016                            }
1017                        }
1018                        #[cfg(swc_ast_unknown)]
1019                        _ => panic!("unable to access unknown nodes"),
1020                    }
1021                }
1022
1023                true
1024            }
1025            Pat::Assign(..) => false,
1026            Pat::Expr(e) => self.is_skippable_for_seq(a, e),
1027            #[cfg(swc_ast_unknown)]
1028            _ => panic!("unable to access unknown nodes"),
1029        }
1030    }
1031
1032    fn drop_mergable_seq(&mut self, a: &mut Mergable) -> Result<bool, ()> {
1033        if let Mergable::Expr(a) = a {
1034            if self.optimize_last_expr_before_termination(a) {
1035                return Ok(true);
1036            }
1037        }
1038
1039        Ok(false)
1040    }
1041
1042    fn is_simple_assign_target_skippable_for_seq(
1043        &self,
1044        a: Option<&Mergable>,
1045        e: &SimpleAssignTarget,
1046    ) -> bool {
1047        match e {
1048            SimpleAssignTarget::Ident(e) => self.is_ident_skippable_for_seq(a, &Ident::from(e)),
1049            SimpleAssignTarget::Member(e) => self.is_member_expr_skippable_for_seq(a, e),
1050            _ => false,
1051        }
1052    }
1053
1054    fn is_ident_skippable_for_seq(&self, a: Option<&Mergable>, e: &Ident) -> bool {
1055        if e.ctxt == self.ctx.expr_ctx.unresolved_ctxt
1056            && self.options.pristine_globals
1057            && is_global_var_with_pure_property_access(&e.sym)
1058        {
1059            return true;
1060        }
1061
1062        if let Some(a) = a {
1063            match a {
1064                Mergable::Var(a) => {
1065                    if is_ident_used_by(e, &a.init) {
1066                        log_abort!("ident used by a (var)");
1067                        return false;
1068                    }
1069                }
1070                Mergable::Expr(a) => {
1071                    if is_ident_used_by(e, &**a) {
1072                        log_abort!("ident used by a (expr)");
1073                        return false;
1074                    }
1075                }
1076
1077                Mergable::FnDecl(a) => {
1078                    // TODO(kdy1): I'm not sure if we can remove this check. I added this
1079                    // just to be safe, and we may remove this check in future.
1080                    if is_ident_used_by(e, &**a) {
1081                        log_abort!("ident used by a (fn)");
1082                        return false;
1083                    }
1084                }
1085
1086                Mergable::Drop => return false,
1087            }
1088
1089            let ids_used_by_a_init = match a {
1090                Mergable::Var(a) => a.init.as_ref().map(|init| {
1091                    try_collect_infects_from(
1092                        init,
1093                        AliasConfig::default()
1094                            .marks(Some(self.marks))
1095                            .ignore_nested(true)
1096                            .need_all(true),
1097                        8,
1098                    )
1099                }),
1100                Mergable::Expr(a) => match a {
1101                    Expr::Assign(a) if a.is_simple_assign() => Some(try_collect_infects_from(
1102                        &a.right,
1103                        AliasConfig::default()
1104                            .marks(Some(self.marks))
1105                            .ignore_nested(true)
1106                            .need_all(true),
1107                        8,
1108                    )),
1109
1110                    _ => None,
1111                },
1112
1113                Mergable::FnDecl(a) => Some(try_collect_infects_from(
1114                    &a.function,
1115                    AliasConfig::default()
1116                        .marks(Some(self.marks))
1117                        .ignore_nested(true)
1118                        .need_all(true),
1119                    8,
1120                )),
1121
1122                Mergable::Drop => return false,
1123            };
1124
1125            if let Some(deps) = ids_used_by_a_init {
1126                let Ok(deps) = deps else {
1127                    return false;
1128                };
1129
1130                if deps.contains(&(e.to_id(), AccessKind::Reference))
1131                    || deps.contains(&(e.to_id(), AccessKind::Call))
1132                {
1133                    return false;
1134                }
1135            }
1136
1137            if !self.assignee_skippable_for_seq(a, e) {
1138                return false;
1139            }
1140        }
1141
1142        true
1143    }
1144
1145    fn is_member_expr_skippable_for_seq(
1146        &self,
1147        a: Option<&Mergable>,
1148        MemberExpr { obj, prop, .. }: &MemberExpr,
1149    ) -> bool {
1150        if !self.is_skippable_for_seq(a, obj) {
1151            return false;
1152        }
1153
1154        if !self.should_preserve_property_access(
1155            obj,
1156            PropertyAccessOpts {
1157                allow_getter: false,
1158                only_ident: false,
1159            },
1160        ) {
1161            if let MemberProp::Computed(prop) = prop {
1162                if !self.is_skippable_for_seq(a, &prop.expr) {
1163                    return false;
1164                }
1165            }
1166
1167            return true;
1168        }
1169
1170        false
1171    }
1172
1173    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
1174    fn is_skippable_for_seq(&self, a: Option<&Mergable>, e: &Expr) -> bool {
1175        if self.ctx.bit_ctx.contains(BitCtx::InTryBlock) {
1176            log_abort!("try block");
1177            return false;
1178        }
1179
1180        trace_op!("is_skippable_for_seq");
1181
1182        match e {
1183            Expr::Ident(e) => self.is_ident_skippable_for_seq(a, e),
1184
1185            Expr::Member(me) => self.is_member_expr_skippable_for_seq(a, me),
1186
1187            Expr::Lit(..) => true,
1188
1189            Expr::Yield(..) | Expr::Await(..) => false,
1190
1191            Expr::Tpl(t) => t.exprs.iter().all(|e| self.is_skippable_for_seq(a, e)),
1192
1193            Expr::TaggedTpl(t) => {
1194                self.is_skippable_for_seq(a, &t.tag)
1195                    && t.tpl.exprs.iter().all(|e| self.is_skippable_for_seq(a, e))
1196            }
1197
1198            Expr::Unary(UnaryExpr {
1199                op: op!("!") | op!("void") | op!("typeof") | op!(unary, "-") | op!(unary, "+"),
1200                arg,
1201                ..
1202            }) => self.is_skippable_for_seq(a, arg),
1203
1204            Expr::Bin(BinExpr { left, right, .. }) => {
1205                self.is_skippable_for_seq(a, left) && self.is_skippable_for_seq(a, right)
1206            }
1207
1208            Expr::Cond(CondExpr {
1209                test, cons, alt, ..
1210            }) => {
1211                self.is_skippable_for_seq(a, test)
1212                    && self.is_skippable_for_seq(a, cons)
1213                    && self.is_skippable_for_seq(a, alt)
1214            }
1215
1216            Expr::Assign(e) => {
1217                let left_id = e.left.as_ident();
1218                let left_id = match left_id {
1219                    Some(v) => v,
1220                    _ => {
1221                        log_abort!("e.left is not ident");
1222                        return false;
1223                    }
1224                };
1225
1226                if let Some(a) = a {
1227                    match a {
1228                        Mergable::Var(a) => {
1229                            if is_ident_used_by(left_id, &**a) {
1230                                log_abort!("e.left is used by a (var)");
1231                                return false;
1232                            }
1233                        }
1234                        Mergable::Expr(a) => {
1235                            if is_ident_used_by(left_id, &**a) {
1236                                log_abort!("e.left is used by a (expr)");
1237                                return false;
1238                            }
1239                        }
1240                        Mergable::FnDecl(a) => {
1241                            // TODO(kdy1): I'm not sure if this check is required.
1242                            if is_ident_used_by(left_id, &**a) {
1243                                log_abort!("e.left is used by a ()");
1244                                return false;
1245                            }
1246                        }
1247                        Mergable::Drop => return false,
1248                    }
1249                }
1250
1251                if !self.is_skippable_for_seq(a, &left_id.id.clone().into()) {
1252                    return false;
1253                }
1254
1255                if let Expr::Lit(..) = &*e.right {
1256                    return true;
1257                }
1258
1259                if contains_this_expr(&*e.right) {
1260                    log_abort!("e.right contains this");
1261                    return false;
1262                }
1263
1264                let used_ids = idents_used_by(&*e.right);
1265                if used_ids.is_empty() {
1266                    return true;
1267                }
1268
1269                if used_ids.len() != 1 || !used_ids.contains(&left_id.to_id()) {
1270                    log_abort!("bad used_ids");
1271                    return false;
1272                }
1273
1274                self.is_skippable_for_seq(a, &e.right)
1275            }
1276
1277            Expr::Object(e) => {
1278                if e.props.is_empty() {
1279                    return true;
1280                }
1281
1282                for p in &e.props {
1283                    match p {
1284                        PropOrSpread::Spread(_) => return false,
1285                        PropOrSpread::Prop(p) => match &**p {
1286                            Prop::Shorthand(i) => {
1287                                if !self.is_skippable_for_seq(a, &i.clone().into()) {
1288                                    return false;
1289                                }
1290                            }
1291                            Prop::KeyValue(kv) => {
1292                                if let PropName::Computed(key) = &kv.key {
1293                                    if !self.is_skippable_for_seq(a, &key.expr) {
1294                                        return false;
1295                                    }
1296                                }
1297
1298                                if !self.is_skippable_for_seq(a, &kv.value) {
1299                                    return false;
1300                                }
1301                            }
1302                            Prop::Assign(_) => {
1303                                log_abort!("assign property");
1304                                return false;
1305                            }
1306                            _ => {
1307                                log_abort!("handler is not implemented for this kind of property");
1308                                return false;
1309                            }
1310                        },
1311                        #[cfg(swc_ast_unknown)]
1312                        _ => panic!("unable to access unknown nodes"),
1313                    }
1314                }
1315
1316                // TODO: Check for side effects in object properties.
1317
1318                true
1319            }
1320
1321            Expr::Array(e) => {
1322                for elem in e.elems.iter().flatten() {
1323                    if !self.is_skippable_for_seq(a, &elem.expr) {
1324                        log_abort!("array element");
1325                        return false;
1326                    }
1327                }
1328
1329                true
1330            }
1331
1332            Expr::Call(e) => {
1333                if e.args.is_empty() {
1334                    if let Callee::Expr(callee) = &e.callee {
1335                        if let Expr::Fn(callee) = &**callee {
1336                            let ids = idents_used_by(&callee.function);
1337
1338                            if ids
1339                                .iter()
1340                                .all(|id| id.1.outer() == self.marks.unresolved_mark)
1341                            {
1342                                return true;
1343                            }
1344                        }
1345                    }
1346                }
1347
1348                if let Callee::Expr(callee) = &e.callee {
1349                    if callee.is_pure_callee(self.ctx.expr_ctx) {
1350                        if !self.is_skippable_for_seq(a, callee) {
1351                            return false;
1352                        }
1353
1354                        for arg in &e.args {
1355                            if !self.is_skippable_for_seq(a, &arg.expr) {
1356                                return false;
1357                            }
1358                        }
1359
1360                        return true;
1361                    }
1362                }
1363
1364                false
1365            }
1366
1367            Expr::Seq(SeqExpr { exprs, .. }) => {
1368                exprs.iter().all(|e| self.is_skippable_for_seq(a, e))
1369            }
1370
1371            Expr::New(..) => {
1372                // TODO(kdy1): We can optimize some known calls.
1373
1374                false
1375            }
1376
1377            // Expressions without any effects
1378            Expr::This(_)
1379            | Expr::Fn(_)
1380            | Expr::MetaProp(_)
1381            | Expr::Arrow(_)
1382            | Expr::PrivateName(_) => true,
1383
1384            Expr::Update(..) => false,
1385            Expr::SuperProp(..) => false,
1386            Expr::Class(_) => e.may_have_side_effects(self.ctx.expr_ctx),
1387
1388            Expr::Paren(e) => self.is_skippable_for_seq(a, &e.expr),
1389            Expr::Unary(e) => self.is_skippable_for_seq(a, &e.arg),
1390
1391            Expr::OptChain(OptChainExpr { base, .. }) => match &**base {
1392                OptChainBase::Member(e) => {
1393                    if !self.should_preserve_property_access(
1394                        &e.obj,
1395                        PropertyAccessOpts {
1396                            allow_getter: false,
1397                            only_ident: false,
1398                        },
1399                    ) {
1400                        if let MemberProp::Computed(prop) = &e.prop {
1401                            if !self.is_skippable_for_seq(a, &prop.expr) {
1402                                return false;
1403                            }
1404                        }
1405
1406                        return true;
1407                    }
1408
1409                    false
1410                }
1411                OptChainBase::Call(e) => {
1412                    if e.callee.is_pure_callee(self.ctx.expr_ctx) {
1413                        if !self.is_skippable_for_seq(a, &e.callee) {
1414                            return false;
1415                        }
1416
1417                        for arg in &e.args {
1418                            if !self.is_skippable_for_seq(a, &arg.expr) {
1419                                return false;
1420                            }
1421                        }
1422
1423                        return true;
1424                    }
1425
1426                    false
1427                }
1428                #[cfg(swc_ast_unknown)]
1429                _ => panic!("unable to access unknown nodes"),
1430            },
1431
1432            Expr::Invalid(_) => true,
1433
1434            Expr::JSXMember(_)
1435            | Expr::JSXNamespacedName(_)
1436            | Expr::JSXEmpty(_)
1437            | Expr::JSXElement(_)
1438            | Expr::JSXFragment(_)
1439            | Expr::TsTypeAssertion(_)
1440            | Expr::TsConstAssertion(_)
1441            | Expr::TsNonNull(_)
1442            | Expr::TsAs(_)
1443            | Expr::TsInstantiation(_)
1444            | Expr::TsSatisfies(_) => false,
1445
1446            #[cfg(swc_ast_unknown)]
1447            _ => panic!("unable to access unknown nodes"),
1448        }
1449    }
1450
1451    fn assignee_skippable_for_seq(&self, a: &Mergable, assignee: &Ident) -> bool {
1452        let usgae = if let Some(usage) = self.data.vars.get(&assignee.to_id()) {
1453            usage
1454        } else {
1455            return false;
1456        };
1457        match a {
1458            Mergable::Expr(a) => {
1459                let has_side_effect = match a {
1460                    Expr::Assign(a) if a.is_simple_assign() => {
1461                        a.right.may_have_side_effects(self.ctx.expr_ctx)
1462                    }
1463                    _ => a.may_have_side_effects(self.ctx.expr_ctx),
1464                };
1465                if has_side_effect
1466                    && !usgae.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
1467                    && (usgae.flags.intersects(
1468                        VarUsageInfoFlags::EXPORTED.union(VarUsageInfoFlags::REASSIGNED),
1469                    ))
1470                {
1471                    log_abort!("a (expr) has side effect");
1472                    return false;
1473                }
1474            }
1475            Mergable::Var(a) => {
1476                if let Some(init) = &a.init {
1477                    if init.may_have_side_effects(self.ctx.expr_ctx)
1478                        && !usgae.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
1479                        && (usgae.flags.intersects(
1480                            VarUsageInfoFlags::EXPORTED.union(VarUsageInfoFlags::REASSIGNED),
1481                        ))
1482                    {
1483                        log_abort!("a (var) init has side effect");
1484                        return false;
1485                    }
1486                }
1487            }
1488            Mergable::FnDecl(_) => (),
1489            Mergable::Drop => return false,
1490        }
1491
1492        true
1493    }
1494
1495    /// Returns true if something is modified.
1496    ///
1497    /// Returns [Err] iff we should stop checking.
1498    fn merge_sequential_expr(&mut self, a: &mut Mergable, b: &mut Expr) -> Result<bool, ()> {
1499        #[cfg(feature = "debug")]
1500        let _tracing = {
1501            let b_str = dump(&*b, false);
1502            let a = match a {
1503                Mergable::Expr(e) => dump(*e, false),
1504                Mergable::Var(e) => dump(*e, false),
1505                Mergable::FnDecl(e) => dump(*e, false),
1506                Mergable::Drop => unreachable!(),
1507            };
1508
1509            Some(
1510                span!(
1511                    Level::ERROR,
1512                    "merge_sequential_expr",
1513                    a = tracing::field::debug(&a),
1514                    b = &*b_str
1515                )
1516                .entered(),
1517            )
1518        };
1519
1520        match &*b {
1521            Expr::Arrow(..)
1522            | Expr::Fn(..)
1523            | Expr::Class(..)
1524            | Expr::Lit(..)
1525            | Expr::Await(..)
1526            | Expr::Yield(..)
1527            | Expr::Tpl(..)
1528            | Expr::TaggedTpl(..)
1529            | Expr::JSXElement(..)
1530            | Expr::JSXEmpty(..)
1531            | Expr::JSXFragment(..)
1532            | Expr::JSXNamespacedName(..)
1533            | Expr::JSXMember(..) => return Ok(false),
1534
1535            // See https://github.com/swc-project/swc/issues/8924 and https://github.com/swc-project/swc/issues/8942
1536            Expr::Assign(AssignExpr {
1537                op: op!("**="),
1538                right,
1539                ..
1540            })
1541            | Expr::Bin(BinExpr {
1542                op: op!("**"),
1543                right,
1544                ..
1545            }) => {
1546                if !right.is_lit() {
1547                    return Ok(false);
1548                }
1549            }
1550
1551            Expr::Unary(UnaryExpr {
1552                op: op!("delete"), ..
1553            }) => return Ok(false),
1554            _ => {}
1555        }
1556
1557        match a {
1558            Mergable::Var(..) | Mergable::FnDecl(..) => {}
1559            Mergable::Expr(a) => {
1560                if a.is_ident() {
1561                    return Ok(false);
1562                }
1563
1564                if let Expr::Seq(a) = a {
1565                    for a in a.exprs.iter_mut().rev() {
1566                        if self.merge_sequential_expr(&mut Mergable::Expr(a), b)? {
1567                            return Ok(true);
1568                        }
1569
1570                        if !self.is_skippable_for_seq(None, a) {
1571                            return Ok(false);
1572                        }
1573
1574                        if a.may_have_side_effects(self.ctx.expr_ctx) {
1575                            return Ok(false);
1576                        }
1577                    }
1578
1579                    return Ok(false);
1580                }
1581            }
1582            Mergable::Drop => return Ok(false),
1583        }
1584
1585        {
1586            // Fast path, before digging into `b`
1587
1588            match a {
1589                Mergable::Var(a) => {
1590                    // We only inline identifiers
1591                    if !a.name.is_ident() {
1592                        return Ok(false);
1593                    }
1594                }
1595                Mergable::Expr(a) => match a {
1596                    Expr::Assign(a) => {
1597                        // We only inline identifiers
1598                        if a.left.as_ident().is_none() {
1599                            return Ok(false);
1600                        }
1601                    }
1602
1603                    // We don't handle this currently, but we will.
1604                    Expr::Update(a) => {
1605                        if !a.arg.is_ident() {
1606                            return Ok(false);
1607                        }
1608                    }
1609
1610                    _ => {
1611                        // if a is not a modification, we can skip it
1612                        return Ok(false);
1613                    }
1614                },
1615
1616                Mergable::FnDecl(..) => {
1617                    // A function declaration is always inlinable as it can be
1618                    // viewed as a variable with an identifier name and a
1619                    // function expression as a initialized.
1620                }
1621
1622                Mergable::Drop => return Ok(false),
1623            }
1624        }
1625
1626        match b {
1627            Expr::Update(..) | Expr::Arrow(..) | Expr::Fn(..) | Expr::OptChain(..) => {
1628                return Ok(false)
1629            }
1630
1631            Expr::Cond(b) => {
1632                trace_op!("seq: Try test of cond");
1633                return self.merge_sequential_expr(a, &mut b.test);
1634            }
1635
1636            Expr::Unary(b) => {
1637                trace_op!("seq: Try arg of unary");
1638                return self.merge_sequential_expr(a, &mut b.arg);
1639            }
1640
1641            Expr::Bin(BinExpr {
1642                op, left, right, ..
1643            }) => {
1644                trace_op!("seq: Try left of bin");
1645                if self.merge_sequential_expr(a, left)? {
1646                    return Ok(true);
1647                }
1648
1649                if !self.is_skippable_for_seq(Some(a), left) {
1650                    return Ok(false);
1651                }
1652
1653                if op.may_short_circuit() {
1654                    return Ok(false);
1655                }
1656
1657                trace_op!("seq: Try right of bin");
1658                return self.merge_sequential_expr(a, right);
1659            }
1660
1661            Expr::Member(MemberExpr { obj, prop, .. }) if !prop.is_computed() => {
1662                trace_op!("seq: Try object of member");
1663                return self.merge_sequential_expr(a, obj);
1664            }
1665
1666            Expr::Member(MemberExpr {
1667                obj,
1668                prop: MemberProp::Computed(c),
1669                ..
1670            }) => {
1671                trace_op!("seq: Try object of member (computed)");
1672                if self.merge_sequential_expr(a, obj)? {
1673                    return Ok(true);
1674                }
1675
1676                if obj.may_have_side_effects(self.ctx.expr_ctx) {
1677                    return Ok(false);
1678                }
1679
1680                // We can't merge into `[]` in some cases because `obj` is **resolved** before
1681                // evaluating `[]`.
1682                //
1683                // See https://github.com/swc-project/swc/pull/6509
1684
1685                let obj_ids = idents_used_by_ignoring_nested(obj);
1686                let a_ids = match a {
1687                    Mergable::Var(a) => idents_used_by_ignoring_nested(&a.init),
1688                    Mergable::Expr(a) => idents_used_by_ignoring_nested(&**a),
1689                    Mergable::FnDecl(a) => idents_used_by_ignoring_nested(&**a),
1690                    Mergable::Drop => return Ok(false),
1691                };
1692                if !obj_ids.is_disjoint(&a_ids) {
1693                    return Ok(false);
1694                }
1695
1696                trace_op!("seq: Try prop of member (computed)");
1697                return self.merge_sequential_expr(a, &mut c.expr);
1698            }
1699
1700            Expr::SuperProp(SuperPropExpr {
1701                prop: SuperProp::Computed(c),
1702                ..
1703            }) => {
1704                trace_op!("seq: Try prop of member (computed)");
1705                return self.merge_sequential_expr(a, &mut c.expr);
1706            }
1707
1708            Expr::Assign(b_assign @ AssignExpr { op: op!("="), .. }) => {
1709                match &mut b_assign.left {
1710                    AssignTarget::Simple(b_left) => {
1711                        trace_op!("seq: Try lhs of assign");
1712
1713                        if let SimpleAssignTarget::Member(..) = b_left {
1714                            let mut b_left_expr: Box<Expr> = b_left.take().into();
1715
1716                            let res = self.merge_sequential_expr(a, &mut b_left_expr);
1717
1718                            b_assign.left = match AssignTarget::try_from(b_left_expr) {
1719                                Ok(v) => v,
1720                                Err(b_left_expr) => {
1721                                    if is_pure_undefined(self.ctx.expr_ctx, &b_left_expr) {
1722                                        *b = *b_assign.right.take();
1723                                        return Ok(true);
1724                                    }
1725
1726                                    unreachable!("{b_left_expr:#?}")
1727                                }
1728                            };
1729                            if res? {
1730                                return Ok(true);
1731                            }
1732
1733                            let AssignTarget::Simple(SimpleAssignTarget::Member(b_left)) =
1734                                &b_assign.left
1735                            else {
1736                                return Err(());
1737                            };
1738
1739                            if let Some(left_obj) = b_left.obj.as_ident() {
1740                                if let Some(usage) = self.data.vars.get(&left_obj.to_id()) {
1741                                    if left_obj.ctxt != self.ctx.expr_ctx.unresolved_ctxt
1742                                        && !usage
1743                                            .flags
1744                                            .contains(VarUsageInfoFlags::INLINE_PREVENTED)
1745                                        && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
1746                                        && !b_left.prop.is_computed()
1747                                    {
1748                                        match &*a {
1749                                            Mergable::Var(a) => {
1750                                                if is_ident_used_by(left_obj, &**a) {
1751                                                    return Ok(false);
1752                                                }
1753                                            }
1754                                            Mergable::Expr(a) => {
1755                                                if is_ident_used_by(left_obj, &**a) {
1756                                                    return Ok(false);
1757                                                }
1758                                            }
1759                                            Mergable::FnDecl(a) => {
1760                                                if is_ident_used_by(left_obj, &**a) {
1761                                                    return Ok(false);
1762                                                }
1763                                            }
1764                                            Mergable::Drop => return Ok(false),
1765                                        }
1766
1767                                        return self.merge_sequential_expr(a, &mut b_assign.right);
1768                                    }
1769                                }
1770                            }
1771                        }
1772
1773                        if b_assign.left.as_ident().is_none() {
1774                            return Ok(false);
1775                        }
1776                    }
1777
1778                    _ => return Ok(false),
1779                };
1780
1781                if self.should_not_check_rhs_of_assign(a, b_assign) {
1782                    return Ok(false);
1783                }
1784
1785                trace_op!("seq: Try rhs of assign");
1786                return self.merge_sequential_expr(a, &mut b_assign.right);
1787            }
1788
1789            Expr::Assign(b_assign) => {
1790                if self.should_not_check_rhs_of_assign(a, b_assign) {
1791                    return Ok(false);
1792                }
1793
1794                let b_left = b_assign.left.as_ident();
1795                let b_left = if let Some(v) = b_left {
1796                    v.clone()
1797                } else {
1798                    return Ok(false);
1799                };
1800
1801                if !self.is_skippable_for_seq(Some(a), &b_left.id.clone().into()) {
1802                    // Let's be safe
1803                    if is_ident_used_by(&b_left.id, &b_assign.right) {
1804                        return Ok(false);
1805                    }
1806
1807                    // As we are not *skipping* lhs, we can inline here
1808                    if let Some(a_id) = a.ident() {
1809                        if a_id.ctxt == b_left.ctxt && a_id.sym == b_left.sym {
1810                            if self.replace_seq_assignment(a, b)? {
1811                                return Ok(true);
1812                            }
1813                        }
1814                    }
1815
1816                    return Ok(false);
1817                }
1818
1819                if is_ident_used_by(&b_left.id, &b_assign.right) {
1820                    return Err(());
1821                }
1822
1823                if let Some(a_id) = a.ident() {
1824                    if a_id.ctxt == b_left.ctxt && a_id.sym == b_left.sym {
1825                        if self.replace_seq_assignment(a, b)? {
1826                            return Ok(true);
1827                        }
1828                    }
1829                }
1830
1831                // Hack for lifetime of mutable borrow
1832                match b {
1833                    Expr::Assign(b) => {
1834                        trace_op!("seq: Try rhs of assign with op");
1835                        return self.merge_sequential_expr(a, &mut b.right);
1836                    }
1837                    _ => unreachable!(),
1838                }
1839            }
1840
1841            Expr::Array(b) => {
1842                for elem in b.elems.iter_mut().flatten() {
1843                    trace_op!("seq: Try element of array");
1844                    if self.merge_sequential_expr(a, &mut elem.expr)? {
1845                        return Ok(true);
1846                    }
1847
1848                    if !self.is_skippable_for_seq(Some(a), &elem.expr) {
1849                        // To preserve side-effects, we need to abort.
1850                        break;
1851                    }
1852                }
1853
1854                return Ok(false);
1855            }
1856
1857            Expr::Call(CallExpr {
1858                callee: Callee::Expr(b_callee),
1859                args: b_args,
1860                ..
1861            }) => {
1862                let is_this_undefined = b_callee.is_ident();
1863                trace_op!("seq: Try callee of call");
1864
1865                if let Expr::Member(MemberExpr { obj, .. }) = &**b_callee {
1866                    if let Expr::Ident(obj) = &**obj {
1867                        if let Mergable::Expr(Expr::Update(UpdateExpr { arg, .. })) = a {
1868                            if let Expr::Ident(arg) = &**arg {
1869                                if arg.ctxt == obj.ctxt && arg.sym == obj.sym {
1870                                    return Ok(false);
1871                                }
1872                            }
1873                        }
1874                    }
1875                }
1876
1877                if self.merge_sequential_expr(a, b_callee)? {
1878                    if is_this_undefined {
1879                        if let Expr::Member(..) = &**b_callee {
1880                            let zero = Lit::Num(Number {
1881                                span: DUMMY_SP,
1882                                value: 0.0,
1883                                raw: None,
1884                            })
1885                            .into();
1886                            report_change!("injecting zero to preserve `this` in call");
1887
1888                            *b_callee = SeqExpr {
1889                                span: b_callee.span(),
1890                                exprs: vec![zero, b_callee.take()],
1891                            }
1892                            .into();
1893                        }
1894                    }
1895
1896                    return Ok(true);
1897                }
1898
1899                if !self.is_skippable_for_seq(Some(a), b_callee) {
1900                    return Ok(false);
1901                }
1902
1903                for arg in b_args {
1904                    trace_op!("seq: Try arg of call");
1905                    if self.merge_sequential_expr(a, &mut arg.expr)? {
1906                        return Ok(true);
1907                    }
1908
1909                    if !self.is_skippable_for_seq(Some(a), &arg.expr) {
1910                        return Ok(false);
1911                    }
1912                }
1913
1914                return Ok(false);
1915            }
1916
1917            Expr::New(NewExpr {
1918                callee: b_callee,
1919                args: b_args,
1920                ..
1921            }) => {
1922                trace_op!("seq: Try callee of new");
1923                if self.merge_sequential_expr(a, b_callee)? {
1924                    return Ok(true);
1925                }
1926
1927                if !self.is_skippable_for_seq(Some(a), b_callee) {
1928                    return Ok(false);
1929                }
1930
1931                if let Some(b_args) = b_args {
1932                    for arg in b_args {
1933                        trace_op!("seq: Try arg of new exp");
1934
1935                        if self.merge_sequential_expr(a, &mut arg.expr)? {
1936                            return Ok(true);
1937                        }
1938
1939                        if !self.is_skippable_for_seq(Some(a), &arg.expr) {
1940                            return Ok(false);
1941                        }
1942                    }
1943                }
1944
1945                return Ok(false);
1946            }
1947
1948            Expr::Seq(SeqExpr { exprs: b_exprs, .. }) => {
1949                for b_expr in b_exprs {
1950                    trace_op!("seq: Try elem of seq");
1951
1952                    if self.merge_sequential_expr(a, b_expr)? {
1953                        return Ok(true);
1954                    }
1955
1956                    if !self.is_skippable_for_seq(Some(a), b_expr) {
1957                        return Ok(false);
1958                    }
1959                }
1960
1961                return Ok(false);
1962            }
1963
1964            Expr::Object(ObjectLit { props, .. }) => {
1965                for prop in props {
1966                    match prop {
1967                        PropOrSpread::Spread(prop) => {
1968                            if self.merge_sequential_expr(a, &mut prop.expr)? {
1969                                return Ok(true);
1970                            }
1971
1972                            return Ok(false);
1973                        }
1974                        PropOrSpread::Prop(prop) => {
1975                            // Inline into key
1976                            let computed = match &mut **prop {
1977                                Prop::Shorthand(_) | Prop::Assign(_) => None,
1978                                Prop::KeyValue(prop) => prop.key.as_mut_computed(),
1979                                Prop::Getter(prop) => prop.key.as_mut_computed(),
1980                                Prop::Setter(prop) => prop.key.as_mut_computed(),
1981                                Prop::Method(prop) => prop.key.as_mut_computed(),
1982                                #[cfg(swc_ast_unknown)]
1983                                _ => panic!("unable to access unknown nodes"),
1984                            };
1985
1986                            if let Some(computed) = computed {
1987                                if self.merge_sequential_expr(a, &mut computed.expr)? {
1988                                    return Ok(true);
1989                                }
1990
1991                                if !self.is_skippable_for_seq(Some(a), &computed.expr) {
1992                                    return Ok(false);
1993                                }
1994                            }
1995
1996                            match &mut **prop {
1997                                Prop::Shorthand(shorthand) => {
1998                                    // We can't ignore shorthand properties
1999                                    //
2000                                    // https://github.com/swc-project/swc/issues/6914
2001                                    let mut new_b = shorthand.clone().into();
2002                                    if self.merge_sequential_expr(a, &mut new_b)? {
2003                                        *prop = Box::new(Prop::KeyValue(KeyValueProp {
2004                                            key: Ident::new_no_ctxt(
2005                                                shorthand.sym.clone(),
2006                                                shorthand.span,
2007                                            )
2008                                            .into(),
2009                                            value: new_b.clone().into(),
2010                                        }));
2011                                    }
2012
2013                                    if !self.is_skippable_for_seq(Some(a), &new_b) {
2014                                        return Ok(false);
2015                                    }
2016                                }
2017                                Prop::KeyValue(prop) => {
2018                                    if self.merge_sequential_expr(a, &mut prop.value)? {
2019                                        return Ok(true);
2020                                    }
2021
2022                                    if !self.is_skippable_for_seq(Some(a), &prop.value) {
2023                                        return Ok(false);
2024                                    }
2025                                }
2026                                _ => {}
2027                            }
2028                        }
2029                        #[cfg(swc_ast_unknown)]
2030                        _ => panic!("unable to access unknown nodes"),
2031                    }
2032                }
2033
2034                return Ok(false);
2035            }
2036
2037            _ => {}
2038        }
2039
2040        #[cfg(feature = "debug")]
2041        match a {
2042            Mergable::Var(a) => {
2043                trace_op!(
2044                    "sequences: Trying to merge `{}` => `{}`",
2045                    crate::debug::dump(&**a, false),
2046                    crate::debug::dump(&*b, false)
2047                );
2048            }
2049            Mergable::Expr(a) => {
2050                trace_op!(
2051                    "sequences: Trying to merge `{}` => `{}`",
2052                    crate::debug::dump(&**a, false),
2053                    crate::debug::dump(&*b, false)
2054                );
2055            }
2056
2057            Mergable::FnDecl(a) => {
2058                trace_op!(
2059                    "sequences: Trying to merge `{}` => `{}`",
2060                    crate::debug::dump(&**a, false),
2061                    crate::debug::dump(&*b, false)
2062                );
2063            }
2064            Mergable::Drop => return Ok(false),
2065        }
2066
2067        if self.replace_seq_update(a, b)? {
2068            return Ok(true);
2069        }
2070
2071        if self.replace_seq_assignment(a, b)? {
2072            return Ok(true);
2073        }
2074
2075        Ok(false)
2076    }
2077
2078    /// This requires tracking if `b` is in an assignment pattern.
2079    ///
2080    /// Update expressions can be inline.
2081    ///
2082    /// c++, console.log(c)
2083    ///
2084    /// is same as
2085    ///
2086    /// console.log(++c)
2087    fn replace_seq_update(&mut self, a: &mut Mergable, b: &mut Expr) -> Result<bool, ()> {
2088        if !self.options.sequences() {
2089            return Ok(false);
2090        }
2091
2092        if let Mergable::Expr(a) = a {
2093            match a {
2094                Expr::Update(UpdateExpr {
2095                    op,
2096                    prefix: false,
2097                    arg,
2098                    ..
2099                }) => {
2100                    if let Expr::Ident(a_id) = &**arg {
2101                        if let Some(usage) = self.data.vars.get(&a_id.to_id()) {
2102                            if let Some(VarDeclKind::Const) = usage.var_kind {
2103                                return Err(());
2104                            }
2105                        }
2106
2107                        let mut v = UsageCounter {
2108                            expr_usage: Default::default(),
2109                            pat_usage: Default::default(),
2110                            target: a_id,
2111                            in_lhs: false,
2112                            abort: false,
2113                            in_abort: false,
2114                        };
2115                        b.visit_with(&mut v);
2116                        if v.expr_usage != 1 || v.pat_usage != 0 || v.abort {
2117                            log_abort!(
2118                                "sequences: Aborting merging of an update expression because of \
2119                                 usage counts ({}, ref = {}, pat = {})",
2120                                a_id,
2121                                v.expr_usage,
2122                                v.pat_usage
2123                            );
2124
2125                            return Ok(false);
2126                        }
2127
2128                        let mut replaced = false;
2129                        replace_expr(b, |e| {
2130                            if replaced {
2131                                return;
2132                            }
2133
2134                            if let Expr::Ident(orig_expr) = &*e {
2135                                if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym {
2136                                    replaced = true;
2137                                    *e = UpdateExpr {
2138                                        span: DUMMY_SP,
2139                                        op: *op,
2140                                        prefix: true,
2141                                        arg: orig_expr.clone().into(),
2142                                    }
2143                                    .into();
2144                                    return;
2145                                }
2146                            }
2147
2148                            if let Expr::Update(e @ UpdateExpr { prefix: false, .. }) = e {
2149                                if let Expr::Ident(arg) = &*e.arg {
2150                                    if *op == e.op && arg.ctxt == a_id.ctxt && arg.sym == a_id.sym {
2151                                        e.prefix = true;
2152                                        replaced = true;
2153                                    }
2154                                }
2155                            }
2156                        });
2157                        if replaced {
2158                            self.changed = true;
2159                            report_change!(
2160                                "sequences: Merged update expression into another expression",
2161                            );
2162
2163                            a.take();
2164                            return Ok(true);
2165                        }
2166                    }
2167                }
2168
2169                Expr::Update(UpdateExpr {
2170                    op,
2171                    prefix: true,
2172                    arg,
2173                    ..
2174                }) => {
2175                    if let Expr::Ident(a_id) = &**arg {
2176                        if let Some(usage) = self.data.vars.get(&a_id.to_id()) {
2177                            if let Some(VarDeclKind::Const) = usage.var_kind {
2178                                return Err(());
2179                            }
2180                        }
2181
2182                        let mut v = UsageCounter {
2183                            expr_usage: Default::default(),
2184                            pat_usage: Default::default(),
2185                            target: a_id,
2186                            in_lhs: false,
2187                            abort: false,
2188                            in_abort: false,
2189                        };
2190                        b.visit_with(&mut v);
2191                        if v.expr_usage != 1 || v.pat_usage != 0 || v.abort {
2192                            log_abort!(
2193                                "sequences: Aborting merging of an update expression because of \
2194                                 usage counts ({}, ref = {}, pat = {})",
2195                                a_id,
2196                                v.expr_usage,
2197                                v.pat_usage
2198                            );
2199
2200                            return Ok(false);
2201                        }
2202
2203                        let mut replaced = false;
2204                        replace_expr(b, |e| {
2205                            if replaced {
2206                                return;
2207                            }
2208
2209                            if let Expr::Ident(orig_expr) = &*e {
2210                                if orig_expr.ctxt == a_id.ctxt && orig_expr.sym == a_id.sym {
2211                                    replaced = true;
2212                                    *e = UpdateExpr {
2213                                        span: DUMMY_SP,
2214                                        op: *op,
2215                                        prefix: true,
2216                                        arg: orig_expr.clone().into(),
2217                                    }
2218                                    .into();
2219                                    return;
2220                                }
2221                            }
2222
2223                            if let Expr::Update(e @ UpdateExpr { prefix: false, .. }) = e {
2224                                if let Expr::Ident(arg) = &*e.arg {
2225                                    if *op == e.op && arg.ctxt == a_id.ctxt && arg.sym == a_id.sym {
2226                                        e.prefix = true;
2227                                        replaced = true;
2228                                    }
2229                                }
2230                            }
2231                        });
2232                        if replaced {
2233                            self.changed = true;
2234                            report_change!(
2235                                "sequences: Merged prefix update expression into another \
2236                                 expression",
2237                            );
2238
2239                            a.take();
2240                            return Ok(true);
2241                        }
2242                    }
2243                }
2244
2245                _ => {}
2246            }
2247        }
2248
2249        Ok(false)
2250    }
2251
2252    /// Handle where a: [Expr::Assign] or [Mergable::Var]
2253    fn replace_seq_assignment(&mut self, a: &mut Mergable, b: &mut Expr) -> Result<bool, ()> {
2254        let mut can_remove = false;
2255        let mut can_take_init = false;
2256
2257        let mut right_val;
2258        let (left_id, a_right) = match a {
2259            Mergable::Expr(a) => {
2260                match a {
2261                    Expr::Assign(AssignExpr { left, right, .. }) => {
2262                        // (a = 5, console.log(a))
2263                        //
2264                        // =>
2265                        //
2266                        // (console.log(a = 5))
2267
2268                        let left_id = match left.as_ident() {
2269                            Some(v) => v.id.clone(),
2270                            None => {
2271                                log_abort!("sequences: Aborting because lhs is not an id");
2272                                return Ok(false);
2273                            }
2274                        };
2275
2276                        if let Some(usage) = self.data.vars.get(&left_id.to_id()) {
2277                            if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) {
2278                                return Ok(false);
2279                            }
2280
2281                            // Reassignment to const?
2282                            if let Some(VarDeclKind::Const) = usage.var_kind {
2283                                return Ok(false);
2284                            }
2285
2286                            if usage.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_EXPR) {
2287                                log_abort!(
2288                                    "sequences: Declared as fn expr ({}, {:?})",
2289                                    left_id.sym,
2290                                    left_id.ctxt
2291                                );
2292                                return Ok(false);
2293                            }
2294
2295                            // We can remove this variable same as unused pass
2296                            if usage.usage_count == 1
2297                                && usage.flags.contains(VarUsageInfoFlags::DECLARED)
2298                                && !usage.flags.intersects(
2299                                    VarUsageInfoFlags::USED_RECURSIVELY
2300                                        .union(VarUsageInfoFlags::REASSIGNED),
2301                                )
2302                            {
2303                                can_remove = true;
2304                            }
2305                        } else {
2306                            return Ok(false);
2307                        }
2308
2309                        (left_id, Some(right))
2310                    }
2311                    _ => return Ok(false),
2312                }
2313            }
2314
2315            Mergable::Var(a) => {
2316                let left = match &a.name {
2317                    Pat::Ident(i) => i.id.clone(),
2318                    _ => return Ok(false),
2319                };
2320
2321                if let Some(usage) = self.data.vars.get(&left.to_id()) {
2322                    let is_lit = match a.init.as_deref() {
2323                        Some(e) => is_trivial_lit(e),
2324                        _ => false,
2325                    };
2326
2327                    if usage.ref_count != 1
2328                        || usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
2329                        || !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
2330                    {
2331                        if is_lit {
2332                            can_take_init = false
2333                        } else {
2334                            return Ok(false);
2335                        }
2336                    } else {
2337                        can_take_init = true;
2338                    }
2339
2340                    if usage.flags.intersects(
2341                        VarUsageInfoFlags::INLINE_PREVENTED
2342                            .union(VarUsageInfoFlags::USED_RECURSIVELY),
2343                    ) {
2344                        return Ok(false);
2345                    }
2346
2347                    match &mut a.init {
2348                        Some(v) => (left, Some(v)),
2349                        None => {
2350                            if usage.declared_count > 1 {
2351                                return Ok(false);
2352                            }
2353
2354                            right_val = Expr::undefined(DUMMY_SP);
2355                            (left, Some(&mut right_val))
2356                        }
2357                    }
2358                } else {
2359                    return Ok(false);
2360                }
2361            }
2362
2363            Mergable::FnDecl(a) => {
2364                if let Some(usage) = self.data.vars.get(&a.ident.to_id()) {
2365                    if usage.ref_count != 1
2366                        || usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
2367                        || !usage.flags.contains(VarUsageInfoFlags::IS_FN_LOCAL)
2368                    {
2369                        return Ok(false);
2370                    }
2371
2372                    if usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED) {
2373                        return Ok(false);
2374                    }
2375
2376                    if contains_arguments(&a.function) {
2377                        return Ok(false);
2378                    }
2379
2380                    (a.ident.clone(), None)
2381                } else {
2382                    return Ok(false);
2383                }
2384            }
2385
2386            Mergable::Drop => return Ok(false),
2387        };
2388
2389        let a_type = a_right.as_deref().map(|a| a.get_type(self.ctx.expr_ctx));
2390
2391        if let Some(a_right) = a_right {
2392            if a_right.is_this() || a_right.is_ident_ref_to("arguments") {
2393                return Ok(false);
2394            }
2395            if contains_arguments(&**a_right) {
2396                return Ok(false);
2397            }
2398        }
2399
2400        let take_a = |a: &mut Mergable, force_drop: bool, drop_op| {
2401            match a {
2402                Mergable::Var(a) => {
2403                    if self.options.unused {
2404                        if let Some(usage) = self.data.vars.get(&left_id.to_id()) {
2405                            // We are eliminating one usage, so we use 1 instead of
2406                            // 0
2407                            if !force_drop
2408                                && usage.usage_count == 1
2409                                && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
2410                            {
2411                                report_change!("sequences: Dropping inlined variable");
2412                                a.name.take();
2413                            }
2414                        }
2415                    }
2416
2417                    if can_take_init || force_drop {
2418                        let init = a.init.take();
2419
2420                        if let Some(usage) = self.data.vars.get(&left_id.to_id()) {
2421                            if usage.var_kind == Some(VarDeclKind::Const) {
2422                                a.init = Some(Expr::undefined(DUMMY_SP));
2423                            }
2424                        }
2425
2426                        init
2427                    } else {
2428                        a.init.clone()
2429                    }
2430                    .unwrap_or_else(|| Expr::undefined(DUMMY_SP))
2431                }
2432                Mergable::Expr(a) => {
2433                    if can_remove || force_drop {
2434                        if let Expr::Assign(e) = a {
2435                            if e.op == op!("=") || drop_op {
2436                                report_change!(
2437                                    "sequences: Dropping assignment as we are going to drop the \
2438                                     variable declaration. ({})",
2439                                    left_id
2440                                );
2441
2442                                **a = *e.right.take();
2443                            }
2444                        }
2445                    }
2446
2447                    Box::new(a.take())
2448                }
2449
2450                Mergable::FnDecl(a) => {
2451                    // We can inline a function declaration as a function expression.
2452
2453                    FnExpr {
2454                        ident: Some(a.ident.take()),
2455                        function: a.function.take(),
2456                    }
2457                    .into()
2458                }
2459
2460                Mergable::Drop => {
2461                    unreachable!()
2462                }
2463            }
2464        };
2465
2466        // x = 1, x += 2 => x = 3
2467        match b {
2468            Expr::Assign(b @ AssignExpr { op: op!("="), .. }) => {
2469                if let Some(b_left) = b.left.as_ident() {
2470                    if b_left.ctxt == left_id.ctxt && b_left.sym == left_id.sym {
2471                        report_change!("sequences: Merged assignment into another assignment");
2472                        self.changed = true;
2473
2474                        let mut a_expr = take_a(a, true, false);
2475                        let a_expr = self.ignore_return_value(&mut a_expr);
2476
2477                        if let Some(a) = a_expr {
2478                            b.right = SeqExpr {
2479                                span: DUMMY_SP,
2480                                exprs: vec![Box::new(a), b.right.take()],
2481                            }
2482                            .into();
2483                        }
2484                        return Ok(true);
2485                    }
2486                }
2487            }
2488            Expr::Assign(b) => {
2489                if let Some(b_left) = b.left.as_ident() {
2490                    let a_op = match a {
2491                        Mergable::Var(_) => Some(op!("=")),
2492                        Mergable::Expr(Expr::Assign(AssignExpr { op: a_op, .. })) => Some(*a_op),
2493                        Mergable::FnDecl(_) => Some(op!("=")),
2494                        _ => None,
2495                    };
2496
2497                    let var_type = self
2498                        .data
2499                        .vars
2500                        .get(&left_id.to_id())
2501                        .and_then(|info| info.merged_var_type);
2502                    let Some(a_type) = a_type else {
2503                        return Ok(false);
2504                    };
2505                    let b_type = b.right.get_type(self.ctx.expr_ctx);
2506
2507                    if let Some(a_op) = a_op {
2508                        if can_drop_op_for(a_op, b.op, var_type, a_type, b_type) {
2509                            if b_left.ctxt == left_id.ctxt && b_left.sym == left_id.sym {
2510                                if let Some(bin_op) = b.op.to_update() {
2511                                    report_change!(
2512                                        "sequences: Merged assignment into another (op) assignment"
2513                                    );
2514                                    self.changed = true;
2515
2516                                    b.op = a_op;
2517
2518                                    let to = take_a(a, true, true);
2519
2520                                    b.right = BinExpr {
2521                                        span: DUMMY_SP,
2522                                        op: bin_op,
2523                                        left: to,
2524                                        right: b.right.take(),
2525                                    }
2526                                    .into();
2527                                    return Ok(true);
2528                                }
2529                            }
2530                        }
2531                    }
2532                }
2533            }
2534            _ => {}
2535        }
2536
2537        {
2538            let mut v = UsageCounter {
2539                expr_usage: Default::default(),
2540                pat_usage: Default::default(),
2541                target: &left_id,
2542                in_lhs: false,
2543                abort: false,
2544                in_abort: false,
2545            };
2546            b.visit_with(&mut v);
2547            if v.expr_usage != 1 || v.pat_usage != 0 || v.abort {
2548                log_abort!(
2549                    "sequences: Aborting because of usage counts ({}{:?}, ref = {}, pat = {})",
2550                    left_id.sym,
2551                    left_id.ctxt,
2552                    v.expr_usage,
2553                    v.pat_usage
2554                );
2555
2556                return Ok(false);
2557            }
2558        }
2559
2560        self.changed = true;
2561        report_change!(
2562            "sequences: Inlining sequential expressions (`{}{:?}`)",
2563            left_id.sym,
2564            left_id.ctxt
2565        );
2566
2567        let to = take_a(a, false, false);
2568
2569        replace_id_with_expr(b, left_id.to_id(), to);
2570
2571        if can_remove {
2572            report_change!("sequences: Removed variable ({})", left_id);
2573            self.vars.removed.insert(left_id.to_id());
2574        }
2575
2576        dump_change_detail!("sequences: {}", dump(&*b, false));
2577
2578        Ok(true)
2579    }
2580
2581    /// TODO(kdy1): Optimize this
2582    ///
2583    /// See https://github.com/swc-project/swc/pull/3480
2584    ///
2585    /// This works, but it should be optimized.
2586    ///
2587    /// This check blocks optimization of clearly valid optimizations like `i +=
2588    /// 1, arr[i]`
2589    //
2590    fn should_not_check_rhs_of_assign(&self, a: &Mergable, b: &mut AssignExpr) -> bool {
2591        if b.op.may_short_circuit() {
2592            return true;
2593        }
2594
2595        if let Some(a_id) = a.ident() {
2596            match a {
2597                Mergable::Expr(Expr::Assign(AssignExpr { op: op!("="), .. })) => {}
2598                Mergable::Expr(Expr::Assign(..)) => {
2599                    let used_by_b = idents_used_by(&*b.right);
2600                    if used_by_b
2601                        .iter()
2602                        .any(|id| id.1 == a_id.ctxt && id.0 == a_id.sym)
2603                    {
2604                        return true;
2605                    }
2606                }
2607                _ => {}
2608            }
2609        }
2610
2611        false
2612    }
2613}
2614
2615struct UsageCounter<'a> {
2616    expr_usage: usize,
2617    pat_usage: usize,
2618
2619    abort: bool,
2620
2621    target: &'a Ident,
2622    in_lhs: bool,
2623    in_abort: bool,
2624}
2625
2626impl Visit for UsageCounter<'_> {
2627    noop_visit_type!(fail);
2628
2629    fn visit_ident(&mut self, i: &Ident) {
2630        if self.target.sym == i.sym && self.target.ctxt == i.ctxt {
2631            if self.in_abort {
2632                self.abort = true;
2633                return;
2634            }
2635
2636            if self.in_lhs {
2637                self.pat_usage += 1;
2638            } else {
2639                self.expr_usage += 1;
2640            }
2641        }
2642    }
2643
2644    fn visit_member_expr(&mut self, e: &MemberExpr) {
2645        e.obj.visit_with(self);
2646
2647        if let MemberProp::Computed(c) = &e.prop {
2648            let old = self.in_lhs;
2649            self.in_lhs = false;
2650            c.expr.visit_with(self);
2651            self.in_lhs = old;
2652        }
2653    }
2654
2655    fn visit_update_expr(&mut self, e: &UpdateExpr) {
2656        let old_in_abort = self.in_abort;
2657        self.in_abort = true;
2658        e.visit_children_with(self);
2659        self.in_abort = old_in_abort;
2660    }
2661
2662    fn visit_await_expr(&mut self, e: &AwaitExpr) {
2663        let old_in_abort = self.in_abort;
2664        self.in_abort = true;
2665        e.visit_children_with(self);
2666        self.in_abort = old_in_abort;
2667    }
2668
2669    fn visit_yield_expr(&mut self, e: &YieldExpr) {
2670        let old_in_abort = self.in_abort;
2671        self.in_abort = true;
2672        e.visit_children_with(self);
2673        self.in_abort = old_in_abort;
2674    }
2675
2676    fn visit_pat(&mut self, p: &Pat) {
2677        let old = self.in_lhs;
2678        self.in_lhs = true;
2679        p.visit_children_with(self);
2680        self.in_lhs = old;
2681    }
2682
2683    fn visit_assign_target(&mut self, p: &AssignTarget) {
2684        let old = self.in_lhs;
2685        self.in_lhs = true;
2686        p.visit_children_with(self);
2687        self.in_lhs = old;
2688    }
2689
2690    fn visit_prop_name(&mut self, p: &PropName) {
2691        if let PropName::Computed(p) = p {
2692            p.visit_with(self)
2693        }
2694    }
2695
2696    fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
2697        if let SuperProp::Computed(c) = &e.prop {
2698            let old = self.in_lhs;
2699            self.in_lhs = false;
2700            c.expr.visit_with(self);
2701            self.in_lhs = old;
2702        }
2703    }
2704}
2705
2706#[derive(Debug)]
2707enum Mergable<'a> {
2708    Var(&'a mut VarDeclarator),
2709    Expr(&'a mut Expr),
2710    FnDecl(&'a mut FnDecl),
2711    Drop,
2712}
2713
2714impl Mergable<'_> {
2715    fn ident(&self) -> Option<&Ident> {
2716        match self {
2717            Mergable::Var(s) => match &s.name {
2718                Pat::Ident(i) => Some(&i.id),
2719                _ => None,
2720            },
2721            Mergable::Expr(s) => match &**s {
2722                Expr::Assign(s) => s.left.as_ident().map(|i| &i.id),
2723                _ => None,
2724            },
2725            Mergable::FnDecl(f) => Some(&f.ident),
2726            Mergable::Drop => None,
2727        }
2728    }
2729}
2730
2731#[derive(Debug, Default)]
2732struct MergeSequenceCache {
2733    ident_usage_cache: Vec<Option<FxHashSet<Id>>>,
2734    top_retain_cache: Vec<Option<bool>>,
2735}
2736
2737impl MergeSequenceCache {
2738    fn new(cap: usize) -> Self {
2739        Self {
2740            ident_usage_cache: vec![None; cap],
2741            top_retain_cache: vec![None; cap],
2742        }
2743    }
2744
2745    fn is_ident_used_by<N: VisitWith<IdentUsageCollector>>(
2746        &mut self,
2747        ident: &Ident,
2748        node: &N,
2749        node_id: usize,
2750    ) -> bool {
2751        let idents = self.ident_usage_cache[node_id].get_or_insert_with(|| idents_used_by(node));
2752        idents
2753            .iter()
2754            .any(|id| id.1 == ident.ctxt && id.0 == ident.sym)
2755    }
2756
2757    fn invalidate(&mut self, node_id: usize) {
2758        self.ident_usage_cache[node_id] = None;
2759    }
2760
2761    fn is_top_retain(&mut self, optimizer: &Optimizer, a: &Mergable, node_id: usize) -> bool {
2762        *self.top_retain_cache[node_id].get_or_insert_with(|| {
2763            if let Mergable::Drop = a {
2764                return true;
2765            }
2766
2767            if let Some(a_id) = a.ident() {
2768                if a_id.sym == "arguments"
2769                    || (matches!(a, Mergable::Var(_) | Mergable::FnDecl(_))
2770                        && !optimizer.may_remove_ident(a_id))
2771                {
2772                    return true;
2773                }
2774            }
2775
2776            false
2777        })
2778    }
2779}
2780
2781/// Returns true for trivial bool/numeric literals
2782pub(crate) fn is_trivial_lit(e: &Expr) -> bool {
2783    match e {
2784        Expr::Lit(Lit::Bool(..) | Lit::Num(..) | Lit::Null(..)) => true,
2785        Expr::Paren(e) => is_trivial_lit(&e.expr),
2786        Expr::Bin(e) => is_trivial_lit(&e.left) && is_trivial_lit(&e.right),
2787        Expr::Unary(e @ UnaryExpr { op: op!("!"), .. }) => is_trivial_lit(&e.arg),
2788        _ => false,
2789    }
2790}
2791
2792/// This assumes `a.left.to_id() == b.left.to_id()`
2793fn can_drop_op_for(
2794    a: AssignOp,
2795    b: AssignOp,
2796    var_type: Option<Value<Type>>,
2797    a_type: Value<Type>,
2798    b_type: Value<Type>,
2799) -> bool {
2800    if a == op!("=") {
2801        return true;
2802    }
2803
2804    if a == b {
2805        if a == op!("+=")
2806            && a_type.is_known()
2807            && a_type == b_type
2808            && (match var_type {
2809                Some(ty) => a_type == ty,
2810                None => true,
2811            })
2812        {
2813            return true;
2814        }
2815
2816        return matches!(a, op!("*="));
2817    }
2818
2819    false
2820}