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