1use std::mem::swap;
2
3use swc_common::{util::take::Take, Spanned};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{ExprCtx, ExprExt, Type, Value};
6
7use super::Pure;
8use crate::{
9 compress::util::{can_absorb_negate, is_eq, is_pure_undefined, negate, negate_cost},
10 util::make_bool,
11};
12
13impl Pure<'_> {
14 pub(super) fn compress_if_stmt_as_expr(&mut self, s: &mut Stmt) {
15 if !self.options.conditionals && !self.options.bools {
16 return;
17 }
18
19 let stmt = match s {
20 Stmt::If(v) => v,
21 _ => return,
22 };
23
24 if stmt.alt.is_none() {
25 if let Stmt::Expr(cons) = &mut *stmt.cons {
26 self.changed = true;
27 report_change!("conditionals: `if (foo) bar;` => `foo && bar`");
28 *s = ExprStmt {
29 span: stmt.span,
30 expr: BinExpr {
31 span: stmt.test.span(),
32 op: op!("&&"),
33 left: stmt.test.take(),
34 right: cons.expr.take(),
35 }
36 .into(),
37 }
38 .into();
39 }
40 }
41 }
42
43 pub(super) fn make_bool_short(
44 &mut self,
45 e: &mut Expr,
46 in_bool_ctx: bool,
47 ignore_return_value: bool,
48 ) {
49 match e {
50 Expr::Cond(cond) => {
51 self.make_bool_short(&mut cond.test, true, false);
52 self.make_bool_short(&mut cond.cons, in_bool_ctx, ignore_return_value);
53 self.make_bool_short(&mut cond.alt, in_bool_ctx, ignore_return_value);
54
55 if negate_cost(self.expr_ctx, &cond.test, true, false) >= 0 {
56 return;
57 }
58 self.negate(&mut cond.test, true, false);
59 swap(&mut cond.cons, &mut cond.alt);
60 return;
61 }
62
63 Expr::Bin(BinExpr {
64 op: op @ (op!("&&") | op!("||")),
65 left,
66 right,
67 ..
68 }) => {
69 self.make_bool_short(left, in_bool_ctx, false);
70 self.make_bool_short(right, in_bool_ctx, ignore_return_value);
71
72 if in_bool_ctx {
73 match *op {
74 op!("||") => {
75 if let Value::Known(false) = right.as_pure_bool(self.expr_ctx) {
78 report_change!(
79 "bools: `expr || false` => `expr` (in bool context)"
80 );
81 self.changed = true;
82 *e = *left.take();
83 return;
84 }
85 }
86
87 op!("&&") => {
88 if let (_, Value::Known(false)) = left.cast_to_bool(self.expr_ctx) {
91 report_change!(
92 "bools: `false && foo` => `false` (in bool context)"
93 );
94 self.changed = true;
95 *e = *left.take();
96 return;
97 }
98 }
99
100 _ => {}
101 }
102 }
103 }
104
105 Expr::Bin(BinExpr { left, right, .. }) => {
106 self.make_bool_short(left, false, false);
107 self.make_bool_short(right, false, false);
108 return;
109 }
110
111 Expr::Unary(UnaryExpr {
112 op: op!("!"), arg, ..
113 }) => {
114 self.make_bool_short(arg, true, ignore_return_value);
115 return;
116 }
117
118 Expr::Array(ArrayLit { elems, .. }) => {
119 for elem in elems.iter_mut().flatten() {
120 self.make_bool_short(&mut elem.expr, false, false);
121 }
122 return;
123 }
124
125 Expr::Call(CallExpr { callee, args, .. }) => {
126 if let Callee::Expr(callee) = callee {
127 self.make_bool_short(callee, false, false);
128 }
129
130 for arg in args {
131 self.make_bool_short(&mut arg.expr, false, false);
132 }
133 return;
134 }
135
136 Expr::Seq(SeqExpr { exprs, .. }) => {
137 let len = exprs.len();
138 for (idx, expr) in exprs.iter_mut().enumerate() {
139 let is_last = idx == len - 1;
140
141 self.make_bool_short(expr, false, !is_last || ignore_return_value);
142 }
143 return;
144 }
145
146 Expr::Assign(AssignExpr { right, .. }) => {
147 self.make_bool_short(right, false, false);
148 return;
149 }
150
151 _ => return,
152 }
153
154 let cost = negate_cost(self.expr_ctx, e, in_bool_ctx, ignore_return_value);
155
156 if cost >= 0 {
157 return;
158 }
159
160 if let Expr::Bin(BinExpr {
161 op: op @ (op!("&&") | op!("||")),
162 left,
163 ..
164 }) = e
165 {
166 if ignore_return_value {
167 *op = match op {
169 op!("&&") => op!("||"),
170 op!("||") => op!("&&"),
171 _ => unreachable!(),
172 };
173
174 self.negate(left, true, false);
175 }
176 }
177 }
178
179 pub(super) fn negate_twice(&mut self, e: &mut Expr, is_ret_val_ignored: bool) {
180 negate(self.expr_ctx, e, true, is_ret_val_ignored);
181 negate(self.expr_ctx, e, false, is_ret_val_ignored);
182 }
183
184 pub(super) fn negate(&mut self, e: &mut Expr, in_bool_ctx: bool, is_ret_val_ignored: bool) {
185 negate(self.expr_ctx, e, in_bool_ctx, is_ret_val_ignored)
186 }
187
188 pub(super) fn optimize_negate_eq(&mut self, e: &mut Expr) {
189 fn negate_eq(op: BinaryOp) -> BinaryOp {
190 match op {
191 op!("==") => op!("!="),
192 op!("!=") => op!("=="),
193 op!("===") => op!("!=="),
194 op!("!==") => op!("==="),
195 _ => unreachable!(),
196 }
197 }
198
199 if !self.options.bools {
200 return;
201 }
202
203 let Expr::Unary(UnaryExpr {
204 op: op!("!"), arg, ..
205 }) = e
206 else {
207 return;
208 };
209
210 let arg_can_negate = can_absorb_negate(arg, self.expr_ctx);
211
212 match &mut **arg {
213 Expr::Bin(BinExpr { op, .. }) if is_eq(*op) => {
214 self.changed = true;
215 report_change!("bools: Optimizing `!(a == b)` as `a != b`");
216
217 *op = negate_eq(*op);
218
219 *e = *arg.take();
220 }
221 Expr::Bin(BinExpr {
222 op: op @ (op!("&&") | op!("||")),
223 left,
224 right,
225 ..
226 }) if arg_can_negate => {
227 self.changed = true;
228 report_change!("bools: Optimizing `!(a == b && c == d)` as `a != b`");
229
230 *op = match op {
231 op!("&&") => op!("||"),
232 op!("||") => op!("&&"),
233 _ => unreachable!(),
234 };
235
236 self.negate(left, false, false);
237 self.negate(right, false, false);
238 *e = *arg.take();
239 }
240 _ => (),
241 }
242 }
243
244 pub(super) fn compress_cmp_with_long_op(&mut self, e: &mut BinExpr) {
245 if !matches!(e.op, op!("===") | op!("!==")) {
246 return;
247 }
248
249 let is_typeof_unaray = |l: &Expr, r: &Expr| {
250 matches!(
251 (l, r),
252 (
253 Expr::Unary(UnaryExpr {
254 op: op!("typeof"),
255 ..
256 }),
257 Expr::Lit(..)
258 )
259 )
260 };
261
262 let should_optimize = is_typeof_unaray(&e.left, &e.right)
263 || is_typeof_unaray(&e.right, &e.left)
264 || (self.options.comparisons && {
265 if let Value::Known(l) = e.left.get_type(self.expr_ctx) {
266 if let Value::Known(r) = e.right.get_type(self.expr_ctx) {
267 l == r
268 } else {
269 false
270 }
271 } else {
272 false
273 }
274 });
275
276 if should_optimize {
277 report_change!("bools: Compressing comparison of `typeof` with literal");
278 self.changed = true;
279 e.op = match e.op {
280 op!("===") => {
281 op!("==")
282 }
283 op!("!==") => {
284 op!("!=")
285 }
286 _ => {
287 unreachable!()
288 }
289 }
290 }
291 }
292
293 pub(super) fn remove_useless_logical_rhs(&mut self, e: &mut Expr) {
298 if !self.options.bools {
299 return;
300 }
301
302 match e {
303 Expr::Bin(BinExpr {
304 left,
305 op: op @ op!("&&"),
306 right,
307 ..
308 })
309 | Expr::Bin(BinExpr {
310 left,
311 op: op @ op!("||"),
312 right,
313 ..
314 }) => {
315 let lt = left.get_type(self.expr_ctx);
316 let rt = right.get_type(self.expr_ctx);
317
318 if let (Value::Known(Type::Bool), Value::Known(Type::Bool)) = (lt, rt) {
319 let rb = right.as_pure_bool(self.expr_ctx);
320 let rb = match rb {
321 Value::Known(v) => v,
322 Value::Unknown => return,
323 };
324
325 let can_remove = if *op == op!("&&") { rb } else { !rb };
327
328 if can_remove {
329 #[allow(clippy::if_same_then_else)]
330 if *op == op!("&&") {
331 report_change!("booleans: Compressing `!foo && true` as `!foo`");
332 } else {
333 report_change!("booleans: Compressing `!foo || false` as `!foo`");
334 }
335 self.changed = true;
336 *e = *left.take();
337 }
338 }
339 }
340 _ => {}
341 }
342 }
343
344 pub(super) fn compress_useless_deletes(&mut self, e: &mut Expr) {
346 if !self.options.bools {
347 return;
348 }
349
350 let delete = match e {
351 Expr::Unary(
352 u @ UnaryExpr {
353 op: op!("delete"), ..
354 },
355 ) => u,
356 _ => return,
357 };
358
359 if delete.arg.may_have_side_effects(ExprCtx {
360 is_unresolved_ref_safe: true,
361 ..self.expr_ctx
362 }) {
363 return;
364 }
365
366 let convert_to_true = match &*delete.arg {
367 Expr::Seq(..)
368 | Expr::Cond(..)
369 | Expr::Bin(BinExpr { op: op!("&&"), .. })
370 | Expr::Bin(BinExpr { op: op!("||"), .. }) => true,
371 Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => false,
373 Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => false,
374 Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => false,
375
376 e if is_pure_undefined(self.expr_ctx, e) => true,
377
378 Expr::Ident(i) => i.ctxt != self.expr_ctx.unresolved_ctxt,
379
380 Expr::Bin(BinExpr {
382 op: op!("/"),
383 right,
384 ..
385 }) => {
386 let rn = right.as_pure_number(self.expr_ctx);
387 let v = if let Value::Known(rn) = rn {
388 rn != 0.0
389 } else {
390 false
391 };
392
393 if v {
394 true
395 } else {
396 self.changed = true;
397 let span = delete.arg.span();
398 report_change!("booleans: Compressing `delete` as sequence expression");
399 *e = SeqExpr {
400 span,
401 exprs: vec![delete.arg.take(), Box::new(make_bool(span, true))],
402 }
403 .into();
404 return;
405 }
406 }
407
408 _ => false,
409 };
410
411 if convert_to_true {
412 self.changed = true;
413 let span = delete.arg.span();
414 report_change!("booleans: Compressing `delete` => true");
415 *e = make_bool(span, true);
416 }
417 }
418
419 pub(super) fn handle_negated_seq(&mut self, n: &mut Expr) {
420 match &mut *n {
421 Expr::Unary(e @ UnaryExpr { op: op!("!"), .. }) => {
422 if let Expr::Seq(SeqExpr { exprs, .. }) = &mut *e.arg {
423 if exprs.is_empty() {
424 return;
425 }
426 report_change!("bools: Optimizing negated sequences");
427
428 {
429 let last = exprs.last_mut().unwrap();
430 self.optimize_expr_in_bool_ctx(last, false);
431 negate(self.expr_ctx, last, false, false);
433 }
434
435 *n = *e.arg.take();
436 }
437 }
438 Expr::Unary(UnaryExpr {
439 op: op!("delete"), ..
440 }) => {
441 }
443 _ => {}
444 }
445 }
446
447 pub(super) fn optimize_expr_in_bool_ctx(
449 &mut self,
450 n: &mut Expr,
451 is_return_value_ignored: bool,
452 ) {
453 match n {
454 Expr::Bin(BinExpr {
455 op: op!("&&") | op!("||"),
456 left,
457 right,
458 ..
459 }) => {
460 self.optimize_expr_in_bool_ctx(left, false);
463 self.optimize_expr_in_bool_ctx(right, is_return_value_ignored);
464 return;
465 }
466
467 Expr::Seq(e) => {
468 if let Some(last) = e.exprs.last_mut() {
469 self.optimize_expr_in_bool_ctx(last, is_return_value_ignored);
470 }
471 }
472
473 _ => {}
474 }
475
476 if !self.options.bools {
477 return;
478 }
479
480 match n {
481 Expr::Unary(UnaryExpr {
482 span,
483 op: op!("!"),
484 arg,
485 }) => match &mut **arg {
486 Expr::Lit(Lit::Num(Number { value, .. })) => {
487 report_change!("Optimizing: number => number (in bool context)");
488
489 self.changed = true;
490 *n = Lit::Num(Number {
491 span: *span,
492 value: if *value == 0.0 { 1.0 } else { 0.0 },
493 raw: None,
494 })
495 .into()
496 }
497
498 Expr::Unary(UnaryExpr {
499 op: op!("!"), arg, ..
500 }) => {
501 report_change!("bools: !!expr => expr (in bool ctx)");
502 self.changed = true;
503 *n = *arg.take();
504 }
505 _ => {}
506 },
507
508 Expr::Unary(UnaryExpr {
509 span,
510 op: op!("typeof"),
511 arg,
512 }) => {
513 report_change!("Optimizing: typeof => true (in bool context)");
514 self.changed = true;
515
516 match &**arg {
517 Expr::Ident(..) => {
518 *n = Lit::Num(Number {
519 span: *span,
520 value: 1.0,
521 raw: None,
522 })
523 .into()
524 }
525 _ => {
526 let true_expr = Lit::Num(Number {
528 span: *span,
529 value: 1.0,
530 raw: None,
531 })
532 .into();
533 *n = SeqExpr {
534 span: *span,
535 exprs: vec![arg.take(), true_expr],
536 }
537 .into()
538 }
539 }
540 }
541
542 Expr::Lit(Lit::Str(s)) => {
543 if !is_return_value_ignored {
544 report_change!("Converting string as boolean expressions");
545 self.changed = true;
546 *n = Lit::Num(Number {
547 span: s.span,
548 value: if s.value.is_empty() { 0.0 } else { 1.0 },
549 raw: None,
550 })
551 .into();
552 }
553 }
554
555 Expr::Lit(Lit::Num(num)) => {
556 if num.value == 1.0 || num.value == 0.0 {
557 return;
558 }
559 if self.options.bools {
560 report_change!("booleans: Converting number as boolean expressions");
561 self.changed = true;
562 *n = Lit::Num(Number {
563 span: num.span,
564 value: if num.value == 0.0 { 0.0 } else { 1.0 },
565 raw: None,
566 })
567 .into();
568 }
569 }
570
571 Expr::Bin(BinExpr {
572 op: op!("??"),
573 left,
574 right,
575 ..
576 }) => {
577 if let Value::Known(false) = right.as_pure_bool(self.expr_ctx) {
579 report_change!(
580 "Dropping right operand of `??` as it's always false (in bool context)"
581 );
582 self.changed = true;
583 *n = *left.take();
584 }
585 }
586
587 Expr::Bin(BinExpr {
588 op: op!("||"),
589 left,
590 right,
591 ..
592 }) => {
593 if let Value::Known(false) = right.as_pure_bool(self.expr_ctx) {
596 report_change!("bools: `expr || false` => `expr` (in bool context)");
597 self.changed = true;
598 *n = *left.take();
599 }
600 }
601
602 _ => {
603 let v = n.as_pure_bool(self.expr_ctx);
604 if let Value::Known(v) = v {
605 let span = n.span();
606 report_change!("Optimizing expr as {} (in bool context)", v);
607 *n = make_bool(span, v);
608 }
609 }
610 }
611 }
612
613 fn can_swap_bin_operands(&mut self, l: &Expr, r: &Expr, is_for_rel: bool) -> bool {
616 match (l, r) {
617 (Expr::Member(_), _) if is_for_rel => false,
618
619 (Expr::Update(..) | Expr::Assign(..), Expr::Lit(..)) if is_for_rel => false,
620
621 (Expr::Ident(..), Expr::Ident(Ident { sym: r_s, .. })) if &**r_s == "undefined" => true,
622
623 (
624 Expr::Member(..)
625 | Expr::Call(..)
626 | Expr::Assign(..)
627 | Expr::Update(..)
628 | Expr::Bin(BinExpr {
629 op: op!("&&") | op!("||"),
630 ..
631 }),
632 Expr::Lit(..),
633 ) => true,
634
635 (
636 Expr::Member(..) | Expr::Call(..) | Expr::Assign(..),
637 Expr::Unary(UnaryExpr {
638 op: op!("!"), arg, ..
639 }),
640 ) if matches!(&**arg, Expr::Lit(..)) => true,
641
642 (Expr::Member(..) | Expr::Call(..) | Expr::Assign(..), r)
643 if is_pure_undefined(self.expr_ctx, r) =>
644 {
645 true
646 }
647
648 (Expr::Ident(l), Expr::Ident(r)) => {
649 is_for_rel && self.options.unsafe_comps && l.sym < r.sym
650 }
651
652 (Expr::Ident(..), Expr::Lit(..)) if is_for_rel => false,
653
654 (Expr::Ident(..), Expr::Lit(..))
655 | (
656 Expr::Ident(..) | Expr::Member(..),
657 Expr::Unary(UnaryExpr {
658 op: op!("void") | op!("!"),
659 ..
660 }),
661 )
662 | (
663 Expr::This(..),
664 Expr::Unary(UnaryExpr {
665 op: op!("void"), ..
666 }),
667 )
668 | (Expr::Unary(..), Expr::Lit(..))
669 | (Expr::Tpl(..), Expr::Lit(..)) => true,
670 _ => false,
671 }
672 }
673
674 fn try_swap_bin(&mut self, op: BinaryOp, left: &mut Expr, right: &mut Expr) -> bool {
675 let can_swap = matches!(
676 op,
677 op!("===")
678 | op!("!==")
679 | op!("==")
680 | op!("!=")
681 | op!("&")
682 | op!("^")
683 | op!("|")
684 | op!("*")
685 ) && self.can_swap_bin_operands(left, right, false);
686
687 let can_swap = can_swap
689 || (matches!(op, op!("*") | op!("&") | op!("|") | op!("^"))
690 && right
691 .as_bin()
692 .filter(|b| b.op.precedence() == op.precedence())
693 .is_some()
694 && left
695 .as_bin()
696 .filter(|b| b.op.precedence() == op.precedence())
697 .is_none()
698 && !left.may_have_side_effects(self.expr_ctx)
699 && !right.may_have_side_effects(self.expr_ctx));
700
701 if can_swap {
702 report_change!("Swapping operands of binary expession");
703 swap(left, right);
704 }
705
706 can_swap
707 }
708
709 pub(super) fn swap_bin_operands(&mut self, expr: &mut Expr) {
711 match expr {
712 Expr::Bin(e @ BinExpr { op: op!("<="), .. })
713 | Expr::Bin(e @ BinExpr { op: op!("<"), .. }) => {
714 if self.options.comparisons && self.can_swap_bin_operands(&e.left, &e.right, true) {
715 self.changed = true;
716 report_change!("comparisons: Swapping operands of {}", e.op);
717
718 e.op = if e.op == op!("<=") {
719 op!(">=")
720 } else {
721 op!(">")
722 };
723
724 swap(&mut e.left, &mut e.right);
725 }
726 }
727
728 Expr::Bin(bin) => {
729 if self.try_swap_bin(bin.op, &mut bin.left, &mut bin.right) {
730 self.changed = true;
731 }
732 }
733 _ => {}
734 }
735 }
736}