1use std::{borrow::Cow, iter, iter::once};
2
3use swc_atoms::Atom;
4use swc_common::{
5 pass::{CompilerPass, Repeated},
6 util::take::Take,
7 Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
8};
9use swc_ecma_ast::*;
10use swc_ecma_transforms_base::{
11 ext::ExprRefExt,
12 perf::{cpu_count, Parallel, ParallelExt},
13};
14use swc_ecma_utils::{
15 is_literal, number::JsNumber, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType,
16 NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value,
17};
18use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
19use Value::{Known, Unknown};
20
21use crate::debug::debug_assert_valid;
22
23#[cfg(test)]
24mod tests;
25
26macro_rules! try_val {
27 ($v:expr) => {{
28 match $v {
29 Value::Known(v) => v,
30 Value::Unknown => return Value::Unknown,
31 }
32 }};
33}
34
35#[derive(Debug, Clone, Copy, Default, Hash)]
37pub struct Config {}
38
39pub fn expr_simplifier(
43 unresolved_mark: Mark,
44 config: Config,
45) -> impl Repeated + Pass + CompilerPass + VisitMut + 'static {
46 visit_mut_pass(SimplifyExpr {
47 expr_ctx: ExprCtx {
48 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
49 is_unresolved_ref_safe: false,
50 in_strict: false,
51 remaining_depth: 4,
52 },
53 config,
54 changed: false,
55 is_arg_of_update: false,
56 is_modifying: false,
57 in_callee: false,
58 })
59}
60
61impl Parallel for SimplifyExpr {
62 fn create(&self) -> Self {
63 Self { ..*self }
64 }
65
66 fn merge(&mut self, other: Self) {
67 self.changed |= other.changed;
68 }
69}
70
71#[derive(Debug)]
72struct SimplifyExpr {
73 expr_ctx: ExprCtx,
74 config: Config,
75
76 changed: bool,
77 is_arg_of_update: bool,
78 is_modifying: bool,
79 in_callee: bool,
80}
81
82impl CompilerPass for SimplifyExpr {
83 fn name(&self) -> Cow<'static, str> {
84 Cow::Borrowed("simplify-expr")
85 }
86}
87
88impl Repeated for SimplifyExpr {
89 fn changed(&self) -> bool {
90 self.changed
91 }
92
93 fn reset(&mut self) {
94 self.changed = false;
95 }
96}
97
98impl VisitMut for SimplifyExpr {
99 noop_visit_mut_type!();
100
101 fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
102 let old = self.is_modifying;
103 self.is_modifying = true;
104 n.left.visit_mut_with(self);
105 self.is_modifying = old;
106
107 self.is_modifying = false;
108 n.right.visit_mut_with(self);
109 self.is_modifying = old;
110 }
111
112 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
114 let old_in_callee = self.in_callee;
115
116 self.in_callee = true;
117 match &mut n.callee {
118 Callee::Super(..) | Callee::Import(..) => {}
119 Callee::Expr(e) => {
120 let may_inject_zero = !need_zero_for_this(e);
121
122 match &mut **e {
123 Expr::Seq(seq) => {
124 if seq.exprs.len() == 1 {
125 let mut expr = seq.exprs.take().into_iter().next().unwrap();
126 expr.visit_mut_with(self);
127 *e = expr;
128 } else if seq
129 .exprs
130 .last()
131 .map(|v| &**v)
132 .map_or(false, Expr::directness_matters)
133 {
134 match seq.exprs.first().map(|v| &**v) {
135 Some(Expr::Lit(..) | Expr::Ident(..)) => {}
136 _ => {
137 tracing::debug!("Injecting `0` to preserve `this = undefined`");
138 seq.exprs.insert(0, 0.0.into());
139 }
140 }
141
142 seq.visit_mut_with(self);
143 }
144 }
145
146 _ => {
147 e.visit_mut_with(self);
148 }
149 }
150
151 if may_inject_zero && need_zero_for_this(e) {
152 match &mut **e {
153 Expr::Seq(seq) => {
154 seq.exprs.insert(0, 0.into());
155 }
156 _ => {
157 let seq = SeqExpr {
158 span: DUMMY_SP,
159 exprs: vec![0.0.into(), e.take()],
160 };
161 **e = seq.into();
162 }
163 }
164 }
165 }
166 }
167
168 self.in_callee = false;
169 n.args.visit_mut_with(self);
170
171 self.in_callee = old_in_callee;
172 }
173
174 fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
175 self.maybe_par(cpu_count(), members, |v, member| {
176 member.visit_mut_with(v);
177 });
178 }
179
180 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
181 fn is_paren_wrap_fn_or_class(expr: &mut Expr, visitor: &mut SimplifyExpr) -> bool {
182 match &mut *expr {
183 Expr::Fn(..) | Expr::Class(..) => {
184 expr.visit_mut_children_with(visitor);
185 true
186 }
187 Expr::Paren(p) => is_paren_wrap_fn_or_class(&mut p.expr, visitor),
188 _ => false,
189 }
190 }
191
192 if !is_paren_wrap_fn_or_class(&mut expr.expr, self) {
193 expr.visit_mut_children_with(self);
194 }
195 }
196
197 fn visit_mut_expr(&mut self, expr: &mut Expr) {
198 if let Expr::Unary(UnaryExpr {
199 op: op!("delete"), ..
200 }) = expr
201 {
202 return;
203 }
204 expr.visit_mut_children_with(self);
206
207 match expr {
208 Expr::Lit(_) | Expr::This(..) | Expr::Paren(..) => return,
211
212 Expr::Seq(seq) if seq.exprs.is_empty() => return,
213
214 Expr::Unary(..)
215 | Expr::Bin(..)
216 | Expr::Member(..)
217 | Expr::Cond(..)
218 | Expr::Seq(..)
219 | Expr::Array(..)
220 | Expr::Object(..)
221 | Expr::New(..) => {}
222
223 _ => return,
224 }
225
226 match expr {
227 Expr::Unary(_) => {
228 optimize_unary_expr(self.expr_ctx, expr, &mut self.changed);
229 debug_assert_valid(expr);
230 }
231 Expr::Bin(_) => {
232 optimize_bin_expr(self.expr_ctx, expr, &mut self.changed);
233 if expr.is_seq() {
234 expr.visit_mut_with(self);
235 }
236
237 debug_assert_valid(expr);
238 }
239 Expr::Member(_) => {
240 if !self.is_modifying {
241 optimize_member_expr(self.expr_ctx, expr, self.in_callee, &mut self.changed);
242
243 debug_assert_valid(expr);
244 }
245 }
246
247 Expr::Cond(CondExpr {
248 span,
249 test,
250 cons,
251 alt,
252 }) => {
253 if let (p, Known(val)) = test.cast_to_bool(self.expr_ctx) {
254 self.changed = true;
255
256 let expr_value = if val { cons } else { alt };
257 *expr = if p.is_pure() {
258 if expr_value.directness_matters() {
259 SeqExpr {
260 span: *span,
261 exprs: vec![0.into(), expr_value.take()],
262 }
263 .into()
264 } else {
265 *expr_value.take()
266 }
267 } else {
268 SeqExpr {
269 span: *span,
270 exprs: vec![test.take(), expr_value.take()],
271 }
272 .into()
273 }
274 }
275 }
276
277 Expr::Seq(SeqExpr { exprs, .. }) => {
279 if exprs.len() == 1 {
280 *expr = *exprs.take().into_iter().next().unwrap()
282 } else {
283 assert!(!exprs.is_empty(), "sequence expression should not be empty");
284 }
286 }
287
288 Expr::Array(ArrayLit { elems, .. }) => {
289 let mut e = Vec::with_capacity(elems.len());
290
291 for elem in elems.take() {
292 match elem {
293 Some(ExprOrSpread {
294 spread: Some(..),
295 expr,
296 }) if expr.is_array() => {
297 self.changed = true;
298
299 e.extend(expr.array().unwrap().elems.into_iter().map(|elem| {
300 Some(elem.unwrap_or_else(|| ExprOrSpread {
301 spread: None,
302 expr: Expr::undefined(DUMMY_SP),
303 }))
304 }));
305 }
306
307 _ => e.push(elem),
308 }
309 }
310 *elems = e;
311 }
312
313 Expr::Object(ObjectLit { props, .. }) => {
314 let should_work = props.iter().any(|p| matches!(p, PropOrSpread::Spread(..)));
315 if !should_work {
316 return;
317 }
318
319 let mut ps = Vec::with_capacity(props.len());
320
321 for p in props.take() {
322 match p {
323 PropOrSpread::Spread(SpreadElement {
324 dot3_token, expr, ..
325 }) if expr.is_object() => {
326 if let Expr::Object(obj) = &*expr {
327 if obj.props.iter().any(|p| match p {
328 PropOrSpread::Spread(..) => true,
329 PropOrSpread::Prop(p) => !matches!(
330 &**p,
331 Prop::Shorthand(_) | Prop::KeyValue(_) | Prop::Method(_)
332 ),
333 }) {
334 ps.push(PropOrSpread::Spread(SpreadElement {
335 dot3_token,
336 expr,
337 }));
338 continue;
339 }
340 }
341 let props = expr.object().unwrap().props;
342 ps.extend(props);
343 self.changed = true;
344 }
345
346 _ => ps.push(p),
347 }
348 }
349 *props = ps;
350 }
351
352 _ => {}
354 };
355 }
356
357 fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
358 self.maybe_par(cpu_count(), n, |v, n| {
359 n.visit_mut_with(v);
360 });
361 }
362
363 fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
364 self.maybe_par(cpu_count(), n, |v, n| {
365 n.visit_mut_with(v);
366 });
367 }
368
369 fn visit_mut_for_head(&mut self, n: &mut ForHead) {
370 let old = self.is_modifying;
371 self.is_modifying = true;
372 n.visit_mut_children_with(self);
373 self.is_modifying = old;
374 }
375
376 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
377 let mut child = SimplifyExpr {
378 expr_ctx: self.expr_ctx,
379 config: self.config,
380 changed: Default::default(),
381 is_arg_of_update: Default::default(),
382 is_modifying: Default::default(),
383 in_callee: Default::default(),
384 };
385
386 child.maybe_par(cpu_count(), n, |v, n| {
387 n.visit_mut_with(v);
388 });
389 self.changed |= child.changed;
390 }
391
392 #[inline]
394 fn visit_mut_opt_chain_expr(&mut self, _: &mut OptChainExpr) {}
395
396 fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
397 if let Some(VarDeclOrExpr::Expr(e)) = n {
398 match &mut **e {
399 Expr::Seq(SeqExpr { exprs, .. }) if exprs.is_empty() => {
400 *n = None;
401 return;
402 }
403 _ => {}
404 }
405 }
406
407 n.visit_mut_children_with(self);
408 }
409
410 fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
411 self.maybe_par(cpu_count(), n, |v, n| {
412 n.visit_mut_with(v);
413 });
414 }
415
416 fn visit_mut_pat(&mut self, p: &mut Pat) {
417 let old_in_callee = self.in_callee;
418 self.in_callee = false;
419 p.visit_mut_children_with(self);
420 self.in_callee = old_in_callee;
421
422 if let Pat::Assign(a) = p {
423 if a.right.is_undefined(self.expr_ctx)
424 || match *a.right {
425 Expr::Unary(UnaryExpr {
426 op: op!("void"),
427 ref arg,
428 ..
429 }) => !arg.may_have_side_effects(self.expr_ctx),
430 _ => false,
431 }
432 {
433 self.changed = true;
434 *p = *a.left.take();
435 }
436 }
437 }
438
439 fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
440 self.maybe_par(cpu_count(), n, |v, n| {
441 n.visit_mut_with(v);
442 });
443 }
444
445 fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
447 if e.exprs.is_empty() {
448 return;
449 }
450
451 let old_in_callee = self.in_callee;
452 let len = e.exprs.len();
453 for (idx, e) in e.exprs.iter_mut().enumerate() {
454 if idx == len - 1 {
455 self.in_callee = old_in_callee;
456 } else {
457 self.in_callee = false;
458 }
459
460 e.visit_mut_with(self);
461 }
462 self.in_callee = old_in_callee;
463
464 let len = e.exprs.len();
465
466 let last_expr = e.exprs.pop().expect("SeqExpr.exprs must not be empty");
467
468 let mut exprs = Vec::with_capacity(e.exprs.len() + 1);
470
471 for expr in e.exprs.take() {
472 match *expr {
473 Expr::Lit(Lit::Num(n)) if self.in_callee && n.value == 0.0 => {
474 if exprs.is_empty() {
475 exprs.push(0.0.into());
476
477 tracing::trace!("expr_simplifier: Preserving first zero");
478 }
479 }
480
481 Expr::Lit(..) | Expr::Ident(..)
482 if self.in_callee && !expr.may_have_side_effects(self.expr_ctx) =>
483 {
484 if exprs.is_empty() {
485 self.changed = true;
486
487 exprs.push(0.0.into());
488
489 tracing::debug!("expr_simplifier: Injected first zero");
490 }
491 }
492
493 Expr::Lit(_) => {}
495
496 Expr::Array(ArrayLit { span, elems }) => {
498 let is_simple = elems
499 .iter()
500 .all(|elem| matches!(elem, None | Some(ExprOrSpread { spread: None, .. })));
501
502 if is_simple {
503 exprs.extend(elems.into_iter().flatten().map(|e| e.expr));
504 } else {
505 exprs.push(Box::new(ArrayLit { span, elems }.into()));
506 }
507 }
508
509 _ => exprs.push(expr),
511 }
512 }
513
514 exprs.push(last_expr);
515
516 self.changed |= len != exprs.len();
517
518 e.exprs = exprs;
519 }
520
521 fn visit_mut_stmt(&mut self, s: &mut Stmt) {
522 let old_is_modifying = self.is_modifying;
523 self.is_modifying = false;
524 let old_is_arg_of_update = self.is_arg_of_update;
525 self.is_arg_of_update = false;
526 s.visit_mut_children_with(self);
527 self.is_arg_of_update = old_is_arg_of_update;
528 self.is_modifying = old_is_modifying;
529
530 debug_assert_valid(s);
531 }
532
533 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
534 let mut child = SimplifyExpr {
535 expr_ctx: self.expr_ctx,
536 config: self.config,
537 changed: Default::default(),
538 is_arg_of_update: Default::default(),
539 is_modifying: Default::default(),
540 in_callee: Default::default(),
541 };
542
543 child.maybe_par(cpu_count(), n, |v, n| {
544 n.visit_mut_with(v);
545 });
546 self.changed |= child.changed;
547 }
548
549 fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
550 let old = self.in_callee;
551 self.in_callee = true;
552
553 n.tag.visit_mut_with(self);
554
555 self.in_callee = false;
556 n.tpl.visit_mut_with(self);
557
558 self.in_callee = old;
559 }
560
561 fn visit_mut_update_expr(&mut self, n: &mut UpdateExpr) {
562 let old = self.is_modifying;
563 self.is_modifying = true;
564 n.arg.visit_mut_with(self);
565 self.is_modifying = old;
566 }
567
568 fn visit_mut_with_stmt(&mut self, n: &mut WithStmt) {
569 n.obj.visit_mut_with(self);
570 }
571}
572
573fn make_bool_expr<I>(ctx: ExprCtx, span: Span, value: bool, orig: I) -> Box<Expr>
575where
576 I: IntoIterator<Item = Box<Expr>>,
577{
578 ctx.preserve_effects(span, Lit::Bool(Bool { value, span }).into(), orig)
579}
580
581fn nth_char(s: &str, mut idx: usize) -> Option<Cow<str>> {
582 if s.chars().any(|c| c.len_utf16() > 1) {
583 return None;
584 }
585
586 if !s.contains("\\ud") && !s.contains("\\uD") {
587 return Some(Cow::Owned(s.chars().nth(idx).unwrap().to_string()));
588 }
589
590 let mut iter = s.chars().peekable();
591
592 while let Some(c) = iter.next() {
593 if c == '\\' && iter.peek().copied() == Some('u') {
594 if idx == 0 {
595 let mut buf = String::new();
596 buf.push('\\');
597 buf.extend(iter.take(5));
598 return Some(Cow::Owned(buf));
599 } else {
600 for _ in 0..5 {
601 iter.next();
602 }
603 }
604 }
605
606 if idx == 0 {
607 return Some(Cow::Owned(c.to_string()));
608 }
609
610 idx -= 1;
611 }
612
613 unreachable!("string is too short")
614}
615
616fn need_zero_for_this(e: &Expr) -> bool {
617 e.directness_matters() || e.is_seq()
618}
619
620fn get_key_value(key: &str, props: &mut Vec<PropOrSpread>) -> Option<Box<Expr>> {
624 let has_spread = props.iter().any(|prop| prop.is_spread());
626
627 if has_spread {
628 return None;
629 }
630
631 for (i, prop) in props.iter_mut().enumerate().rev() {
632 let prop = match prop {
633 PropOrSpread::Prop(x) => &mut **x,
634 PropOrSpread::Spread(_) => unreachable!(),
635 };
636
637 match prop {
638 Prop::Shorthand(ident) if ident.sym == key => {
639 let prop = match props.remove(i) {
640 PropOrSpread::Prop(x) => *x,
641 _ => unreachable!(),
642 };
643 let ident = match prop {
644 Prop::Shorthand(x) => x,
645 _ => unreachable!(),
646 };
647 return Some(ident.into());
648 }
649
650 Prop::KeyValue(prop) => {
651 if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") {
652 let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else {
655 return None;
658 };
659
660 let v = get_key_value(key, props);
664 if v.is_some() {
665 return v;
666 }
667 } else if prop_name_eq(&prop.key, key) {
668 let prop = match props.remove(i) {
669 PropOrSpread::Prop(x) => *x,
670 _ => unreachable!(),
671 };
672 let prop = match prop {
673 Prop::KeyValue(x) => x,
674 _ => unreachable!(),
675 };
676 return Some(prop.value);
677 }
678 }
679
680 _ => {}
681 }
682 }
683
684 None
685}
686
687pub fn optimize_member_expr(
689 expr_ctx: ExprCtx,
690 expr: &mut Expr,
691 is_callee: bool,
692 changed: &mut bool,
693) {
694 let MemberExpr { obj, prop, .. } = match expr {
695 Expr::Member(member) => member,
696 _ => return,
697 };
698
699 #[derive(Clone, PartialEq)]
700 enum KnownOp {
701 Len,
703
704 Index(f64),
714
715 IndexStr(Atom),
717 }
718 let op = match prop {
719 MemberProp::Ident(IdentName { sym, .. }) if &**sym == "length" && !obj.is_object() => {
720 KnownOp::Len
721 }
722 MemberProp::Ident(IdentName { sym, .. }) => {
723 if is_callee {
724 return;
725 }
726
727 KnownOp::IndexStr(sym.clone())
728 }
729 MemberProp::Computed(ComputedPropName { expr, .. }) => {
730 if is_callee {
731 return;
732 }
733
734 if let Expr::Lit(Lit::Num(Number { value, .. })) = &**expr {
735 KnownOp::Index(*value)
737 } else if let Known(s) = expr.as_pure_string(expr_ctx) {
738 if s == "length" && !obj.is_object() {
739 KnownOp::Len
741 } else if let Ok(n) = s.parse::<f64>() {
742 KnownOp::Index(n)
744 } else {
745 KnownOp::IndexStr(s.into())
747 }
748 } else {
749 return;
750 }
751 }
752 _ => return,
753 };
754
755 match &mut **obj {
762 Expr::Lit(Lit::Str(Str { value, span, .. })) => match op {
763 KnownOp::Len => {
768 *changed = true;
769
770 *expr = Lit::Num(Number {
771 value: value.chars().map(|c| c.len_utf16()).sum::<usize>() as _,
772 span: *span,
773 raw: None,
774 })
775 .into();
776 }
777
778 KnownOp::Index(idx) => {
780 if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() {
781 return;
784 }
785
786 let Some(value) = nth_char(value, idx as _) else {
787 return;
788 };
789
790 *changed = true;
791
792 *expr = Lit::Str(Str {
793 raw: None,
794 value: value.into(),
795 span: *span,
796 })
797 .into()
798 }
799
800 KnownOp::IndexStr(..) => {}
804 },
805
806 Expr::Array(ArrayLit { elems, span }) => {
810 let has_spread = elems.iter().any(|elem| {
812 elem.as_ref()
813 .map(|elem| elem.spread.is_some())
814 .unwrap_or(false)
815 });
816
817 if has_spread {
818 return;
819 }
820
821 match op {
822 KnownOp::Len => {
823 let may_have_side_effects = elems
825 .iter()
826 .filter_map(|e| e.as_ref())
827 .any(|e| e.expr.may_have_side_effects(expr_ctx));
828
829 if may_have_side_effects {
830 return;
831 }
832
833 *changed = true;
835
836 *expr = Lit::Num(Number {
837 value: elems.len() as _,
838 span: *span,
839 raw: None,
840 })
841 .into();
842 }
843
844 KnownOp::Index(idx) => {
845 if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() {
848 return;
849 }
850
851 let after_has_side_effect =
853 elems
854 .iter()
855 .skip((idx as usize + 1) as _)
856 .any(|elem| match elem {
857 Some(elem) => elem.expr.may_have_side_effects(expr_ctx),
858 None => false,
859 });
860
861 if after_has_side_effect {
862 return;
863 }
864
865 *changed = true;
866
867 let before: Vec<Option<ExprOrSpread>> = elems.drain(..(idx as usize)).collect();
869 let mut iter = elems.take().into_iter();
870 let e = iter.next().flatten();
872 let after: Vec<Option<ExprOrSpread>> = iter.collect();
874
875 let v = match e {
877 None => Expr::undefined(*span),
878 Some(e) => e.expr,
879 };
880
881 let mut exprs = Vec::new();
883
884 for elem in before.into_iter().flatten() {
886 expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
887 }
888
889 let val = v;
891
892 for elem in after.into_iter().flatten() {
894 expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
895 }
896
897 if exprs.is_empty() && val.directness_matters() {
903 exprs.push(0.into());
904 }
905
906 exprs.push(val);
908 *expr = *Expr::from_exprs(exprs);
909 }
910
911 KnownOp::IndexStr(..) => {}
913 }
914 }
915
916 Expr::Object(ObjectLit { props, span }) => {
920 let key = match op {
922 KnownOp::Index(i) => Atom::from(i.to_string()),
923 KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key,
924 _ => return,
925 };
926
927 let Some(v) = get_key_value(&key, props) else {
930 return;
931 };
932
933 *changed = true;
934
935 *expr = *expr_ctx.preserve_effects(
936 *span,
937 v,
938 once(
939 ObjectLit {
940 props: props.take(),
941 span: *span,
942 }
943 .into(),
944 ),
945 );
946 }
947
948 _ => {}
949 }
950}
951
952pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
954 let BinExpr {
955 left,
956 op,
957 right,
958 span,
959 } = match expr {
960 Expr::Bin(bin) => bin,
961 _ => return,
962 };
963 let op = *op;
964
965 macro_rules! try_replace {
966 ($v:expr) => {{
967 match $v {
968 Known(v) => {
969 *changed = true;
971
972 *expr = *make_bool_expr(expr_ctx, *span, v, {
973 iter::once(left.take()).chain(iter::once(right.take()))
974 });
975 return;
976 }
977 _ => {}
978 }
979 }};
980 (number, $v:expr) => {{
981 match $v {
982 Known(v) => {
983 *changed = true;
984
985 let value_expr = if !v.is_nan() {
986 Expr::Lit(Lit::Num(Number {
987 value: v,
988 span: *span,
989 raw: None,
990 }))
991 } else {
992 Expr::Ident(Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt))
993 };
994
995 *expr = *expr_ctx.preserve_effects(*span, value_expr.into(), {
996 iter::once(left.take()).chain(iter::once(right.take()))
997 });
998 return;
999 }
1000 _ => {}
1001 }
1002 }};
1003 }
1004
1005 match op {
1006 op!(bin, "+") => {
1007 if left.is_str() || left.is_array_lit() || right.is_str() || right.is_array_lit() {
1009 if let (Known(l), Known(r)) = (
1010 left.as_pure_string(expr_ctx),
1011 right.as_pure_string(expr_ctx),
1012 ) {
1013 let mut l = l.into_owned();
1014
1015 l.push_str(&r);
1016
1017 *changed = true;
1018
1019 *expr = Lit::Str(Str {
1020 raw: None,
1021 value: l.into(),
1022 span: *span,
1023 })
1024 .into();
1025 return;
1026 }
1027 }
1028
1029 match expr.get_type(expr_ctx) {
1030 Known(StringType) => match expr {
1032 Expr::Bin(BinExpr {
1033 left, right, span, ..
1034 }) => {
1035 if !left.may_have_side_effects(expr_ctx)
1036 && !right.may_have_side_effects(expr_ctx)
1037 {
1038 if let (Known(l), Known(r)) = (
1039 left.as_pure_string(expr_ctx),
1040 right.as_pure_string(expr_ctx),
1041 ) {
1042 *changed = true;
1043
1044 let value = format!("{}{}", l, r);
1045
1046 *expr = Lit::Str(Str {
1047 raw: None,
1048 value: value.into(),
1049 span: *span,
1050 })
1051 .into();
1052 }
1053 }
1054 }
1055 _ => unreachable!(),
1056 },
1057 Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => {
1059 match expr {
1060 Expr::Bin(BinExpr {
1061 left, right, span, ..
1062 }) => {
1063 if let Known(v) = perform_arithmetic_op(expr_ctx, op, left, right) {
1064 *changed = true;
1065 let span = *span;
1066
1067 let value_expr = if !v.is_nan() {
1068 Lit::Num(Number {
1069 value: v,
1070 span,
1071 raw: None,
1072 })
1073 .into()
1074 } else {
1075 Ident::new("NaN".into(), span, expr_ctx.unresolved_ctxt).into()
1076 };
1077
1078 *expr = *expr_ctx.preserve_effects(
1079 span,
1080 value_expr,
1081 iter::once(left.take()).chain(iter::once(right.take())),
1082 );
1083 }
1084 }
1085 _ => unreachable!(),
1086 };
1087 }
1088 _ => {}
1089 }
1090
1091 }
1093
1094 op!("&&") | op!("||") => {
1095 if let (_, Known(val)) = left.cast_to_bool(expr_ctx) {
1096 let node = if op == op!("&&") {
1097 if val {
1098 right
1100 } else {
1101 *changed = true;
1102
1103 *expr = *left.take();
1105 return;
1106 }
1107 } else if val {
1108 *changed = true;
1109
1110 *expr = *(left.take());
1112 return;
1113 } else {
1114 right
1116 };
1117
1118 if !left.may_have_side_effects(expr_ctx) {
1119 *changed = true;
1120
1121 if node.directness_matters() {
1122 *expr = SeqExpr {
1123 span: node.span(),
1124 exprs: vec![0.into(), node.take()],
1125 }
1126 .into();
1127 } else {
1128 *expr = *node.take();
1129 }
1130 } else {
1131 *changed = true;
1132
1133 let seq = SeqExpr {
1134 span: *span,
1135 exprs: vec![left.take(), node.take()],
1136 };
1137
1138 *expr = seq.into()
1139 };
1140 }
1141 }
1142 op!("instanceof") => {
1143 fn is_non_obj(e: &Expr) -> bool {
1144 match e {
1145 Expr::Lit(Lit::Str { .. })
1147 | Expr::Lit(Lit::Num(..))
1148 | Expr::Lit(Lit::Null(..))
1149 | Expr::Lit(Lit::Bool(..)) => true,
1150 Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => true,
1151 Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => true,
1152 Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => true,
1153
1154 Expr::Unary(UnaryExpr {
1155 op: op!("!"),
1156 ref arg,
1157 ..
1158 })
1159 | Expr::Unary(UnaryExpr {
1160 op: op!(unary, "-"),
1161 ref arg,
1162 ..
1163 })
1164 | Expr::Unary(UnaryExpr {
1165 op: op!("void"),
1166 ref arg,
1167 ..
1168 }) => is_non_obj(arg),
1169 _ => false,
1170 }
1171 }
1172
1173 fn is_obj(e: &Expr) -> bool {
1174 matches!(
1175 *e,
1176 Expr::Array { .. } | Expr::Object { .. } | Expr::Fn { .. } | Expr::New { .. }
1177 )
1178 }
1179
1180 if is_non_obj(left) {
1182 *changed = true;
1183
1184 *expr = *make_bool_expr(expr_ctx, *span, false, iter::once(right.take()));
1185 return;
1186 }
1187
1188 if is_obj(left) && right.is_global_ref_to(expr_ctx, "Object") {
1189 *changed = true;
1190
1191 *expr = *make_bool_expr(expr_ctx, *span, true, iter::once(left.take()));
1192 }
1193 }
1194
1195 op!(bin, "-") | op!("/") | op!("%") | op!("**") => {
1197 try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right))
1198 }
1199
1200 op!("<<") | op!(">>") | op!(">>>") => {
1202 fn try_fold_shift(ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1203 if !left.is_number() || !right.is_number() {
1204 return Unknown;
1205 }
1206
1207 let (lv, rv) = match (left.as_pure_number(ctx), right.as_pure_number(ctx)) {
1208 (Known(lv), Known(rv)) => (lv, rv),
1209 _ => unreachable!(),
1210 };
1211 let (lv, rv) = (JsNumber::from(lv), JsNumber::from(rv));
1212
1213 Known(match op {
1214 op!("<<") => *(lv << rv),
1215 op!(">>") => *(lv >> rv),
1216 op!(">>>") => *(lv.unsigned_shr(rv)),
1217
1218 _ => unreachable!("Unknown bit operator {:?}", op),
1219 })
1220 }
1221 try_replace!(number, try_fold_shift(expr_ctx, op, left, right))
1222 }
1223
1224 op!("*") | op!("&") | op!("|") | op!("^") => {
1228 try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right));
1229
1230 if let Expr::Bin(BinExpr {
1232 span: _,
1233 left: left_lhs,
1234 op: left_op,
1235 right: left_rhs,
1236 }) = &mut **left
1237 {
1238 if *left_op == op {
1239 if let Known(value) = perform_arithmetic_op(expr_ctx, op, left_rhs, right) {
1240 let value_expr = if !value.is_nan() {
1241 Lit::Num(Number {
1242 value,
1243 span: *span,
1244 raw: None,
1245 })
1246 .into()
1247 } else {
1248 Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt).into()
1249 };
1250
1251 *changed = true;
1252 *left = left_lhs.take();
1253 *right = Box::new(value_expr);
1254 }
1255 }
1256 }
1257 }
1258
1259 op!("<") => {
1261 try_replace!(perform_abstract_rel_cmp(expr_ctx, left, right, false))
1262 }
1263 op!(">") => {
1264 try_replace!(perform_abstract_rel_cmp(expr_ctx, right, left, false))
1265 }
1266 op!("<=") => {
1267 try_replace!(!perform_abstract_rel_cmp(expr_ctx, right, left, true))
1268 }
1269 op!(">=") => {
1270 try_replace!(!perform_abstract_rel_cmp(expr_ctx, left, right, true))
1271 }
1272
1273 op!("==") => try_replace!(perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1274 op!("!=") => try_replace!(!perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1275 op!("===") => try_replace!(perform_strict_eq_cmp(expr_ctx, left, right)),
1276 op!("!==") => try_replace!(!perform_strict_eq_cmp(expr_ctx, left, right)),
1277 _ => {}
1278 };
1279}
1280
1281pub fn optimize_unary_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1283 let UnaryExpr { op, arg, span } = match expr {
1284 Expr::Unary(unary) => unary,
1285 _ => return,
1286 };
1287 let may_have_side_effects = arg.may_have_side_effects(expr_ctx);
1288
1289 match op {
1290 op!("typeof") if !may_have_side_effects => {
1291 try_fold_typeof(expr_ctx, expr, changed);
1292 }
1293 op!("!") => {
1294 match &**arg {
1295 Expr::Lit(Lit::Num(..)) => return,
1297
1298 Expr::Call(call) => {
1300 if let Callee::Expr(callee) = &call.callee {
1301 if let Expr::Fn(..) = &**callee {
1302 return;
1303 }
1304 }
1305 }
1306 _ => {}
1307 }
1308
1309 if let (_, Known(val)) = arg.cast_to_bool(expr_ctx) {
1310 *changed = true;
1311
1312 *expr = *make_bool_expr(expr_ctx, *span, !val, iter::once(arg.take()));
1313 }
1314 }
1315 op!(unary, "+") => {
1316 if let Known(v) = arg.as_pure_number(expr_ctx) {
1317 *changed = true;
1318
1319 if v.is_nan() {
1320 *expr = *expr_ctx.preserve_effects(
1321 *span,
1322 Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt).into(),
1323 iter::once(arg.take()),
1324 );
1325 return;
1326 }
1327
1328 *expr = *expr_ctx.preserve_effects(
1329 *span,
1330 Lit::Num(Number {
1331 value: v,
1332 span: *span,
1333 raw: None,
1334 })
1335 .into(),
1336 iter::once(arg.take()),
1337 );
1338 }
1339 }
1340 op!(unary, "-") => match &**arg {
1341 Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => {}
1342 Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => {
1344 *changed = true;
1345 *expr = *(arg.take());
1346 }
1347 Expr::Lit(Lit::Num(Number { value: f, .. })) => {
1348 *changed = true;
1349 *expr = Lit::Num(Number {
1350 value: -f,
1351 span: *span,
1352 raw: None,
1353 })
1354 .into();
1355 }
1356 _ => {
1357
1358 }
1361 },
1362 op!("void") if !may_have_side_effects => {
1363 match &**arg {
1364 Expr::Lit(Lit::Num(Number { value, .. })) if *value == 0.0 => return,
1365 _ => {}
1366 }
1367 *changed = true;
1368
1369 *arg = Lit::Num(Number {
1370 value: 0.0,
1371 span: arg.span(),
1372 raw: None,
1373 })
1374 .into();
1375 }
1376
1377 op!("~") => {
1378 if let Known(value) = arg.as_pure_number(expr_ctx) {
1379 if value.fract() == 0.0 {
1380 *changed = true;
1381 *expr = Lit::Num(Number {
1382 span: *span,
1383 value: if value < 0.0 {
1384 !(value as i32 as u32) as i32 as f64
1385 } else {
1386 !(value as u32) as i32 as f64
1387 },
1388 raw: None,
1389 })
1390 .into();
1391 }
1392 }
1394 }
1395 _ => {}
1396 }
1397}
1398
1399fn try_fold_typeof(_expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1405 let UnaryExpr { op, arg, span } = match expr {
1406 Expr::Unary(unary) => unary,
1407 _ => return,
1408 };
1409 assert_eq!(*op, op!("typeof"));
1410
1411 let val = match &**arg {
1412 Expr::Fn(..) => "function",
1413 Expr::Lit(Lit::Str { .. }) => "string",
1414 Expr::Lit(Lit::Num(..)) => "number",
1415 Expr::Lit(Lit::Bool(..)) => "boolean",
1416 Expr::Lit(Lit::Null(..)) | Expr::Object { .. } | Expr::Array { .. } => "object",
1417 Expr::Unary(UnaryExpr {
1418 op: op!("void"), ..
1419 }) => "undefined",
1420
1421 Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => {
1422 "undefined"
1425 }
1426
1427 _ => {
1428 return;
1429 }
1430 };
1431
1432 *changed = true;
1433
1434 *expr = Lit::Str(Str {
1435 span: *span,
1436 raw: None,
1437 value: val.into(),
1438 })
1439 .into();
1440}
1441
1442fn perform_arithmetic_op(expr_ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1444 macro_rules! try_replace {
1446 ($value:expr) => {{
1447 let (ls, rs) = (left.span(), right.span());
1448 if ls.is_dummy() || rs.is_dummy() {
1449 Known($value)
1450 } else {
1451 let new_len = format!("{}", $value).len();
1452 if right.span().hi() > left.span().lo() {
1453 let orig_len =
1454 right.span().hi() - right.span().lo() + left.span().hi() - left.span().lo();
1455 if new_len <= orig_len.0 as usize + 1 {
1456 Known($value)
1457 } else {
1458 Unknown
1459 }
1460 } else {
1461 Known($value)
1462 }
1463 }
1464 }};
1465 (i32, $value:expr) => {
1466 try_replace!($value as f64)
1467 };
1468 }
1469
1470 let (lv, rv) = (
1471 left.as_pure_number(expr_ctx),
1472 right.as_pure_number(expr_ctx),
1473 );
1474
1475 if (lv.is_unknown() && rv.is_unknown())
1476 || op == op!(bin, "+")
1477 && (!left.get_type(expr_ctx).casted_to_number_on_add()
1478 || !right.get_type(expr_ctx).casted_to_number_on_add())
1479 {
1480 return Unknown;
1481 }
1482
1483 match op {
1484 op!(bin, "+") => {
1485 if let (Known(lv), Known(rv)) = (lv, rv) {
1486 return try_replace!(lv + rv);
1487 }
1488
1489 if lv == Known(0.0) {
1490 return rv;
1491 } else if rv == Known(0.0) {
1492 return lv;
1493 }
1494
1495 return Unknown;
1496 }
1497 op!(bin, "-") => {
1498 if let (Known(lv), Known(rv)) = (lv, rv) {
1499 return try_replace!(lv - rv);
1500 }
1501
1502 if lv == Known(0.0) {
1504 return rv;
1505 }
1506
1507 if rv == Known(0.0) {
1509 return lv;
1510 }
1511
1512 return Unknown;
1513 }
1514 op!("*") => {
1515 if let (Known(lv), Known(rv)) = (lv, rv) {
1516 return try_replace!(lv * rv);
1517 }
1518 if Known(1.0) == lv {
1522 return rv;
1523 }
1524 if Known(1.0) == rv {
1525 return lv;
1526 }
1527
1528 return Unknown;
1529 }
1530
1531 op!("/") => {
1532 if let (Known(lv), Known(rv)) = (lv, rv) {
1533 if rv == 0.0 {
1534 return Unknown;
1535 }
1536 return try_replace!(lv / rv);
1537 }
1538
1539 if rv == Known(1.0) {
1542 return lv;
1545 }
1546 return Unknown;
1547 }
1548
1549 op!("**") => {
1550 if Known(0.0) == rv {
1551 return Known(1.0);
1552 }
1553
1554 if let (Known(lv), Known(rv)) = (lv, rv) {
1555 let lv: JsNumber = lv.into();
1556 let rv: JsNumber = rv.into();
1557 let result: f64 = lv.pow(rv).into();
1558 return try_replace!(result);
1559 }
1560
1561 return Unknown;
1562 }
1563 _ => {}
1564 }
1565 let (lv, rv) = match (lv, rv) {
1566 (Known(lv), Known(rv)) => (lv, rv),
1567 _ => return Unknown,
1568 };
1569
1570 match op {
1571 op!("&") => try_replace!(i32, to_int32(lv) & to_int32(rv)),
1572 op!("|") => try_replace!(i32, to_int32(lv) | to_int32(rv)),
1573 op!("^") => try_replace!(i32, to_int32(lv) ^ to_int32(rv)),
1574 op!("%") => {
1575 if rv == 0.0 {
1576 return Unknown;
1577 }
1578 try_replace!(lv % rv)
1579 }
1580 _ => unreachable!("unknown binary operator: {:?}", op),
1581 }
1582}
1583
1584fn perform_abstract_rel_cmp(
1588 expr_ctx: ExprCtx,
1589 left: &Expr,
1590 right: &Expr,
1591 will_negate: bool,
1592) -> Value<bool> {
1593 match (left, right) {
1594 (
1596 &Expr::Ident(
1597 Ident {
1598 sym: ref li,
1599 ctxt: l_ctxt,
1600 ..
1601 },
1602 ..,
1603 ),
1604 &Expr::Ident(Ident {
1605 sym: ref ri,
1606 ctxt: r_ctxt,
1607 ..
1608 }),
1609 ) if !will_negate && li == ri && l_ctxt == r_ctxt => {
1610 return Known(false);
1611 }
1612 (
1614 &Expr::Unary(UnaryExpr {
1615 op: op!("typeof"),
1616 arg: ref la,
1617 ..
1618 }),
1619 &Expr::Unary(UnaryExpr {
1620 op: op!("typeof"),
1621 arg: ref ra,
1622 ..
1623 }),
1624 ) if la.as_ident().is_some()
1625 && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1626 {
1627 return Known(false)
1628 }
1629 _ => {}
1630 }
1631
1632 let (lt, rt) = (left.get_type(expr_ctx), right.get_type(expr_ctx));
1634
1635 if let (Known(StringType), Known(StringType)) = (lt, rt) {
1636 if let (Known(lv), Known(rv)) = (
1637 left.as_pure_string(expr_ctx),
1638 right.as_pure_string(expr_ctx),
1639 ) {
1640 if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1643 return Unknown;
1644 } else {
1645 return Known(lv < rv);
1646 }
1647 }
1648 }
1649
1650 let (lv, rv) = (
1653 try_val!(left.as_pure_number(expr_ctx)),
1654 try_val!(right.as_pure_number(expr_ctx)),
1655 );
1656 if lv.is_nan() || rv.is_nan() {
1657 return Known(will_negate);
1658 }
1659
1660 Known(lv < rv)
1661}
1662
1663fn perform_abstract_eq_cmp(
1665 expr_ctx: ExprCtx,
1666 span: Span,
1667 left: &Expr,
1668 right: &Expr,
1669) -> Value<bool> {
1670 let (lt, rt) = (
1671 try_val!(left.get_type(expr_ctx)),
1672 try_val!(right.get_type(expr_ctx)),
1673 );
1674
1675 if lt == rt {
1676 return perform_strict_eq_cmp(expr_ctx, left, right);
1677 }
1678
1679 match (lt, rt) {
1680 (NullType, UndefinedType) | (UndefinedType, NullType) => Known(true),
1681 (NumberType, StringType) | (_, BoolType) => {
1682 let rv = try_val!(right.as_pure_number(expr_ctx));
1683 perform_abstract_eq_cmp(
1684 expr_ctx,
1685 span,
1686 left,
1687 &Lit::Num(Number {
1688 value: rv,
1689 span,
1690 raw: None,
1691 })
1692 .into(),
1693 )
1694 }
1695
1696 (StringType, NumberType) | (BoolType, _) => {
1697 let lv = try_val!(left.as_pure_number(expr_ctx));
1698 perform_abstract_eq_cmp(
1699 expr_ctx,
1700 span,
1701 &Lit::Num(Number {
1702 value: lv,
1703 span,
1704 raw: None,
1705 })
1706 .into(),
1707 right,
1708 )
1709 }
1710
1711 (StringType, ObjectType)
1712 | (NumberType, ObjectType)
1713 | (ObjectType, StringType)
1714 | (ObjectType, NumberType) => Unknown,
1715
1716 _ => Known(false),
1717 }
1718}
1719
1720fn perform_strict_eq_cmp(expr_ctx: ExprCtx, left: &Expr, right: &Expr) -> Value<bool> {
1722 if left.is_nan() || right.is_nan() {
1724 return Known(false);
1725 }
1726 match (left, right) {
1727 (
1729 &Expr::Unary(UnaryExpr {
1730 op: op!("typeof"),
1731 arg: ref la,
1732 ..
1733 }),
1734 &Expr::Unary(UnaryExpr {
1735 op: op!("typeof"),
1736 arg: ref ra,
1737 ..
1738 }),
1739 ) if la.as_ident().is_some()
1740 && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1741 {
1742 return Known(true)
1743 }
1744 _ => {}
1745 }
1746
1747 let (lt, rt) = (
1748 try_val!(left.get_type(expr_ctx)),
1749 try_val!(right.get_type(expr_ctx)),
1750 );
1751 if lt != rt {
1753 return Known(false);
1754 }
1755
1756 match lt {
1757 UndefinedType | NullType => Known(true),
1758 NumberType => Known(
1759 try_val!(left.as_pure_number(expr_ctx)) == try_val!(right.as_pure_number(expr_ctx)),
1760 ),
1761 StringType => {
1762 let (lv, rv) = (
1763 try_val!(left.as_pure_string(expr_ctx)),
1764 try_val!(right.as_pure_string(expr_ctx)),
1765 );
1766 if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1769 return Unknown;
1770 }
1771 Known(lv == rv)
1772 }
1773 BoolType => {
1774 let (lv, rv) = (left.as_pure_bool(expr_ctx), right.as_pure_bool(expr_ctx));
1775
1776 lv.and(rv).or((!lv).and(!rv))
1779 }
1780 ObjectType | SymbolType => Unknown,
1781 }
1782}