swc_ecma_transforms_optimization/simplify/expr/
mod.rs

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/// All [bool] fields defaults to [false].
36#[derive(Debug, Clone, Copy, Default, Hash)]
37pub struct Config {}
38
39/// Not intended for general use. Use [simplifier] instead.
40///
41/// Ported from `PeepholeFoldConstants` of google closure compiler.
42pub 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    /// This is overriden to preserve `this`.
113    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        // fold children before doing something more.
207        expr.visit_mut_children_with(self);
208
209        match expr {
210            // Do nothing.
211            // Note: Paren should be handled in fixer
212            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            // Simplify sequence expression.
280            Expr::Seq(SeqExpr { exprs, .. }) => {
281                if exprs.len() == 1 {
282                    //TODO: Respan
283                    *expr = *exprs.take().into_iter().next().unwrap()
284                } else {
285                    assert!(!exprs.is_empty(), "sequence expression should not be empty");
286                    //TODO: remove unused
287                }
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            // be conservative.
357            _ => {}
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    /// Currently noop
397    #[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    /// Drops unused values
450    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        // Expressions except last one
473        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                // Drop side-effect free nodes.
498                Expr::Lit(_) => {}
499
500                // Flatten array
501                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                // Default case: preserve it
514                _ => 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
577/// make a new boolean expression preserving side effects, if any.
578fn 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
624/// Gets the value of the given key from the given object properties, if the key
625/// exists. If the key does exist, `Some` is returned and the property is
626/// removed from the given properties.
627fn get_key_value(key: &str, props: &mut Vec<PropOrSpread>) -> Option<Box<Expr>> {
628    // It's impossible to know the value for certain if a spread property exists.
629    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                    // If __proto__ is defined, we need to check the contents of it,
659                    // as well as any nested __proto__ objects
660                    let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else {
661                        // __proto__ is not an ObjectLiteral. It's unsafe to keep trying to find
662                        // a value for this key, since __proto__ might also contain the key.
663                        return None;
664                    };
665
666                    // Get key value from __props__ object. Only return if
667                    // the result is Some. If None, we keep searching in the
668                    // parent object.
669                    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
693/// **NOTE**: This is **NOT** a public API. DO NOT USE.
694pub 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        /// [a, b].length
708        Len,
709
710        /// [a, b][0]
711        ///
712        /// {0.5: "bar"}[0.5]
713        /// Note: callers need to check `v.fract() == 0.0` in some cases.
714        /// ie non-integer indexes for arrays result in `undefined`
715        /// but not for objects (because indexing an object
716        /// returns the value of the key, ie `0.5` will not
717        /// return `undefined` if a key `0.5` exists
718        /// and its value is not `undefined`).
719        Index(f64),
720
721        /// ({}).foo
722        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                // x[5]
742                KnownOp::Index(*value)
743            } else if let Known(s) = expr.as_pure_string(expr_ctx) {
744                if s == "length" && !obj.is_object() {
745                    // Length of non-object type
746                    KnownOp::Len
747                } else if let Ok(n) = s.parse::<f64>() {
748                    // x['0'] is treated as x[0]
749                    KnownOp::Index(n)
750                } else {
751                    // x[''] or x[...] where ... is an expression like [], ie x[[]]
752                    KnownOp::IndexStr(s.into())
753                }
754            } else {
755                return;
756            }
757        }
758        _ => return,
759    };
760
761    // Note: pristine_globals refers to the compress config option pristine_globals.
762    // Any potential cases where globals are not pristine are handled in compress,
763    // e.g. x[-1] is not changed as the object's prototype may be modified.
764    // For example, Array.prototype[-1] = "foo" will result in [][-1] returning
765    // "foo".
766
767    match &mut **obj {
768        Expr::Lit(Lit::Str(Str { value, span, .. })) => match op {
769            // 'foo'.length
770            //
771            // Prototype changes do not affect .length, so we don't need to worry
772            // about pristine_globals here.
773            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            // 'foo'[1]
785            KnownOp::Index(idx) => {
786                if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() {
787                    // Prototype changes affect indexing if the index is out of bounds, so we
788                    // don't replace out-of-bound indexes.
789                    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            // 'foo'['']
807            //
808            // Handled in compress
809            KnownOp::IndexStr(..) => {}
810        },
811
812        // [1, 2, 3].length
813        //
814        // [1, 2, 3][0]
815        Expr::Array(ArrayLit { elems, span }) => {
816            // do nothing if spread exists
817            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                    // do nothing if replacement will have side effects
830                    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                    // Prototype changes do not affect .length
840                    *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 the fraction part is non-zero, or if the index is out of bounds,
852                    // then we handle this in compress as Array's prototype may be modified.
853                    if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() {
854                        return;
855                    }
856
857                    // Don't change if after has side effects.
858                    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                    // elements before target element
874                    let before: Vec<Option<ExprOrSpread>> = elems.drain(..(idx as usize)).collect();
875                    let mut iter = elems.take().into_iter();
876                    // element at idx
877                    let e = iter.next().flatten();
878                    // elements after target element
879                    let after: Vec<Option<ExprOrSpread>> = iter.collect();
880
881                    // element value
882                    let v = match e {
883                        None => Expr::undefined(*span),
884                        Some(e) => e.expr,
885                    };
886
887                    // Replacement expressions.
888                    let mut exprs = Vec::new();
889
890                    // Add before side effects.
891                    for elem in before.into_iter().flatten() {
892                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
893                    }
894
895                    // Element value.
896                    let val = v;
897
898                    // Add after side effects.
899                    for elem in after.into_iter().flatten() {
900                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
901                    }
902
903                    // Note: we always replace with a SeqExpr so that
904                    // `this` remains undefined in strict mode.
905
906                    // No side effects exist, replace with:
907                    // (0, val)
908                    if exprs.is_empty() && val.directness_matters() {
909                        exprs.push(0.into());
910                    }
911
912                    // Add value and replace with SeqExpr
913                    exprs.push(val);
914                    *expr = *Expr::from_exprs(exprs);
915                }
916
917                // Handled in compress
918                KnownOp::IndexStr(..) => {}
919            }
920        }
921
922        // { foo: true }['foo']
923        //
924        // { 0.5: true }[0.5]
925        Expr::Object(ObjectLit { props, span }) => {
926            // get key
927            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            // Get `key`s value. Non-existent keys are handled in compress.
934            // This also checks if spread exists.
935            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
958/// **NOTE**: This is **NOT** a public API. DO NOT USE.
959pub 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                    // TODO: Optimize
976                    *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            // It's string concatenation if either left or right is string.
1014            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                // String concatenation
1037                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                // Numerical calculation
1064                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            //TODO: try string concat
1098        }
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                        // 1 && $right
1105                        right
1106                    } else {
1107                        *changed = true;
1108
1109                        // 0 && $right
1110                        *expr = *left.take();
1111                        return;
1112                    }
1113                } else if val {
1114                    *changed = true;
1115
1116                    // 1 || $right
1117                    *expr = *(left.take());
1118                    return;
1119                } else {
1120                    // 0 || $right
1121                    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                    // Non-object types are never instances.
1152                    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            // Non-object types are never instances.
1187            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        // Arithmetic operations
1202        op!(bin, "-") | op!("/") | op!("%") | op!("**") => {
1203            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right))
1204        }
1205
1206        // Bit shift operations
1207        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        // These needs one more check.
1231        //
1232        // (a * 1) * 2 --> a * (1 * 2) --> a * 2
1233        op!("*") | op!("&") | op!("|") | op!("^") => {
1234            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right));
1235
1236            // Try left.rhs * right
1237            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        // Comparisons
1266        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
1287/// **NOTE**: This is **NOT** a public API. DO NOT USE.
1288pub 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                // Don't expand booleans.
1302                Expr::Lit(Lit::Num(..)) => return,
1303
1304                // Don't remove ! from negated iifes.
1305                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            // "-NaN" is "NaN"
1349            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                // TODO: Report that user is something bad (negating
1365                // non-number value)
1366            }
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                // TODO: Report error
1399            }
1400        }
1401        _ => {}
1402    }
1403}
1404
1405/// Folds 'typeof(foo)' if foo is a literal, e.g.
1406///
1407/// typeof("bar") --> "string"
1408///
1409/// typeof(6) --> "number"
1410fn 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            // We can assume `undefined` is `undefined`,
1429            // because overriding `undefined` is always hard error in swc.
1430            "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
1448/// Try to fold arithmetic binary operators
1449fn perform_arithmetic_op(expr_ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1450    /// Replace only if it becomes shorter
1451    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            // 0 - x => -x
1509            if lv == Known(0.0) {
1510                return rv;
1511            }
1512
1513            // x - 0 => x
1514            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            // NOTE: 0*x != 0 for all x, if x==0, then it is NaN.  So we can't take
1525            // advantage of that without some kind of non-NaN proof.  So the special cases
1526            // here only deal with 1*x
1527            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            // NOTE: 0/x != 0 for all x, if x==0, then it is NaN
1546
1547            if rv == Known(1.0) {
1548                // TODO: cloneTree
1549                // x/1->x
1550                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
1590/// This actually performs `<`.
1591///
1592/// https://tc39.github.io/ecma262/#sec-abstract-relational-comparison
1593fn 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        // Special case: `x < x` is always false.
1601        (
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        // Special case: `typeof a < typeof a` is always false.
1619        (
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    // Try to evaluate based on the general type.
1639    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            // In JS, browsers parse \v differently. So do not compare strings if one
1647            // contains \v.
1648            if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1649                return Unknown;
1650            } else {
1651                return Known(lv < rv);
1652            }
1653        }
1654    }
1655
1656    // Then, try to evaluate based on the value of the node. Try comparing as
1657    // numbers.
1658    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
1669/// https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
1670fn 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
1726/// https://tc39.github.io/ecma262/#sec-strict-equality-comparison
1727fn perform_strict_eq_cmp(expr_ctx: ExprCtx, left: &Expr, right: &Expr) -> Value<bool> {
1728    // Any strict equality comparison against NaN returns false.
1729    if left.is_nan() || right.is_nan() {
1730        return Known(false);
1731    }
1732    match (left, right) {
1733        // Special case, typeof a == typeof a is always true.
1734        (
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    // Strict equality can only be true for values of the same type.
1758    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            // In JS, browsers parse \v differently. So do not consider strings
1773            // equal if one contains \v.
1774            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 && rv || !lv && !rv
1783
1784            lv.and(rv).or((!lv).and(!rv))
1785        }
1786        ObjectType | SymbolType => Unknown,
1787    }
1788}