swc_ecma_transforms_optimization/simplify/expr/
mod.rs

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/// All [bool] fields defaults to [false].
40#[derive(Debug, Clone, Copy, Default, Hash)]
41pub struct Config {}
42
43/// Not intended for general use. Use [simplifier] instead.
44///
45/// Ported from `PeepholeFoldConstants` of google closure compiler.
46pub 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    /// This is overriden to preserve `this`.
117    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        // fold children before doing something more.
211        expr.visit_mut_children_with(self);
212
213        match expr {
214            // Do nothing.
215            // Note: Paren should be handled in fixer
216            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            // Simplify sequence expression.
284            Expr::Seq(SeqExpr { exprs, .. }) => {
285                if exprs.len() == 1 {
286                    //TODO: Respan
287                    *expr = *exprs.take().into_iter().next().unwrap()
288                } else {
289                    assert!(!exprs.is_empty(), "sequence expression should not be empty");
290                    //TODO: remove unused
291                }
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            // be conservative.
361            _ => {}
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    /// Currently noop
401    #[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    /// Drops unused values
454    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        // Expressions except last one
477        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                // Drop side-effect free nodes.
502                Expr::Lit(_) => {}
503
504                // Flatten array
505                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                // Default case: preserve it
518                _ => 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
581/// make a new boolean expression preserving side effects, if any.
582fn 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
589/// Gets the `idx`-th UTF-16 code unit from the given [Wtf8].
590/// Surrogate pairs are splitted into high and low surrogates and counted
591/// separately.
592fn nth_char(s: &Wtf8, idx: usize) -> CodePoint {
593    match s.to_ill_formed_utf16().nth(idx) {
594        Some(c) =>
595        // SAFETY: `IllFormedUtf16CodeUnits` always returns code units in the range of UTF-16.
596        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
605/// Gets the value of the given key from the given object properties, if the key
606/// exists. If the key does exist, `Some` is returned and the property is
607/// removed from the given properties.
608fn get_key_value(key: &str, props: &mut Vec<PropOrSpread>) -> Option<Box<Expr>> {
609    // It's impossible to know the value for certain if a spread property exists.
610    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                    // If __proto__ is defined, we need to check the contents of it,
640                    // as well as any nested __proto__ objects
641                    let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else {
642                        // __proto__ is not an ObjectLiteral. It's unsafe to keep trying to find
643                        // a value for this key, since __proto__ might also contain the key.
644                        return None;
645                    };
646
647                    // Get key value from __props__ object. Only return if
648                    // the result is Some. If None, we keep searching in the
649                    // parent object.
650                    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
674/// **NOTE**: This is **NOT** a public API. DO NOT USE.
675pub 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        /// [a, b].length
689        Len,
690
691        /// [a, b][0]
692        ///
693        /// {0.5: "bar"}[0.5]
694        /// Note: callers need to check `v.fract() == 0.0` in some cases.
695        /// ie non-integer indexes for arrays result in `undefined`
696        /// but not for objects (because indexing an object
697        /// returns the value of the key, ie `0.5` will not
698        /// return `undefined` if a key `0.5` exists
699        /// and its value is not `undefined`).
700        Index(f64),
701
702        /// ({}).foo
703        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                // x[5]
723                KnownOp::Index(*value)
724            } else if let Known(s) = expr.as_pure_string(expr_ctx) {
725                if s == "length" && !obj.is_object() {
726                    // Length of non-object type
727                    KnownOp::Len
728                } else if let Ok(n) = s.parse::<f64>() {
729                    // x['0'] is treated as x[0]
730                    KnownOp::Index(n)
731                } else {
732                    // x[''] or x[...] where ... is an expression like [], ie x[[]]
733                    KnownOp::IndexStr(s.into())
734                }
735            } else {
736                return;
737            }
738        }
739        _ => return,
740    };
741
742    // Note: pristine_globals refers to the compress config option pristine_globals.
743    // Any potential cases where globals are not pristine are handled in compress,
744    // e.g. x[-1] is not changed as the object's prototype may be modified.
745    // For example, Array.prototype[-1] = "foo" will result in [][-1] returning
746    // "foo".
747
748    match &mut **obj {
749        Expr::Lit(Lit::Str(Str { value, span, .. })) => match op {
750            // 'foo'.length
751            //
752            // Prototype changes do not affect .length, so we don't need to worry
753            // about pristine_globals here.
754            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            // 'foo'[1]
770            KnownOp::Index(idx) => {
771                if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() {
772                    // Prototype changes affect indexing if the index is out of bounds, so we
773                    // don't replace out-of-bound indexes.
774                    return;
775                }
776
777                let c = nth_char(value, idx as _);
778                *changed = true;
779
780                // `nth_char` always returns a code point within the UTF-16 range.
781                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            // 'foo'['']
793            //
794            // Handled in compress
795            KnownOp::IndexStr(..) => {}
796        },
797
798        // [1, 2, 3].length
799        //
800        // [1, 2, 3][0]
801        Expr::Array(ArrayLit { elems, span }) => {
802            // do nothing if spread exists
803            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                    // do nothing if replacement will have side effects
816                    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                    // Prototype changes do not affect .length
826                    *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 the fraction part is non-zero, or if the index is out of bounds,
838                    // then we handle this in compress as Array's prototype may be modified.
839                    if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() {
840                        return;
841                    }
842
843                    // Don't change if after has side effects.
844                    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                    // elements before target element
860                    let before: Vec<Option<ExprOrSpread>> = elems.drain(..(idx as usize)).collect();
861                    let mut iter = elems.take().into_iter();
862                    // element at idx
863                    let e = iter.next().flatten();
864                    // elements after target element
865                    let after: Vec<Option<ExprOrSpread>> = iter.collect();
866
867                    // element value
868                    let v = match e {
869                        None => Expr::undefined(*span),
870                        Some(e) => e.expr,
871                    };
872
873                    // Replacement expressions.
874                    let mut exprs = Vec::new();
875
876                    // Add before side effects.
877                    for elem in before.into_iter().flatten() {
878                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
879                    }
880
881                    // Element value.
882                    let val = v;
883
884                    // Add after side effects.
885                    for elem in after.into_iter().flatten() {
886                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
887                    }
888
889                    // Note: we always replace with a SeqExpr so that
890                    // `this` remains undefined in strict mode.
891
892                    // No side effects exist, replace with:
893                    // (0, val)
894                    if exprs.is_empty() && val.directness_matters() {
895                        exprs.push(0.into());
896                    }
897
898                    // Add value and replace with SeqExpr
899                    exprs.push(val);
900                    *expr = *Expr::from_exprs(exprs);
901                }
902
903                // Handled in compress
904                KnownOp::IndexStr(..) => {}
905            }
906        }
907
908        // { foo: true }['foo']
909        //
910        // { 0.5: true }[0.5]
911        Expr::Object(ObjectLit { props, span }) => {
912            // get key
913            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            // Get `key`s value. Non-existent keys are handled in compress.
920            // This also checks if spread exists.
921            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
944/// **NOTE**: This is **NOT** a public API. DO NOT USE.
945pub 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                    // TODO: Optimize
962                    *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            // It's string concatenation if either left or right is string.
1000            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                // String concatenation
1022                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                // Numerical calculation
1049                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            //TODO: try string concat
1083        }
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                        // 1 && $right
1090                        right
1091                    } else {
1092                        *changed = true;
1093
1094                        // 0 && $right
1095                        *expr = *left.take();
1096                        return;
1097                    }
1098                } else if val {
1099                    *changed = true;
1100
1101                    // 1 || $right
1102                    *expr = *(left.take());
1103                    return;
1104                } else {
1105                    // 0 || $right
1106                    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                    // Non-object types are never instances.
1137                    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            // Non-object types are never instances.
1172            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        // Arithmetic operations
1187        op!(bin, "-") | op!("/") | op!("%") | op!("**") => {
1188            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right))
1189        }
1190
1191        // Bit shift operations
1192        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        // These needs one more check.
1216        //
1217        // (a * 1) * 2 --> a * (1 * 2) --> a * 2
1218        op!("*") | op!("&") | op!("|") | op!("^") => {
1219            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right));
1220
1221            // Try left.rhs * right
1222            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        // Comparisons
1251        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
1272/// **NOTE**: This is **NOT** a public API. DO NOT USE.
1273pub 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                // Don't expand booleans.
1287                Expr::Lit(Lit::Num(..)) => return,
1288
1289                // Don't remove ! from negated iifes.
1290                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            // "-NaN" is "NaN"
1334            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                // TODO: Report that user is something bad (negating
1350                // non-number value)
1351            }
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                // TODO: Report error
1384            }
1385        }
1386        _ => {}
1387    }
1388}
1389
1390/// Folds 'typeof(foo)' if foo is a literal, e.g.
1391///
1392/// typeof("bar") --> "string"
1393///
1394/// typeof(6) --> "number"
1395fn 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            // We can assume `undefined` is `undefined`,
1414            // because overriding `undefined` is always hard error in swc.
1415            "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
1433/// Try to fold arithmetic binary operators
1434fn perform_arithmetic_op(expr_ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1435    /// Replace only if it becomes shorter
1436    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            // 0 - x => -x
1494            if lv == Known(0.0) {
1495                return rv;
1496            }
1497
1498            // x - 0 => x
1499            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            // NOTE: 0*x != 0 for all x, if x==0, then it is NaN.  So we can't take
1510            // advantage of that without some kind of non-NaN proof.  So the special cases
1511            // here only deal with 1*x
1512            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            // NOTE: 0/x != 0 for all x, if x==0, then it is NaN
1531
1532            if rv == Known(1.0) {
1533                // TODO: cloneTree
1534                // x/1->x
1535                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
1575/// This actually performs `<`.
1576///
1577/// https://tc39.github.io/ecma262/#sec-abstract-relational-comparison
1578fn 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        // Special case: `x < x` is always false.
1586        (
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        // Special case: `typeof a < typeof a` is always false.
1604        (
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    // Try to evaluate based on the general type.
1624    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            // In JS, browsers parse \v differently. So do not compare strings if one
1632            // contains \v.
1633            if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1634                return Unknown;
1635            } else {
1636                return Known(lv < rv);
1637            }
1638        }
1639    }
1640
1641    // Then, try to evaluate based on the value of the node. Try comparing as
1642    // numbers.
1643    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
1654/// https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
1655fn 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
1711/// https://tc39.github.io/ecma262/#sec-strict-equality-comparison
1712fn perform_strict_eq_cmp(expr_ctx: ExprCtx, left: &Expr, right: &Expr) -> Value<bool> {
1713    // Any strict equality comparison against NaN returns false.
1714    if left.is_nan() || right.is_nan() {
1715        return Known(false);
1716    }
1717    match (left, right) {
1718        // Special case, typeof a == typeof a is always true.
1719        (
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    // Strict equality can only be true for values of the same type.
1743    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            // In JS, browsers parse \v differently. So do not consider strings
1758            // equal if one contains \v.
1759            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 && rv || !lv && !rv
1768
1769            lv.and(rv).or((!lv).and(!rv))
1770        }
1771        ObjectType | SymbolType => Unknown,
1772    }
1773}