1use std::mem::swap;
2
3use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::ext::ExprRefExt;
6use swc_ecma_transforms_optimization::debug_assert_valid;
7use swc_ecma_utils::{ExprExt, ExprFactory, IdentUsageFinder, StmtExt, StmtLike};
8
9use super::Optimizer;
10use crate::{
11 compress::{
12 optimize::BitCtx,
13 util::{negate, negate_cost},
14 },
15 program_data::VarUsageInfoFlags,
16 DISABLE_BUGGY_PASSES,
17};
18
19impl Optimizer<'_> {
22 pub(super) fn negate_if_stmt(&mut self, stmt: &mut IfStmt) {
24 let alt = match stmt.alt.as_deref_mut() {
25 Some(v) => v,
26 _ => return,
27 };
28
29 match &*stmt.cons {
30 Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => return,
31 _ => {}
32 }
33
34 if negate_cost(self.ctx.expr_ctx, &stmt.test, true, false) < 0 {
35 report_change!("if_return: Negating `cond` of an if statement which has cons and alt");
36 let ctx = self.ctx.clone().with(BitCtx::InBoolCtx, true);
37 self.with_ctx(ctx).negate(&mut stmt.test, false);
38 swap(alt, &mut *stmt.cons);
39 return;
40 }
41
42 match &*alt {
43 Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => {
44 self.changed = true;
45 report_change!(
46 "if_return: Negating an if statement because the alt is return / continue"
47 );
48 self.negate(&mut stmt.test, false);
49 swap(alt, &mut *stmt.cons);
50 }
51 _ => {}
52 }
53 }
54
55 pub(super) fn compress_cond_to_logical_ignoring_return_value(&mut self, e: &mut Expr) {
59 let cond = match e {
60 Expr::Cond(cond) => cond,
61 _ => return,
62 };
63
64 if !cond.cons.may_have_side_effects(self.ctx.expr_ctx) {
65 self.changed = true;
66 report_change!("conditionals: `cond ? useless : alt` => `cond || alt`");
67 *e = BinExpr {
68 span: cond.span,
69 op: op!("||"),
70 left: cond.test.take(),
71 right: cond.alt.take(),
72 }
73 .into();
74 return;
75 }
76
77 if !cond.alt.may_have_side_effects(self.ctx.expr_ctx) {
78 self.changed = true;
79 report_change!("conditionals: `cond ? cons : useless` => `cond && cons`");
80 *e = BinExpr {
81 span: cond.span,
82 op: op!("&&"),
83 left: cond.test.take(),
84 right: cond.cons.take(),
85 }
86 .into();
87 }
88 }
89
90 pub(super) fn merge_similar_ifs<T>(&mut self, stmts: &mut Vec<T>)
108 where
109 T: StmtLike,
110 {
111 if !self.options.conditionals {
112 return;
113 }
114
115 let has_work =
116 stmts
117 .windows(2)
118 .any(|stmts| match (&stmts[0].as_stmt(), &stmts[1].as_stmt()) {
119 (
120 Some(Stmt::If(l @ IfStmt { alt: None, .. })),
121 Some(Stmt::If(r @ IfStmt { alt: None, .. })),
122 ) => SyntaxContext::within_ignored_ctxt(|| {
123 l.cons.eq_ignore_span(&r.cons) && l.cons.terminates()
124 }),
125 _ => false,
126 });
127 if !has_work {
128 return;
129 }
130
131 self.changed = true;
132 report_change!("conditionals: Merging if statements with same `cons`");
133
134 let mut cur: Option<IfStmt> = None;
135 let mut new = Vec::with_capacity(stmts.len());
136 for stmt in stmts.take() {
137 match stmt.try_into_stmt() {
138 Ok(stmt) => {
139 match stmt {
140 Stmt::If(mut stmt @ IfStmt { alt: None, .. }) => {
141 match &mut cur {
144 Some(cur_if) => {
145 if SyntaxContext::within_ignored_ctxt(|| {
147 cur_if.cons.eq_ignore_span(&stmt.cons)
148 }) {
149 cur_if.test = BinExpr {
150 span: DUMMY_SP,
151 left: cur_if.test.take(),
152 op: op!("||"),
153 right: stmt.test.take(),
154 }
155 .into();
156 } else {
157 new.extend(cur.take().map(Stmt::If).map(T::from));
158
159 cur = Some(stmt);
160 }
161 }
162 None => {
163 cur = Some(stmt);
164 }
165 }
166 }
167 _ => {
168 new.extend(cur.take().map(Stmt::If).map(T::from));
169
170 new.push(T::from(stmt));
171 }
172 }
173 }
174 Err(item) => {
175 new.extend(cur.take().map(Stmt::If).map(T::from));
176
177 new.push(item);
178 }
179 }
180 }
181
182 new.extend(cur.map(Stmt::If).map(T::from));
183
184 *stmts = new;
185 }
186
187 pub(super) fn compress_if_stmt_as_cond(&mut self, s: &mut Stmt) {
210 let stmt = match s {
211 Stmt::If(v) => v,
212 _ => return,
213 };
214
215 if let Stmt::Empty(..) = &*stmt.cons {
216 if (self.options.conditionals || self.options.unused) && stmt.alt.is_none() {
217 *s = ExprStmt {
218 span: stmt.span,
219 expr: stmt.test.take(),
220 }
221 .into();
222 self.changed = true;
223 report_change!("conditionals: `if (foo);` => `foo` ");
224 return;
225 }
226 }
227
228 let alt = match &mut stmt.alt {
231 Some(v) => &mut **v,
232 None => {
233 return;
234 }
235 };
236 let alt = match extract_expr_stmt(alt) {
237 Some(v) => v,
238 None => return,
239 };
240
241 if let Stmt::Empty(..) = &*stmt.cons {
245 match &mut *stmt.test {
246 Expr::Unary(UnaryExpr {
247 op: op!("!"), arg, ..
248 }) => {
249 report_change!("Optimizing `if (!foo); else bar();` as `foo && bar();`");
250
251 let mut expr = BinExpr {
252 span: DUMMY_SP,
253 left: arg.take(),
254 op: op!("&&"),
255 right: Box::new(alt.take()),
256 }
257 .into();
258 self.compress_logical_exprs_as_bang_bang(&mut expr, true);
259 *s = ExprStmt {
260 span: stmt.span,
261 expr: expr.into(),
262 }
263 .into();
264 }
265 _ => {
266 report_change!("Optimizing `if (foo); else bar();` as `foo || bar();`");
267
268 let mut expr = BinExpr {
269 span: DUMMY_SP,
270 left: stmt.test.take(),
271 op: op!("||"),
272 right: Box::new(alt.take()),
273 }
274 .into();
275 self.compress_logical_exprs_as_bang_bang(&mut expr, false);
276 *s = ExprStmt {
277 span: stmt.span,
278 expr: expr.into(),
279 }
280 .into();
281 }
282 }
283 return;
284 }
285
286 let cons = match extract_expr_stmt(&mut stmt.cons) {
287 Some(v) => v,
288 None => return,
289 };
290
291 let new_expr = self.compress_similar_cons_alt(&mut stmt.test, cons, alt, true);
292
293 if let Some(v) = new_expr {
294 debug_assert_valid(&v);
295
296 self.changed = true;
297 report_change!("conditionals: Merging cons and alt as only one argument differs");
298 *s = ExprStmt {
299 span: stmt.span,
300 expr: Box::new(v),
301 }
302 .into();
303 return;
304 }
305
306 if self.options.conditionals || self.options.bools {
307 report_change!(
309 "Compressing if statement as conditional expression (even though cons and alt is \
310 not compressable)"
311 );
312 self.changed = true;
313 *s = ExprStmt {
314 span: stmt.span,
315 expr: CondExpr {
316 span: DUMMY_SP,
317 test: stmt.test.take(),
318 cons: Box::new(cons.take()),
319 alt: Box::new(alt.take()),
320 }
321 .into(),
322 }
323 .into()
324 }
325 }
326
327 pub(super) fn compress_cond_expr_if_similar(&mut self, e: &mut Expr) {
329 if !self.options.conditionals {
330 return;
331 }
332
333 let cond = match e {
334 Expr::Cond(expr) => expr,
335 _ => return,
336 };
337
338 let compressed =
339 self.compress_similar_cons_alt(&mut cond.test, &mut cond.cons, &mut cond.alt, false);
340
341 if let Some(v) = compressed {
342 *e = v;
343 self.changed = true;
344 return;
345 }
346
347 if cond.test.is_ident() && cond.test.eq_ignore_span(&cond.cons) {
349 report_change!("Compressing `x ? x : y` as `x || y`");
350 self.changed = true;
351 *e = BinExpr {
352 span: cond.span,
353 op: op!("||"),
354 left: cond.test.take(),
355 right: cond.alt.take(),
356 }
357 .into();
358 }
359 }
360
361 fn compress_similar_cons_alt(
362 &mut self,
363 test: &mut Box<Expr>,
364 cons: &mut Expr,
365 alt: &mut Expr,
366 is_for_if_stmt: bool,
367 ) -> Option<Expr> {
368 debug_assert_valid(cons);
369 debug_assert_valid(alt);
370
371 if cons.eq_ignore_span(alt) && !matches!(&*cons, Expr::Yield(..) | Expr::Fn(..)) {
372 report_change!("conditionals: cons is same as alt");
373 return Some(
374 SeqExpr {
375 span: DUMMY_SP,
376 exprs: vec![test.take(), Box::new(cons.take())],
377 }
378 .into(),
379 );
380 }
381
382 match (cons, alt) {
383 (Expr::Call(cons), Expr::Call(alt)) => {
384 let side_effects_in_test = test.may_have_side_effects(self.ctx.expr_ctx);
389
390 if self.data.contains_unresolved(test) {
391 return None;
392 }
393
394 let cons_callee = cons.callee.as_expr().and_then(|e| e.as_ident())?;
395 if IdentUsageFinder::find(cons_callee, &**test) {
396 return None;
397 }
398 if !cons.callee.eq_ignore_span(&alt.callee) {
401 return None;
402 }
403
404 let side_effect_free = self
405 .data
406 .vars
407 .get(&cons_callee.to_id())
408 .map(|v| {
409 v.flags.contains(
410 VarUsageInfoFlags::IS_FN_LOCAL.union(VarUsageInfoFlags::DECLARED),
411 )
412 })
413 .unwrap_or(false);
414
415 if side_effect_free
416 && cons.args.len() == alt.args.len()
417 && cons.args.iter().all(|arg| arg.spread.is_none())
418 && alt.args.iter().all(|arg| arg.spread.is_none())
419 {
420 let mut diff_count = 0;
421 let mut diff_idx = None;
422
423 for (idx, (cons, alt)) in cons.args.iter().zip(alt.args.iter()).enumerate() {
424 if !cons.eq_ignore_span(alt) {
425 diff_count += 1;
426 diff_idx = Some(idx);
427 } else {
428 if side_effects_in_test && !cons.expr.is_pure(self.ctx.expr_ctx) {
430 return None;
431 }
432 }
433 }
434
435 if diff_count == 1 {
436 let diff_idx = diff_idx.unwrap();
437
438 report_change!(
439 "conditionals: Merging cons and alt as only one argument differs"
440 );
441 self.changed = true;
442
443 let mut new_args = Vec::new();
444
445 for (idx, arg) in cons.args.take().into_iter().enumerate() {
446 if idx == diff_idx {
447 new_args.push(ExprOrSpread {
449 spread: None,
450 expr: CondExpr {
451 span: arg.expr.span(),
452 test: test.take(),
453 cons: arg.expr,
454 alt: alt.args[idx].expr.take(),
455 }
456 .into(),
457 })
458 } else {
459 new_args.push(arg)
460 }
461 }
462
463 return Some(
464 CallExpr {
465 span: test.span(),
466 callee: cons_callee.clone().as_callee(),
467 args: new_args,
468 ..Default::default()
469 }
470 .into(),
471 );
472 }
473 }
474
475 if side_effect_free
476 && cons.args.len() == 1
477 && alt.args.len() == 1
478 && cons.args.iter().all(|arg| arg.spread.is_none())
479 && alt.args.iter().all(|arg| arg.spread.is_none())
480 {
481 let args = vec![CondExpr {
490 span: DUMMY_SP,
491 test: test.take(),
492 cons: cons.args[0].expr.take(),
493 alt: alt.args[0].expr.take(),
494 }
495 .as_arg()];
496
497 report_change!(
498 "Compressing if into cond as there's no side effect and the number of \
499 arguments is 1"
500 );
501 return Some(
502 CallExpr {
503 span: DUMMY_SP,
504 callee: cons.callee.take(),
505 args,
506 ..Default::default()
507 }
508 .into(),
509 );
510 }
511
512 if !side_effect_free && is_for_if_stmt {
513 report_change!("Compressing if into cond while preserving side effects");
514 return Some(
515 CondExpr {
516 span: DUMMY_SP,
517 test: test.take(),
518 cons: cons.take().into(),
519 alt: alt.take().into(),
520 }
521 .into(),
522 );
523 }
524
525 None
526 }
527
528 (Expr::New(cons), Expr::New(alt)) => {
529 if self.data.contains_unresolved(test) {
530 return None;
531 }
532
533 if cons.callee.eq_ignore_span(&alt.callee)
536 && cons.args.as_ref().map(|v| v.len() <= 1).unwrap_or(true)
537 && alt.args.as_ref().map(|v| v.len() <= 1).unwrap_or(true)
538 && cons.args.as_ref().map(|v| v.len()).unwrap_or(0)
539 == alt.args.as_ref().map(|v| v.len()).unwrap_or(0)
540 && (cons.args.is_some()
541 && cons
542 .args
543 .as_ref()
544 .unwrap()
545 .iter()
546 .all(|arg| arg.spread.is_none()))
547 && (alt.args.is_some()
548 && alt
549 .args
550 .as_ref()
551 .unwrap()
552 .iter()
553 .all(|arg| arg.spread.is_none()))
554 {
555 let mut args = Vec::new();
556
557 if cons.args.as_ref().map(|v| v.len()).unwrap_or(0) == 1 {
558 args = vec![ExprOrSpread {
559 spread: None,
560 expr: Box::new(Expr::Cond(CondExpr {
561 span: DUMMY_SP,
562 test: test.take(),
563 cons: cons.args.as_mut().unwrap()[0].expr.take(),
564 alt: alt.args.as_mut().unwrap()[0].expr.take(),
565 })),
566 }];
567 }
568
569 report_change!(
570 "Compressing if statement into a conditional expression of `new` as \
571 there's no side effect and the number of arguments is 1"
572 );
573 return Some(
574 NewExpr {
575 span: DUMMY_SP,
576 callee: cons.callee.take(),
577 args: Some(args),
578 ..Default::default()
579 }
580 .into(),
581 );
582 }
583
584 None
585 }
586
587 (
588 Expr::Assign(cons @ AssignExpr { op: op!("="), .. }),
589 Expr::Assign(alt @ AssignExpr { op: op!("="), .. }),
590 ) if cons.left.eq_ignore_span(&alt.left) && cons.left.as_ident().is_some() => {
591 if self
592 .data
593 .ident_is_unresolved(&cons.left.as_ident().unwrap().id)
594 {
595 return None;
596 }
597
598 report_change!("Merging assignments in cons and alt of if statement");
599 Some(
600 AssignExpr {
601 span: DUMMY_SP,
602 op: cons.op,
603 left: cons.left.take(),
604 right: CondExpr {
605 span: DUMMY_SP,
606 test: test.take(),
607 cons: cons.right.take(),
608 alt: alt.right.take(),
609 }
610 .into(),
611 }
612 .into(),
613 )
614 }
615
616 (Expr::Cond(cons), alt) if (*cons.alt).eq_ignore_span(&*alt) => {
618 report_change!("conditionals: a ? b ? c() : d() : d() => a && b ? c() : d()");
619 Some(
620 CondExpr {
621 span: DUMMY_SP,
622 test: BinExpr {
623 span: DUMMY_SP,
624 left: test.take(),
625 op: op!("&&"),
626 right: cons.test.take(),
627 }
628 .into(),
629 cons: cons.cons.take(),
630 alt: cons.alt.take(),
631 }
632 .into(),
633 )
634 }
635
636 (cons, Expr::Cond(alt)) if cons.eq_ignore_span(&*alt.cons) => {
638 report_change!("conditionals: a ? c() : b ? c() : d() => a || b ? c() : d()");
639 Some(
640 CondExpr {
641 span: DUMMY_SP,
642 test: BinExpr {
643 span: DUMMY_SP,
644 left: test.take(),
645 op: op!("||"),
646 right: alt.test.take(),
647 }
648 .into(),
649 cons: alt.cons.take(),
650 alt: alt.alt.take(),
651 }
652 .into(),
653 )
654 }
655
656 (cons, Expr::Seq(alt)) if (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) => {
660 self.changed = true;
661 report_change!("conditionals: Reducing seq expr in alt");
662 alt.exprs.pop();
664 let first = BinExpr {
665 span: DUMMY_SP,
666 left: test.take(),
667 op: op!("||"),
668 right: Expr::from_exprs(alt.exprs.take()),
669 }
670 .into();
671 Some(
672 SeqExpr {
673 span: DUMMY_SP,
674 exprs: vec![first, Box::new(cons.take())],
675 }
676 .into(),
677 )
678 }
679
680 (Expr::Seq(cons), alt) if (**cons.exprs.last().unwrap()).eq_ignore_span(&*alt) => {
684 self.changed = true;
685 report_change!("conditionals: Reducing seq expr in cons");
686 cons.exprs.pop();
688 let first = BinExpr {
689 span: DUMMY_SP,
690 left: test.take(),
691 op: op!("&&"),
692 right: Expr::from_exprs(cons.exprs.take()),
693 }
694 .into();
695 Some(
696 SeqExpr {
697 span: DUMMY_SP,
698 exprs: vec![first, Box::new(alt.take())],
699 }
700 .into(),
701 )
702 }
703
704 (Expr::Seq(left), Expr::Seq(right)) => {
705 let left_len = left.exprs.len();
706 let right_len = right.exprs.len();
707 let min_len = left_len.min(right_len);
708
709 let mut idx = 0;
710
711 while idx < min_len
712 && left.exprs[left_len - idx - 1]
713 .eq_ignore_span(&right.exprs[right_len - idx - 1])
714 && !matches!(
715 &*left.exprs[left_len - idx - 1],
716 Expr::Yield(..) | Expr::Fn(..)
717 )
718 {
719 idx += 1;
720 }
721
722 if idx == 0 {
723 None
724 } else if idx == left_len {
725 self.changed = true;
726 report_change!("conditionals: Reducing similar seq expr in cons");
727
728 let mut alt = right.exprs.take();
729
730 alt.truncate(alt.len() - idx);
731
732 let mut seq = vec![Box::new(Expr::Bin(BinExpr {
733 span: DUMMY_SP,
734 left: test.take(),
735 op: op!("||"),
736 right: Expr::from_exprs(alt),
737 }))];
738 seq.append(&mut left.exprs);
739
740 Some(
741 SeqExpr {
742 span: DUMMY_SP,
743 exprs: seq,
744 }
745 .into(),
746 )
747 } else if idx == right_len {
748 self.changed = true;
749 report_change!("conditionals: Reducing similar seq expr in alt");
750
751 let mut cons = left.exprs.take();
752
753 cons.truncate(cons.len() - idx);
754
755 let mut seq = vec![Box::new(Expr::Bin(BinExpr {
756 span: DUMMY_SP,
757 left: test.take(),
758 op: op!("&&"),
759 right: Expr::from_exprs(cons),
760 }))];
761 seq.append(&mut right.exprs);
762
763 Some(
764 SeqExpr {
765 span: DUMMY_SP,
766 exprs: seq,
767 }
768 .into(),
769 )
770 } else {
771 self.changed = true;
772 report_change!("conditionals: Reducing similar seq expr");
773 let _ = left.exprs.split_off(left_len - idx);
774 let mut common = right.exprs.split_off(right_len - idx);
775
776 let mut seq = vec![Box::new(Expr::Cond(CondExpr {
777 span: DUMMY_SP,
778 test: test.take(),
779 cons: Box::new(Expr::Seq(left.take())),
780 alt: Box::new(Expr::Seq(right.take())),
781 }))];
782 seq.append(&mut common);
783
784 Some(
785 SeqExpr {
786 span: DUMMY_SP,
787 exprs: seq,
788 }
789 .into(),
790 )
791 }
792 }
793
794 _ => None,
795 }
796 }
797
798 pub(super) fn inject_else(&mut self, stmts: &mut Vec<Stmt>) {
800 if DISABLE_BUGGY_PASSES {
801 return;
802 }
803
804 let len = stmts.len();
805
806 let pos_of_if = stmts.iter().enumerate().rposition(|(idx, s)| {
807 idx != len - 1
808 && match s {
809 Stmt::If(IfStmt {
810 cons, alt: None, ..
811 }) => match &**cons {
812 Stmt::Block(b) => {
813 b.stmts.len() == 2
814 && !matches!(b.stmts.first(), Some(Stmt::If(..) | Stmt::Expr(..)))
815 && matches!(
816 b.stmts.last(),
817 Some(Stmt::Return(ReturnStmt { arg: None, .. }))
818 )
819 }
820 _ => false,
821 },
822 _ => false,
823 }
824 });
825
826 let pos_of_if = match pos_of_if {
827 Some(v) => v,
828 _ => return,
829 };
830
831 self.changed = true;
832 report_change!("if_return: Injecting else because it's shorter");
833
834 let mut new = Vec::with_capacity(pos_of_if + 1);
835 new.extend(stmts.drain(..pos_of_if));
836 let alt = stmts.drain(1..).collect::<Vec<_>>();
837
838 let if_stmt = stmts.take().into_iter().next().unwrap();
839 match if_stmt {
840 Stmt::If(mut s) => {
841 match &mut *s.cons {
842 Stmt::Block(cons) => {
843 cons.stmts.pop();
844 }
845 _ => {
846 unreachable!()
847 }
848 }
849
850 assert_eq!(s.alt, None);
851
852 s.alt = Some(if alt.len() == 1 {
853 Box::new(alt.into_iter().next().unwrap())
854 } else {
855 Box::new(
856 BlockStmt {
857 span: DUMMY_SP,
858 stmts: alt,
859 ..Default::default()
860 }
861 .into(),
862 )
863 });
864
865 new.push(s.into())
866 }
867 _ => {
868 unreachable!()
869 }
870 }
871
872 *stmts = new;
873 }
874
875 pub(super) fn drop_else_token<T>(&mut self, stmts: &mut Vec<T>)
884 where
885 T: StmtLike,
886 {
887 let need_work = stmts.iter().any(|stmt| match stmt.as_stmt() {
889 Some(Stmt::If(IfStmt {
890 cons,
891 alt: Some(..),
892 ..
893 })) => cons.terminates(),
894 _ => false,
895 });
896 if !need_work {
897 return;
898 }
899 let mut new_stmts = Vec::with_capacity(stmts.len() * 2);
901 stmts.take().into_iter().for_each(|stmt| {
902 match stmt.try_into_stmt() {
903 Ok(stmt) => match stmt {
904 Stmt::If(IfStmt {
905 span,
906 mut test,
907 mut cons,
908 alt: Some(mut alt),
909 ..
910 }) if cons.terminates() => {
911 if let (
912 Stmt::Return(ReturnStmt { arg: None, .. }),
913 Stmt::Decl(Decl::Fn(..)),
914 ) = (&*cons, &*alt)
915 {
916 negate(self.ctx.expr_ctx, &mut test, true, false);
918
919 swap(&mut cons, &mut alt);
920 }
921
922 new_stmts.push(T::from(
923 IfStmt {
924 span,
925 test,
926 cons,
927 alt: None,
928 }
929 .into(),
930 ));
931 new_stmts.push(T::from(*alt));
932 }
933 _ => {
934 new_stmts.push(T::from(stmt));
935 }
936 },
937 Err(stmt) => new_stmts.push(stmt),
938 }
939 });
940
941 self.changed = true;
942 report_change!("conditionals: Dropped useless `else` token");
943 *stmts = new_stmts;
944 }
945}
946
947fn extract_expr_stmt(s: &mut Stmt) -> Option<&mut Expr> {
948 match s {
949 Stmt::Expr(e) => Some(&mut *e.expr),
950 _ => None,
951 }
952}