1use std::{borrow::Cow, iter, iter::once};
2
3use swc_atoms::{atom, 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 .is_some_and(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 #[cfg(swc_ast_unknown)]
167 _ => panic!("unable to access unknown nodes"),
168 }
169
170 self.in_callee = false;
171 n.args.visit_mut_with(self);
172
173 self.in_callee = old_in_callee;
174 }
175
176 fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
177 self.maybe_par(cpu_count(), members, |v, member| {
178 member.visit_mut_with(v);
179 });
180 }
181
182 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
183 fn is_paren_wrap_fn_or_class(expr: &mut Expr, visitor: &mut SimplifyExpr) -> bool {
184 match &mut *expr {
185 Expr::Fn(..) | Expr::Class(..) => {
186 expr.visit_mut_children_with(visitor);
187 true
188 }
189 Expr::Paren(p) => is_paren_wrap_fn_or_class(&mut p.expr, visitor),
190 _ => false,
191 }
192 }
193
194 if !is_paren_wrap_fn_or_class(&mut expr.expr, self) {
195 expr.visit_mut_children_with(self);
196 }
197 }
198
199 fn visit_mut_expr(&mut self, expr: &mut Expr) {
200 if let Expr::Unary(UnaryExpr {
201 op: op!("delete"), ..
202 }) = expr
203 {
204 return;
205 }
206 expr.visit_mut_children_with(self);
208
209 match expr {
210 Expr::Lit(_) | Expr::This(..) | Expr::Paren(..) => return,
213
214 Expr::Seq(seq) if seq.exprs.is_empty() => return,
215
216 Expr::Unary(..)
217 | Expr::Bin(..)
218 | Expr::Member(..)
219 | Expr::Cond(..)
220 | Expr::Seq(..)
221 | Expr::Array(..)
222 | Expr::Object(..)
223 | Expr::New(..) => {}
224
225 _ => return,
226 }
227
228 match expr {
229 Expr::Unary(_) => {
230 optimize_unary_expr(self.expr_ctx, expr, &mut self.changed);
231 debug_assert_valid(expr);
232 }
233 Expr::Bin(_) => {
234 optimize_bin_expr(self.expr_ctx, expr, &mut self.changed);
235 if expr.is_seq() {
236 expr.visit_mut_with(self);
237 }
238
239 debug_assert_valid(expr);
240 }
241 Expr::Member(_) => {
242 if !self.is_modifying {
243 optimize_member_expr(self.expr_ctx, expr, self.in_callee, &mut self.changed);
244
245 debug_assert_valid(expr);
246 }
247 }
248
249 Expr::Cond(CondExpr {
250 span,
251 test,
252 cons,
253 alt,
254 }) => {
255 if let (p, Known(val)) = test.cast_to_bool(self.expr_ctx) {
256 self.changed = true;
257
258 let expr_value = if val { cons } else { alt };
259 *expr = if p.is_pure() {
260 if expr_value.directness_matters() {
261 SeqExpr {
262 span: *span,
263 exprs: vec![0.into(), expr_value.take()],
264 }
265 .into()
266 } else {
267 *expr_value.take()
268 }
269 } else {
270 SeqExpr {
271 span: *span,
272 exprs: vec![test.take(), expr_value.take()],
273 }
274 .into()
275 }
276 }
277 }
278
279 Expr::Seq(SeqExpr { exprs, .. }) => {
281 if exprs.len() == 1 {
282 *expr = *exprs.take().into_iter().next().unwrap()
284 } else {
285 assert!(!exprs.is_empty(), "sequence expression should not be empty");
286 }
288 }
289
290 Expr::Array(ArrayLit { elems, .. }) => {
291 let mut e = Vec::with_capacity(elems.len());
292
293 for elem in elems.take() {
294 match elem {
295 Some(ExprOrSpread {
296 spread: Some(..),
297 expr,
298 }) if expr.is_array() => {
299 self.changed = true;
300
301 e.extend(expr.array().unwrap().elems.into_iter().map(|elem| {
302 Some(elem.unwrap_or_else(|| ExprOrSpread {
303 spread: None,
304 expr: Expr::undefined(DUMMY_SP),
305 }))
306 }));
307 }
308
309 _ => e.push(elem),
310 }
311 }
312 *elems = e;
313 }
314
315 Expr::Object(ObjectLit { props, .. }) => {
316 let should_work = props.iter().any(|p| matches!(p, PropOrSpread::Spread(..)));
317 if !should_work {
318 return;
319 }
320
321 let mut ps = Vec::with_capacity(props.len());
322
323 for p in props.take() {
324 match p {
325 PropOrSpread::Spread(SpreadElement {
326 dot3_token, expr, ..
327 }) if expr.is_object() => {
328 if let Expr::Object(obj) = &*expr {
329 if obj.props.iter().any(|p| match p {
330 PropOrSpread::Spread(..) => true,
331 PropOrSpread::Prop(p) => !matches!(
332 &**p,
333 Prop::Shorthand(_) | Prop::KeyValue(_) | Prop::Method(_)
334 ),
335 #[cfg(swc_ast_unknown)]
336 _ => panic!("unable to access unknown nodes"),
337 }) {
338 ps.push(PropOrSpread::Spread(SpreadElement {
339 dot3_token,
340 expr,
341 }));
342 continue;
343 }
344 }
345 let props = expr.object().unwrap().props;
346 ps.extend(props);
347 self.changed = true;
348 }
349
350 _ => ps.push(p),
351 }
352 }
353 *props = ps;
354 }
355
356 _ => {}
358 };
359 }
360
361 fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
362 self.maybe_par(cpu_count(), n, |v, n| {
363 n.visit_mut_with(v);
364 });
365 }
366
367 fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
368 self.maybe_par(cpu_count(), n, |v, n| {
369 n.visit_mut_with(v);
370 });
371 }
372
373 fn visit_mut_for_head(&mut self, n: &mut ForHead) {
374 let old = self.is_modifying;
375 self.is_modifying = true;
376 n.visit_mut_children_with(self);
377 self.is_modifying = old;
378 }
379
380 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
381 let mut child = SimplifyExpr {
382 expr_ctx: self.expr_ctx,
383 config: self.config,
384 changed: Default::default(),
385 is_arg_of_update: Default::default(),
386 is_modifying: Default::default(),
387 in_callee: Default::default(),
388 };
389
390 child.maybe_par(cpu_count(), n, |v, n| {
391 n.visit_mut_with(v);
392 });
393 self.changed |= child.changed;
394 }
395
396 #[inline]
398 fn visit_mut_opt_chain_expr(&mut self, _: &mut OptChainExpr) {}
399
400 fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
401 if let Some(VarDeclOrExpr::Expr(e)) = n {
402 match &mut **e {
403 Expr::Seq(SeqExpr { exprs, .. }) if exprs.is_empty() => {
404 *n = None;
405 return;
406 }
407 _ => {}
408 }
409 }
410
411 n.visit_mut_children_with(self);
412 }
413
414 fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
415 self.maybe_par(cpu_count(), n, |v, n| {
416 n.visit_mut_with(v);
417 });
418 }
419
420 fn visit_mut_pat(&mut self, p: &mut Pat) {
421 let old_in_callee = self.in_callee;
422 self.in_callee = false;
423 p.visit_mut_children_with(self);
424 self.in_callee = old_in_callee;
425
426 if let Pat::Assign(a) = p {
427 if a.right.is_undefined(self.expr_ctx)
428 || match *a.right {
429 Expr::Unary(UnaryExpr {
430 op: op!("void"),
431 ref arg,
432 ..
433 }) => !arg.may_have_side_effects(self.expr_ctx),
434 _ => false,
435 }
436 {
437 self.changed = true;
438 *p = *a.left.take();
439 }
440 }
441 }
442
443 fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
444 self.maybe_par(cpu_count(), n, |v, n| {
445 n.visit_mut_with(v);
446 });
447 }
448
449 fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
451 if e.exprs.is_empty() {
452 return;
453 }
454
455 let old_in_callee = self.in_callee;
456 let len = e.exprs.len();
457 for (idx, e) in e.exprs.iter_mut().enumerate() {
458 if idx == len - 1 {
459 self.in_callee = old_in_callee;
460 } else {
461 self.in_callee = false;
462 }
463
464 e.visit_mut_with(self);
465 }
466 self.in_callee = old_in_callee;
467
468 let len = e.exprs.len();
469
470 let last_expr = e.exprs.pop().expect("SeqExpr.exprs must not be empty");
471
472 let mut exprs = Vec::with_capacity(e.exprs.len() + 1);
474
475 for expr in e.exprs.take() {
476 match *expr {
477 Expr::Lit(Lit::Num(n)) if self.in_callee && n.value == 0.0 => {
478 if exprs.is_empty() {
479 exprs.push(0.0.into());
480
481 tracing::trace!("expr_simplifier: Preserving first zero");
482 }
483 }
484
485 Expr::Lit(..) | Expr::Ident(..)
486 if self.in_callee && !expr.may_have_side_effects(self.expr_ctx) =>
487 {
488 if exprs.is_empty() {
489 self.changed = true;
490
491 exprs.push(0.0.into());
492
493 tracing::debug!("expr_simplifier: Injected first zero");
494 }
495 }
496
497 Expr::Lit(_) => {}
499
500 Expr::Array(ArrayLit { span, elems }) => {
502 let is_simple = elems
503 .iter()
504 .all(|elem| matches!(elem, None | Some(ExprOrSpread { spread: None, .. })));
505
506 if is_simple {
507 exprs.extend(elems.into_iter().flatten().map(|e| e.expr));
508 } else {
509 exprs.push(Box::new(ArrayLit { span, elems }.into()));
510 }
511 }
512
513 _ => exprs.push(expr),
515 }
516 }
517
518 exprs.push(last_expr);
519
520 self.changed |= len != exprs.len();
521
522 e.exprs = exprs;
523 }
524
525 fn visit_mut_stmt(&mut self, s: &mut Stmt) {
526 let old_is_modifying = self.is_modifying;
527 self.is_modifying = false;
528 let old_is_arg_of_update = self.is_arg_of_update;
529 self.is_arg_of_update = false;
530 s.visit_mut_children_with(self);
531 self.is_arg_of_update = old_is_arg_of_update;
532 self.is_modifying = old_is_modifying;
533
534 debug_assert_valid(s);
535 }
536
537 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
538 let mut child = SimplifyExpr {
539 expr_ctx: self.expr_ctx,
540 config: self.config,
541 changed: Default::default(),
542 is_arg_of_update: Default::default(),
543 is_modifying: Default::default(),
544 in_callee: Default::default(),
545 };
546
547 child.maybe_par(cpu_count(), n, |v, n| {
548 n.visit_mut_with(v);
549 });
550 self.changed |= child.changed;
551 }
552
553 fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
554 let old = self.in_callee;
555 self.in_callee = true;
556
557 n.tag.visit_mut_with(self);
558
559 self.in_callee = false;
560 n.tpl.visit_mut_with(self);
561
562 self.in_callee = old;
563 }
564
565 fn visit_mut_update_expr(&mut self, n: &mut UpdateExpr) {
566 let old = self.is_modifying;
567 self.is_modifying = true;
568 n.arg.visit_mut_with(self);
569 self.is_modifying = old;
570 }
571
572 fn visit_mut_with_stmt(&mut self, n: &mut WithStmt) {
573 n.obj.visit_mut_with(self);
574 }
575}
576
577fn make_bool_expr<I>(ctx: ExprCtx, span: Span, value: bool, orig: I) -> Box<Expr>
579where
580 I: IntoIterator<Item = Box<Expr>>,
581{
582 ctx.preserve_effects(span, Lit::Bool(Bool { value, span }).into(), orig)
583}
584
585fn nth_char(s: &str, mut idx: usize) -> Option<Cow<str>> {
586 if s.chars().any(|c| c.len_utf16() > 1) {
587 return None;
588 }
589
590 if !s.contains("\\ud") && !s.contains("\\uD") {
591 return Some(Cow::Owned(s.chars().nth(idx).unwrap().to_string()));
592 }
593
594 let mut iter = s.chars().peekable();
595
596 while let Some(c) = iter.next() {
597 if c == '\\' && iter.peek().copied() == Some('u') {
598 if idx == 0 {
599 let mut buf = String::new();
600 buf.push('\\');
601 buf.extend(iter.take(5));
602 return Some(Cow::Owned(buf));
603 } else {
604 for _ in 0..5 {
605 iter.next();
606 }
607 }
608 }
609
610 if idx == 0 {
611 return Some(Cow::Owned(c.to_string()));
612 }
613
614 idx -= 1;
615 }
616
617 unreachable!("string is too short")
618}
619
620fn need_zero_for_this(e: &Expr) -> bool {
621 e.directness_matters() || e.is_seq()
622}
623
624fn get_key_value(key: &str, props: &mut Vec<PropOrSpread>) -> Option<Box<Expr>> {
628 let has_spread = props.iter().any(|prop| prop.is_spread());
630
631 if has_spread {
632 return None;
633 }
634
635 for (i, prop) in props.iter_mut().enumerate().rev() {
636 let prop = match prop {
637 PropOrSpread::Prop(x) => &mut **x,
638 PropOrSpread::Spread(_) => unreachable!(),
639 #[cfg(swc_ast_unknown)]
640 _ => panic!("unable to access unknown nodes"),
641 };
642
643 match prop {
644 Prop::Shorthand(ident) if ident.sym == key => {
645 let prop = match props.remove(i) {
646 PropOrSpread::Prop(x) => *x,
647 _ => unreachable!(),
648 };
649 let ident = match prop {
650 Prop::Shorthand(x) => x,
651 _ => unreachable!(),
652 };
653 return Some(ident.into());
654 }
655
656 Prop::KeyValue(prop) => {
657 if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") {
658 let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else {
661 return None;
664 };
665
666 let v = get_key_value(key, props);
670 if v.is_some() {
671 return v;
672 }
673 } else if prop_name_eq(&prop.key, key) {
674 let prop = match props.remove(i) {
675 PropOrSpread::Prop(x) => *x,
676 _ => unreachable!(),
677 };
678 let prop = match prop {
679 Prop::KeyValue(x) => x,
680 _ => unreachable!(),
681 };
682 return Some(prop.value);
683 }
684 }
685
686 _ => {}
687 }
688 }
689
690 None
691}
692
693pub fn optimize_member_expr(
695 expr_ctx: ExprCtx,
696 expr: &mut Expr,
697 is_callee: bool,
698 changed: &mut bool,
699) {
700 let MemberExpr { obj, prop, .. } = match expr {
701 Expr::Member(member) => member,
702 _ => return,
703 };
704
705 #[derive(Clone, PartialEq)]
706 enum KnownOp {
707 Len,
709
710 Index(f64),
720
721 IndexStr(Atom),
723 }
724 let op = match prop {
725 MemberProp::Ident(IdentName { sym, .. }) if &**sym == "length" && !obj.is_object() => {
726 KnownOp::Len
727 }
728 MemberProp::Ident(IdentName { sym, .. }) => {
729 if is_callee {
730 return;
731 }
732
733 KnownOp::IndexStr(sym.clone())
734 }
735 MemberProp::Computed(ComputedPropName { expr, .. }) => {
736 if is_callee {
737 return;
738 }
739
740 if let Expr::Lit(Lit::Num(Number { value, .. })) = &**expr {
741 KnownOp::Index(*value)
743 } else if let Known(s) = expr.as_pure_string(expr_ctx) {
744 if s == "length" && !obj.is_object() {
745 KnownOp::Len
747 } else if let Ok(n) = s.parse::<f64>() {
748 KnownOp::Index(n)
750 } else {
751 KnownOp::IndexStr(s.into())
753 }
754 } else {
755 return;
756 }
757 }
758 _ => return,
759 };
760
761 match &mut **obj {
768 Expr::Lit(Lit::Str(Str { value, span, .. })) => match op {
769 KnownOp::Len => {
774 *changed = true;
775
776 *expr = Lit::Num(Number {
777 value: value.chars().map(|c| c.len_utf16()).sum::<usize>() as _,
778 span: *span,
779 raw: None,
780 })
781 .into();
782 }
783
784 KnownOp::Index(idx) => {
786 if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() {
787 return;
790 }
791
792 let Some(value) = nth_char(value, idx as _) else {
793 return;
794 };
795
796 *changed = true;
797
798 *expr = Lit::Str(Str {
799 raw: None,
800 value: value.into(),
801 span: *span,
802 })
803 .into()
804 }
805
806 KnownOp::IndexStr(..) => {}
810 },
811
812 Expr::Array(ArrayLit { elems, span }) => {
816 let has_spread = elems.iter().any(|elem| {
818 elem.as_ref()
819 .map(|elem| elem.spread.is_some())
820 .unwrap_or(false)
821 });
822
823 if has_spread {
824 return;
825 }
826
827 match op {
828 KnownOp::Len => {
829 let may_have_side_effects = elems
831 .iter()
832 .filter_map(|e| e.as_ref())
833 .any(|e| e.expr.may_have_side_effects(expr_ctx));
834
835 if may_have_side_effects {
836 return;
837 }
838
839 *changed = true;
841
842 *expr = Lit::Num(Number {
843 value: elems.len() as _,
844 span: *span,
845 raw: None,
846 })
847 .into();
848 }
849
850 KnownOp::Index(idx) => {
851 if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() {
854 return;
855 }
856
857 let after_has_side_effect =
859 elems
860 .iter()
861 .skip((idx as usize + 1) as _)
862 .any(|elem| match elem {
863 Some(elem) => elem.expr.may_have_side_effects(expr_ctx),
864 None => false,
865 });
866
867 if after_has_side_effect {
868 return;
869 }
870
871 *changed = true;
872
873 let before: Vec<Option<ExprOrSpread>> = elems.drain(..(idx as usize)).collect();
875 let mut iter = elems.take().into_iter();
876 let e = iter.next().flatten();
878 let after: Vec<Option<ExprOrSpread>> = iter.collect();
880
881 let v = match e {
883 None => Expr::undefined(*span),
884 Some(e) => e.expr,
885 };
886
887 let mut exprs = Vec::new();
889
890 for elem in before.into_iter().flatten() {
892 expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
893 }
894
895 let val = v;
897
898 for elem in after.into_iter().flatten() {
900 expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
901 }
902
903 if exprs.is_empty() && val.directness_matters() {
909 exprs.push(0.into());
910 }
911
912 exprs.push(val);
914 *expr = *Expr::from_exprs(exprs);
915 }
916
917 KnownOp::IndexStr(..) => {}
919 }
920 }
921
922 Expr::Object(ObjectLit { props, span }) => {
926 let key = match op {
928 KnownOp::Index(i) => Atom::from(i.to_string()),
929 KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key,
930 _ => return,
931 };
932
933 let Some(v) = get_key_value(&key, props) else {
936 return;
937 };
938
939 *changed = true;
940
941 *expr = *expr_ctx.preserve_effects(
942 *span,
943 v,
944 once(
945 ObjectLit {
946 props: props.take(),
947 span: *span,
948 }
949 .into(),
950 ),
951 );
952 }
953
954 _ => {}
955 }
956}
957
958pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
960 let BinExpr {
961 left,
962 op,
963 right,
964 span,
965 } = match expr {
966 Expr::Bin(bin) => bin,
967 _ => return,
968 };
969 let op = *op;
970
971 macro_rules! try_replace {
972 ($v:expr) => {{
973 match $v {
974 Known(v) => {
975 *changed = true;
977
978 *expr = *make_bool_expr(expr_ctx, *span, v, {
979 iter::once(left.take()).chain(iter::once(right.take()))
980 });
981 return;
982 }
983 _ => {}
984 }
985 }};
986 (number, $v:expr) => {{
987 match $v {
988 Known(v) => {
989 *changed = true;
990
991 let value_expr = if !v.is_nan() {
992 Expr::Lit(Lit::Num(Number {
993 value: v,
994 span: *span,
995 raw: None,
996 }))
997 } else {
998 Expr::Ident(Ident::new(atom!("NaN"), *span, expr_ctx.unresolved_ctxt))
999 };
1000
1001 *expr = *expr_ctx.preserve_effects(*span, value_expr.into(), {
1002 iter::once(left.take()).chain(iter::once(right.take()))
1003 });
1004 return;
1005 }
1006 _ => {}
1007 }
1008 }};
1009 }
1010
1011 match op {
1012 op!(bin, "+") => {
1013 if let (Known(l), Known(r)) = (
1015 left.as_pure_string(expr_ctx),
1016 right.as_pure_string(expr_ctx),
1017 ) {
1018 if left.is_str() || left.is_array_lit() || right.is_str() || right.is_array_lit() {
1019 let mut l = l.into_owned();
1020
1021 l.push_str(&r);
1022
1023 *changed = true;
1024
1025 *expr = Lit::Str(Str {
1026 raw: None,
1027 value: l.into(),
1028 span: *span,
1029 })
1030 .into();
1031 return;
1032 }
1033 }
1034
1035 match expr.get_type(expr_ctx) {
1036 Known(StringType) => match expr {
1038 Expr::Bin(BinExpr {
1039 left, right, span, ..
1040 }) => {
1041 if !left.may_have_side_effects(expr_ctx)
1042 && !right.may_have_side_effects(expr_ctx)
1043 {
1044 if let (Known(l), Known(r)) = (
1045 left.as_pure_string(expr_ctx),
1046 right.as_pure_string(expr_ctx),
1047 ) {
1048 *changed = true;
1049
1050 let value = format!("{l}{r}");
1051
1052 *expr = Lit::Str(Str {
1053 raw: None,
1054 value: value.into(),
1055 span: *span,
1056 })
1057 .into();
1058 }
1059 }
1060 }
1061 _ => unreachable!(),
1062 },
1063 Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => {
1065 match expr {
1066 Expr::Bin(BinExpr {
1067 left, right, span, ..
1068 }) => {
1069 if let Known(v) = perform_arithmetic_op(expr_ctx, op, left, right) {
1070 *changed = true;
1071 let span = *span;
1072
1073 let value_expr = if !v.is_nan() {
1074 Lit::Num(Number {
1075 value: v,
1076 span,
1077 raw: None,
1078 })
1079 .into()
1080 } else {
1081 Ident::new(atom!("NaN"), span, expr_ctx.unresolved_ctxt).into()
1082 };
1083
1084 *expr = *expr_ctx.preserve_effects(
1085 span,
1086 value_expr,
1087 iter::once(left.take()).chain(iter::once(right.take())),
1088 );
1089 }
1090 }
1091 _ => unreachable!(),
1092 };
1093 }
1094 _ => {}
1095 }
1096
1097 }
1099
1100 op!("&&") | op!("||") => {
1101 if let (_, Known(val)) = left.cast_to_bool(expr_ctx) {
1102 let node = if op == op!("&&") {
1103 if val {
1104 right
1106 } else {
1107 *changed = true;
1108
1109 *expr = *left.take();
1111 return;
1112 }
1113 } else if val {
1114 *changed = true;
1115
1116 *expr = *(left.take());
1118 return;
1119 } else {
1120 right
1122 };
1123
1124 if !left.may_have_side_effects(expr_ctx) {
1125 *changed = true;
1126
1127 if node.directness_matters() {
1128 *expr = SeqExpr {
1129 span: node.span(),
1130 exprs: vec![0.into(), node.take()],
1131 }
1132 .into();
1133 } else {
1134 *expr = *node.take();
1135 }
1136 } else {
1137 *changed = true;
1138
1139 let seq = SeqExpr {
1140 span: *span,
1141 exprs: vec![left.take(), node.take()],
1142 };
1143
1144 *expr = seq.into()
1145 };
1146 }
1147 }
1148 op!("instanceof") => {
1149 fn is_non_obj(e: &Expr) -> bool {
1150 match e {
1151 Expr::Lit(Lit::Str { .. })
1153 | Expr::Lit(Lit::Num(..))
1154 | Expr::Lit(Lit::Null(..))
1155 | Expr::Lit(Lit::Bool(..)) => true,
1156 Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => true,
1157 Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => true,
1158 Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => true,
1159
1160 Expr::Unary(UnaryExpr {
1161 op: op!("!"),
1162 ref arg,
1163 ..
1164 })
1165 | Expr::Unary(UnaryExpr {
1166 op: op!(unary, "-"),
1167 ref arg,
1168 ..
1169 })
1170 | Expr::Unary(UnaryExpr {
1171 op: op!("void"),
1172 ref arg,
1173 ..
1174 }) => is_non_obj(arg),
1175 _ => false,
1176 }
1177 }
1178
1179 fn is_obj(e: &Expr) -> bool {
1180 matches!(
1181 *e,
1182 Expr::Array { .. } | Expr::Object { .. } | Expr::Fn { .. } | Expr::New { .. }
1183 )
1184 }
1185
1186 if is_non_obj(left) {
1188 *changed = true;
1189
1190 *expr = *make_bool_expr(expr_ctx, *span, false, iter::once(right.take()));
1191 return;
1192 }
1193
1194 if is_obj(left) && right.is_global_ref_to(expr_ctx, "Object") {
1195 *changed = true;
1196
1197 *expr = *make_bool_expr(expr_ctx, *span, true, iter::once(left.take()));
1198 }
1199 }
1200
1201 op!(bin, "-") | op!("/") | op!("%") | op!("**") => {
1203 try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right))
1204 }
1205
1206 op!("<<") | op!(">>") | op!(">>>") => {
1208 fn try_fold_shift(ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1209 if !left.is_number() || !right.is_number() {
1210 return Unknown;
1211 }
1212
1213 let (lv, rv) = match (left.as_pure_number(ctx), right.as_pure_number(ctx)) {
1214 (Known(lv), Known(rv)) => (lv, rv),
1215 _ => unreachable!(),
1216 };
1217 let (lv, rv) = (JsNumber::from(lv), JsNumber::from(rv));
1218
1219 Known(match op {
1220 op!("<<") => *(lv << rv),
1221 op!(">>") => *(lv >> rv),
1222 op!(">>>") => *(lv.unsigned_shr(rv)),
1223
1224 _ => unreachable!("Unknown bit operator {:?}", op),
1225 })
1226 }
1227 try_replace!(number, try_fold_shift(expr_ctx, op, left, right))
1228 }
1229
1230 op!("*") | op!("&") | op!("|") | op!("^") => {
1234 try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right));
1235
1236 if let Expr::Bin(BinExpr {
1238 span: _,
1239 left: left_lhs,
1240 op: left_op,
1241 right: left_rhs,
1242 }) = &mut **left
1243 {
1244 if *left_op == op {
1245 if let Known(value) = perform_arithmetic_op(expr_ctx, op, left_rhs, right) {
1246 let value_expr = if !value.is_nan() {
1247 Lit::Num(Number {
1248 value,
1249 span: *span,
1250 raw: None,
1251 })
1252 .into()
1253 } else {
1254 Ident::new(atom!("NaN"), *span, expr_ctx.unresolved_ctxt).into()
1255 };
1256
1257 *changed = true;
1258 *left = left_lhs.take();
1259 *right = Box::new(value_expr);
1260 }
1261 }
1262 }
1263 }
1264
1265 op!("<") => {
1267 try_replace!(perform_abstract_rel_cmp(expr_ctx, left, right, false))
1268 }
1269 op!(">") => {
1270 try_replace!(perform_abstract_rel_cmp(expr_ctx, right, left, false))
1271 }
1272 op!("<=") => {
1273 try_replace!(!perform_abstract_rel_cmp(expr_ctx, right, left, true))
1274 }
1275 op!(">=") => {
1276 try_replace!(!perform_abstract_rel_cmp(expr_ctx, left, right, true))
1277 }
1278
1279 op!("==") => try_replace!(perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1280 op!("!=") => try_replace!(!perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1281 op!("===") => try_replace!(perform_strict_eq_cmp(expr_ctx, left, right)),
1282 op!("!==") => try_replace!(!perform_strict_eq_cmp(expr_ctx, left, right)),
1283 _ => {}
1284 };
1285}
1286
1287pub fn optimize_unary_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1289 let UnaryExpr { op, arg, span } = match expr {
1290 Expr::Unary(unary) => unary,
1291 _ => return,
1292 };
1293 let may_have_side_effects = arg.may_have_side_effects(expr_ctx);
1294
1295 match op {
1296 op!("typeof") if !may_have_side_effects => {
1297 try_fold_typeof(expr_ctx, expr, changed);
1298 }
1299 op!("!") => {
1300 match &**arg {
1301 Expr::Lit(Lit::Num(..)) => return,
1303
1304 Expr::Call(call) => {
1306 if let Callee::Expr(callee) = &call.callee {
1307 if let Expr::Fn(..) = &**callee {
1308 return;
1309 }
1310 }
1311 }
1312 _ => {}
1313 }
1314
1315 if let (_, Known(val)) = arg.cast_to_bool(expr_ctx) {
1316 *changed = true;
1317
1318 *expr = *make_bool_expr(expr_ctx, *span, !val, iter::once(arg.take()));
1319 }
1320 }
1321 op!(unary, "+") => {
1322 if let Known(v) = arg.as_pure_number(expr_ctx) {
1323 *changed = true;
1324
1325 if v.is_nan() {
1326 *expr = *expr_ctx.preserve_effects(
1327 *span,
1328 Ident::new(atom!("NaN"), *span, expr_ctx.unresolved_ctxt).into(),
1329 iter::once(arg.take()),
1330 );
1331 return;
1332 }
1333
1334 *expr = *expr_ctx.preserve_effects(
1335 *span,
1336 Lit::Num(Number {
1337 value: v,
1338 span: *span,
1339 raw: None,
1340 })
1341 .into(),
1342 iter::once(arg.take()),
1343 );
1344 }
1345 }
1346 op!(unary, "-") => match &**arg {
1347 Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => {}
1348 Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => {
1350 *changed = true;
1351 *expr = *(arg.take());
1352 }
1353 Expr::Lit(Lit::Num(Number { value: f, .. })) => {
1354 *changed = true;
1355 *expr = Lit::Num(Number {
1356 value: -f,
1357 span: *span,
1358 raw: None,
1359 })
1360 .into();
1361 }
1362 _ => {
1363
1364 }
1367 },
1368 op!("void") if !may_have_side_effects => {
1369 match &**arg {
1370 Expr::Lit(Lit::Num(Number { value, .. })) if *value == 0.0 => return,
1371 _ => {}
1372 }
1373 *changed = true;
1374
1375 *arg = Lit::Num(Number {
1376 value: 0.0,
1377 span: arg.span(),
1378 raw: None,
1379 })
1380 .into();
1381 }
1382
1383 op!("~") => {
1384 if let Known(value) = arg.as_pure_number(expr_ctx) {
1385 if value.fract() == 0.0 {
1386 *changed = true;
1387 *expr = Lit::Num(Number {
1388 span: *span,
1389 value: if value < 0.0 {
1390 !(value as i32 as u32) as i32 as f64
1391 } else {
1392 !(value as u32) as i32 as f64
1393 },
1394 raw: None,
1395 })
1396 .into();
1397 }
1398 }
1400 }
1401 _ => {}
1402 }
1403}
1404
1405fn try_fold_typeof(_expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1411 let UnaryExpr { op, arg, span } = match expr {
1412 Expr::Unary(unary) => unary,
1413 _ => return,
1414 };
1415 assert_eq!(*op, op!("typeof"));
1416
1417 let val = match &**arg {
1418 Expr::Fn(..) => "function",
1419 Expr::Lit(Lit::Str { .. }) => "string",
1420 Expr::Lit(Lit::Num(..)) => "number",
1421 Expr::Lit(Lit::Bool(..)) => "boolean",
1422 Expr::Lit(Lit::Null(..)) | Expr::Object { .. } | Expr::Array { .. } => "object",
1423 Expr::Unary(UnaryExpr {
1424 op: op!("void"), ..
1425 }) => "undefined",
1426
1427 Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => {
1428 "undefined"
1431 }
1432
1433 _ => {
1434 return;
1435 }
1436 };
1437
1438 *changed = true;
1439
1440 *expr = Lit::Str(Str {
1441 span: *span,
1442 raw: None,
1443 value: val.into(),
1444 })
1445 .into();
1446}
1447
1448fn perform_arithmetic_op(expr_ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1450 macro_rules! try_replace {
1452 ($value:expr) => {{
1453 let (ls, rs) = (left.span(), right.span());
1454 if ls.is_dummy() || rs.is_dummy() {
1455 Known($value)
1456 } else {
1457 let new_len = format!("{}", $value).len();
1458 if right.span().hi() > left.span().lo() {
1459 let orig_len =
1460 right.span().hi() - right.span().lo() + left.span().hi() - left.span().lo();
1461 if new_len <= orig_len.0 as usize + 1 {
1462 Known($value)
1463 } else {
1464 Unknown
1465 }
1466 } else {
1467 Known($value)
1468 }
1469 }
1470 }};
1471 (i32, $value:expr) => {
1472 try_replace!($value as f64)
1473 };
1474 }
1475
1476 let (lv, rv) = (
1477 left.as_pure_number(expr_ctx),
1478 right.as_pure_number(expr_ctx),
1479 );
1480
1481 if (lv.is_unknown() && rv.is_unknown())
1482 || op == op!(bin, "+")
1483 && (!left.get_type(expr_ctx).casted_to_number_on_add()
1484 || !right.get_type(expr_ctx).casted_to_number_on_add())
1485 {
1486 return Unknown;
1487 }
1488
1489 match op {
1490 op!(bin, "+") => {
1491 if let (Known(lv), Known(rv)) = (lv, rv) {
1492 return try_replace!(lv + rv);
1493 }
1494
1495 if lv == Known(0.0) {
1496 return rv;
1497 } else if rv == Known(0.0) {
1498 return lv;
1499 }
1500
1501 return Unknown;
1502 }
1503 op!(bin, "-") => {
1504 if let (Known(lv), Known(rv)) = (lv, rv) {
1505 return try_replace!(lv - rv);
1506 }
1507
1508 if lv == Known(0.0) {
1510 return rv;
1511 }
1512
1513 if rv == Known(0.0) {
1515 return lv;
1516 }
1517
1518 return Unknown;
1519 }
1520 op!("*") => {
1521 if let (Known(lv), Known(rv)) = (lv, rv) {
1522 return try_replace!(lv * rv);
1523 }
1524 if Known(1.0) == lv {
1528 return rv;
1529 }
1530 if Known(1.0) == rv {
1531 return lv;
1532 }
1533
1534 return Unknown;
1535 }
1536
1537 op!("/") => {
1538 if let (Known(lv), Known(rv)) = (lv, rv) {
1539 if rv == 0.0 {
1540 return Unknown;
1541 }
1542 return try_replace!(lv / rv);
1543 }
1544
1545 if rv == Known(1.0) {
1548 return lv;
1551 }
1552 return Unknown;
1553 }
1554
1555 op!("**") => {
1556 if Known(0.0) == rv {
1557 return Known(1.0);
1558 }
1559
1560 if let (Known(lv), Known(rv)) = (lv, rv) {
1561 let lv: JsNumber = lv.into();
1562 let rv: JsNumber = rv.into();
1563 let result: f64 = lv.pow(rv).into();
1564 return try_replace!(result);
1565 }
1566
1567 return Unknown;
1568 }
1569 _ => {}
1570 }
1571 let (lv, rv) = match (lv, rv) {
1572 (Known(lv), Known(rv)) => (lv, rv),
1573 _ => return Unknown,
1574 };
1575
1576 match op {
1577 op!("&") => try_replace!(i32, to_int32(lv) & to_int32(rv)),
1578 op!("|") => try_replace!(i32, to_int32(lv) | to_int32(rv)),
1579 op!("^") => try_replace!(i32, to_int32(lv) ^ to_int32(rv)),
1580 op!("%") => {
1581 if rv == 0.0 {
1582 return Unknown;
1583 }
1584 try_replace!(lv % rv)
1585 }
1586 _ => unreachable!("unknown binary operator: {:?}", op),
1587 }
1588}
1589
1590fn perform_abstract_rel_cmp(
1594 expr_ctx: ExprCtx,
1595 left: &Expr,
1596 right: &Expr,
1597 will_negate: bool,
1598) -> Value<bool> {
1599 match (left, right) {
1600 (
1602 &Expr::Ident(
1603 Ident {
1604 sym: ref li,
1605 ctxt: l_ctxt,
1606 ..
1607 },
1608 ..,
1609 ),
1610 &Expr::Ident(Ident {
1611 sym: ref ri,
1612 ctxt: r_ctxt,
1613 ..
1614 }),
1615 ) if !will_negate && li == ri && l_ctxt == r_ctxt => {
1616 return Known(false);
1617 }
1618 (
1620 &Expr::Unary(UnaryExpr {
1621 op: op!("typeof"),
1622 arg: ref la,
1623 ..
1624 }),
1625 &Expr::Unary(UnaryExpr {
1626 op: op!("typeof"),
1627 arg: ref ra,
1628 ..
1629 }),
1630 ) if la.as_ident().is_some()
1631 && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1632 {
1633 return Known(false)
1634 }
1635 _ => {}
1636 }
1637
1638 let (lt, rt) = (left.get_type(expr_ctx), right.get_type(expr_ctx));
1640
1641 if let (Known(StringType), Known(StringType)) = (lt, rt) {
1642 if let (Known(lv), Known(rv)) = (
1643 left.as_pure_string(expr_ctx),
1644 right.as_pure_string(expr_ctx),
1645 ) {
1646 if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1649 return Unknown;
1650 } else {
1651 return Known(lv < rv);
1652 }
1653 }
1654 }
1655
1656 let (lv, rv) = (
1659 try_val!(left.as_pure_number(expr_ctx)),
1660 try_val!(right.as_pure_number(expr_ctx)),
1661 );
1662 if lv.is_nan() || rv.is_nan() {
1663 return Known(will_negate);
1664 }
1665
1666 Known(lv < rv)
1667}
1668
1669fn perform_abstract_eq_cmp(
1671 expr_ctx: ExprCtx,
1672 span: Span,
1673 left: &Expr,
1674 right: &Expr,
1675) -> Value<bool> {
1676 let (lt, rt) = (
1677 try_val!(left.get_type(expr_ctx)),
1678 try_val!(right.get_type(expr_ctx)),
1679 );
1680
1681 if lt == rt {
1682 return perform_strict_eq_cmp(expr_ctx, left, right);
1683 }
1684
1685 match (lt, rt) {
1686 (NullType, UndefinedType) | (UndefinedType, NullType) => Known(true),
1687 (NumberType, StringType) | (_, BoolType) => {
1688 let rv = try_val!(right.as_pure_number(expr_ctx));
1689 perform_abstract_eq_cmp(
1690 expr_ctx,
1691 span,
1692 left,
1693 &Lit::Num(Number {
1694 value: rv,
1695 span,
1696 raw: None,
1697 })
1698 .into(),
1699 )
1700 }
1701
1702 (StringType, NumberType) | (BoolType, _) => {
1703 let lv = try_val!(left.as_pure_number(expr_ctx));
1704 perform_abstract_eq_cmp(
1705 expr_ctx,
1706 span,
1707 &Lit::Num(Number {
1708 value: lv,
1709 span,
1710 raw: None,
1711 })
1712 .into(),
1713 right,
1714 )
1715 }
1716
1717 (StringType, ObjectType)
1718 | (NumberType, ObjectType)
1719 | (ObjectType, StringType)
1720 | (ObjectType, NumberType) => Unknown,
1721
1722 _ => Known(false),
1723 }
1724}
1725
1726fn perform_strict_eq_cmp(expr_ctx: ExprCtx, left: &Expr, right: &Expr) -> Value<bool> {
1728 if left.is_nan() || right.is_nan() {
1730 return Known(false);
1731 }
1732 match (left, right) {
1733 (
1735 &Expr::Unary(UnaryExpr {
1736 op: op!("typeof"),
1737 arg: ref la,
1738 ..
1739 }),
1740 &Expr::Unary(UnaryExpr {
1741 op: op!("typeof"),
1742 arg: ref ra,
1743 ..
1744 }),
1745 ) if la.as_ident().is_some()
1746 && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1747 {
1748 return Known(true)
1749 }
1750 _ => {}
1751 }
1752
1753 let (lt, rt) = (
1754 try_val!(left.get_type(expr_ctx)),
1755 try_val!(right.get_type(expr_ctx)),
1756 );
1757 if lt != rt {
1759 return Known(false);
1760 }
1761
1762 match lt {
1763 UndefinedType | NullType => Known(true),
1764 NumberType => Known(
1765 try_val!(left.as_pure_number(expr_ctx)) == try_val!(right.as_pure_number(expr_ctx)),
1766 ),
1767 StringType => {
1768 let (lv, rv) = (
1769 try_val!(left.as_pure_string(expr_ctx)),
1770 try_val!(right.as_pure_string(expr_ctx)),
1771 );
1772 if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1775 return Unknown;
1776 }
1777 Known(lv == rv)
1778 }
1779 BoolType => {
1780 let (lv, rv) = (left.as_pure_bool(expr_ctx), right.as_pure_bool(expr_ctx));
1781
1782 lv.and(rv).or((!lv).and(!rv))
1785 }
1786 ObjectType | SymbolType => Unknown,
1787 }
1788}