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
33impl Optimizer<'_> {
36 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 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 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 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 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 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 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 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 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 e.exprs.retain(|e| !e.is_invalid());
711 }
712
713 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 if let (Pat::Ident(an), Pat::Ident(bn)) = (&av.name, &bv.name) {
743 if an.ctxt == bn.ctxt && an.sym == bn.sym {
744 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 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 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 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 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 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 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 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 false
1375 }
1376
1377 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 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 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 match a {
1589 Mergable::Var(a) => {
1590 if !a.name.is_ident() {
1592 return Ok(false);
1593 }
1594 }
1595 Mergable::Expr(a) => match a {
1596 Expr::Assign(a) => {
1597 if a.left.as_ident().is_none() {
1599 return Ok(false);
1600 }
1601 }
1602
1603 Expr::Update(a) => {
1605 if !a.arg.is_ident() {
1606 return Ok(false);
1607 }
1608 }
1609
1610 _ => {
1611 return Ok(false);
1613 }
1614 },
1615
1616 Mergable::FnDecl(..) => {
1617 }
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 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 if is_ident_used_by(&b_left.id, &b_assign.right) {
1804 return Ok(false);
1805 }
1806
1807 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 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 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 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 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 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 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 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 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 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 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 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 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 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
2781pub(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
2792fn 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}