swc_ecma_transforms_optimization/simplify/expr/
mod.rs

1use std::{borrow::Cow, iter, iter::once};
2
3use swc_atoms::Atom;
4use swc_common::{
5    pass::{CompilerPass, Repeated},
6    util::take::Take,
7    Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
8};
9use swc_ecma_ast::*;
10use swc_ecma_transforms_base::{
11    ext::ExprRefExt,
12    perf::{cpu_count, Parallel, ParallelExt},
13};
14use swc_ecma_utils::{
15    is_literal, number::JsNumber, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType,
16    NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value,
17};
18use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
19use Value::{Known, Unknown};
20
21use crate::debug::debug_assert_valid;
22
23#[cfg(test)]
24mod tests;
25
26macro_rules! try_val {
27    ($v:expr) => {{
28        match $v {
29            Value::Known(v) => v,
30            Value::Unknown => return Value::Unknown,
31        }
32    }};
33}
34
35/// 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                            .map_or(false, Expr::directness_matters)
133                        {
134                            match seq.exprs.first().map(|v| &**v) {
135                                Some(Expr::Lit(..) | Expr::Ident(..)) => {}
136                                _ => {
137                                    tracing::debug!("Injecting `0` to preserve `this = undefined`");
138                                    seq.exprs.insert(0, 0.0.into());
139                                }
140                            }
141
142                            seq.visit_mut_with(self);
143                        }
144                    }
145
146                    _ => {
147                        e.visit_mut_with(self);
148                    }
149                }
150
151                if may_inject_zero && need_zero_for_this(e) {
152                    match &mut **e {
153                        Expr::Seq(seq) => {
154                            seq.exprs.insert(0, 0.into());
155                        }
156                        _ => {
157                            let seq = SeqExpr {
158                                span: DUMMY_SP,
159                                exprs: vec![0.0.into(), e.take()],
160                            };
161                            **e = seq.into();
162                        }
163                    }
164                }
165            }
166        }
167
168        self.in_callee = false;
169        n.args.visit_mut_with(self);
170
171        self.in_callee = old_in_callee;
172    }
173
174    fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
175        self.maybe_par(cpu_count(), members, |v, member| {
176            member.visit_mut_with(v);
177        });
178    }
179
180    fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
181        fn is_paren_wrap_fn_or_class(expr: &mut Expr, visitor: &mut SimplifyExpr) -> bool {
182            match &mut *expr {
183                Expr::Fn(..) | Expr::Class(..) => {
184                    expr.visit_mut_children_with(visitor);
185                    true
186                }
187                Expr::Paren(p) => is_paren_wrap_fn_or_class(&mut p.expr, visitor),
188                _ => false,
189            }
190        }
191
192        if !is_paren_wrap_fn_or_class(&mut expr.expr, self) {
193            expr.visit_mut_children_with(self);
194        }
195    }
196
197    fn visit_mut_expr(&mut self, expr: &mut Expr) {
198        if let Expr::Unary(UnaryExpr {
199            op: op!("delete"), ..
200        }) = expr
201        {
202            return;
203        }
204        // fold children before doing something more.
205        expr.visit_mut_children_with(self);
206
207        match expr {
208            // Do nothing.
209            // Note: Paren should be handled in fixer
210            Expr::Lit(_) | Expr::This(..) | Expr::Paren(..) => return,
211
212            Expr::Seq(seq) if seq.exprs.is_empty() => return,
213
214            Expr::Unary(..)
215            | Expr::Bin(..)
216            | Expr::Member(..)
217            | Expr::Cond(..)
218            | Expr::Seq(..)
219            | Expr::Array(..)
220            | Expr::Object(..)
221            | Expr::New(..) => {}
222
223            _ => return,
224        }
225
226        match expr {
227            Expr::Unary(_) => {
228                optimize_unary_expr(self.expr_ctx, expr, &mut self.changed);
229                debug_assert_valid(expr);
230            }
231            Expr::Bin(_) => {
232                optimize_bin_expr(self.expr_ctx, expr, &mut self.changed);
233                if expr.is_seq() {
234                    expr.visit_mut_with(self);
235                }
236
237                debug_assert_valid(expr);
238            }
239            Expr::Member(_) => {
240                if !self.is_modifying {
241                    optimize_member_expr(self.expr_ctx, expr, self.in_callee, &mut self.changed);
242
243                    debug_assert_valid(expr);
244                }
245            }
246
247            Expr::Cond(CondExpr {
248                span,
249                test,
250                cons,
251                alt,
252            }) => {
253                if let (p, Known(val)) = test.cast_to_bool(self.expr_ctx) {
254                    self.changed = true;
255
256                    let expr_value = if val { cons } else { alt };
257                    *expr = if p.is_pure() {
258                        if expr_value.directness_matters() {
259                            SeqExpr {
260                                span: *span,
261                                exprs: vec![0.into(), expr_value.take()],
262                            }
263                            .into()
264                        } else {
265                            *expr_value.take()
266                        }
267                    } else {
268                        SeqExpr {
269                            span: *span,
270                            exprs: vec![test.take(), expr_value.take()],
271                        }
272                        .into()
273                    }
274                }
275            }
276
277            // Simplify sequence expression.
278            Expr::Seq(SeqExpr { exprs, .. }) => {
279                if exprs.len() == 1 {
280                    //TODO: Respan
281                    *expr = *exprs.take().into_iter().next().unwrap()
282                } else {
283                    assert!(!exprs.is_empty(), "sequence expression should not be empty");
284                    //TODO: remove unused
285                }
286            }
287
288            Expr::Array(ArrayLit { elems, .. }) => {
289                let mut e = Vec::with_capacity(elems.len());
290
291                for elem in elems.take() {
292                    match elem {
293                        Some(ExprOrSpread {
294                            spread: Some(..),
295                            expr,
296                        }) if expr.is_array() => {
297                            self.changed = true;
298
299                            e.extend(expr.array().unwrap().elems.into_iter().map(|elem| {
300                                Some(elem.unwrap_or_else(|| ExprOrSpread {
301                                    spread: None,
302                                    expr: Expr::undefined(DUMMY_SP),
303                                }))
304                            }));
305                        }
306
307                        _ => e.push(elem),
308                    }
309                }
310                *elems = e;
311            }
312
313            Expr::Object(ObjectLit { props, .. }) => {
314                let should_work = props.iter().any(|p| matches!(p, PropOrSpread::Spread(..)));
315                if !should_work {
316                    return;
317                }
318
319                let mut ps = Vec::with_capacity(props.len());
320
321                for p in props.take() {
322                    match p {
323                        PropOrSpread::Spread(SpreadElement {
324                            dot3_token, expr, ..
325                        }) if expr.is_object() => {
326                            if let Expr::Object(obj) = &*expr {
327                                if obj.props.iter().any(|p| match p {
328                                    PropOrSpread::Spread(..) => true,
329                                    PropOrSpread::Prop(p) => !matches!(
330                                        &**p,
331                                        Prop::Shorthand(_) | Prop::KeyValue(_) | Prop::Method(_)
332                                    ),
333                                }) {
334                                    ps.push(PropOrSpread::Spread(SpreadElement {
335                                        dot3_token,
336                                        expr,
337                                    }));
338                                    continue;
339                                }
340                            }
341                            let props = expr.object().unwrap().props;
342                            ps.extend(props);
343                            self.changed = true;
344                        }
345
346                        _ => ps.push(p),
347                    }
348                }
349                *props = ps;
350            }
351
352            // be conservative.
353            _ => {}
354        };
355    }
356
357    fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
358        self.maybe_par(cpu_count(), n, |v, n| {
359            n.visit_mut_with(v);
360        });
361    }
362
363    fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
364        self.maybe_par(cpu_count(), n, |v, n| {
365            n.visit_mut_with(v);
366        });
367    }
368
369    fn visit_mut_for_head(&mut self, n: &mut ForHead) {
370        let old = self.is_modifying;
371        self.is_modifying = true;
372        n.visit_mut_children_with(self);
373        self.is_modifying = old;
374    }
375
376    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
377        let mut child = SimplifyExpr {
378            expr_ctx: self.expr_ctx,
379            config: self.config,
380            changed: Default::default(),
381            is_arg_of_update: Default::default(),
382            is_modifying: Default::default(),
383            in_callee: Default::default(),
384        };
385
386        child.maybe_par(cpu_count(), n, |v, n| {
387            n.visit_mut_with(v);
388        });
389        self.changed |= child.changed;
390    }
391
392    /// Currently noop
393    #[inline]
394    fn visit_mut_opt_chain_expr(&mut self, _: &mut OptChainExpr) {}
395
396    fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
397        if let Some(VarDeclOrExpr::Expr(e)) = n {
398            match &mut **e {
399                Expr::Seq(SeqExpr { exprs, .. }) if exprs.is_empty() => {
400                    *n = None;
401                    return;
402                }
403                _ => {}
404            }
405        }
406
407        n.visit_mut_children_with(self);
408    }
409
410    fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
411        self.maybe_par(cpu_count(), n, |v, n| {
412            n.visit_mut_with(v);
413        });
414    }
415
416    fn visit_mut_pat(&mut self, p: &mut Pat) {
417        let old_in_callee = self.in_callee;
418        self.in_callee = false;
419        p.visit_mut_children_with(self);
420        self.in_callee = old_in_callee;
421
422        if let Pat::Assign(a) = p {
423            if a.right.is_undefined(self.expr_ctx)
424                || match *a.right {
425                    Expr::Unary(UnaryExpr {
426                        op: op!("void"),
427                        ref arg,
428                        ..
429                    }) => !arg.may_have_side_effects(self.expr_ctx),
430                    _ => false,
431                }
432            {
433                self.changed = true;
434                *p = *a.left.take();
435            }
436        }
437    }
438
439    fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
440        self.maybe_par(cpu_count(), n, |v, n| {
441            n.visit_mut_with(v);
442        });
443    }
444
445    /// Drops unused values
446    fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
447        if e.exprs.is_empty() {
448            return;
449        }
450
451        let old_in_callee = self.in_callee;
452        let len = e.exprs.len();
453        for (idx, e) in e.exprs.iter_mut().enumerate() {
454            if idx == len - 1 {
455                self.in_callee = old_in_callee;
456            } else {
457                self.in_callee = false;
458            }
459
460            e.visit_mut_with(self);
461        }
462        self.in_callee = old_in_callee;
463
464        let len = e.exprs.len();
465
466        let last_expr = e.exprs.pop().expect("SeqExpr.exprs must not be empty");
467
468        // Expressions except last one
469        let mut exprs = Vec::with_capacity(e.exprs.len() + 1);
470
471        for expr in e.exprs.take() {
472            match *expr {
473                Expr::Lit(Lit::Num(n)) if self.in_callee && n.value == 0.0 => {
474                    if exprs.is_empty() {
475                        exprs.push(0.0.into());
476
477                        tracing::trace!("expr_simplifier: Preserving first zero");
478                    }
479                }
480
481                Expr::Lit(..) | Expr::Ident(..)
482                    if self.in_callee && !expr.may_have_side_effects(self.expr_ctx) =>
483                {
484                    if exprs.is_empty() {
485                        self.changed = true;
486
487                        exprs.push(0.0.into());
488
489                        tracing::debug!("expr_simplifier: Injected first zero");
490                    }
491                }
492
493                // Drop side-effect free nodes.
494                Expr::Lit(_) => {}
495
496                // Flatten array
497                Expr::Array(ArrayLit { span, elems }) => {
498                    let is_simple = elems
499                        .iter()
500                        .all(|elem| matches!(elem, None | Some(ExprOrSpread { spread: None, .. })));
501
502                    if is_simple {
503                        exprs.extend(elems.into_iter().flatten().map(|e| e.expr));
504                    } else {
505                        exprs.push(Box::new(ArrayLit { span, elems }.into()));
506                    }
507                }
508
509                // Default case: preserve it
510                _ => exprs.push(expr),
511            }
512        }
513
514        exprs.push(last_expr);
515
516        self.changed |= len != exprs.len();
517
518        e.exprs = exprs;
519    }
520
521    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
522        let old_is_modifying = self.is_modifying;
523        self.is_modifying = false;
524        let old_is_arg_of_update = self.is_arg_of_update;
525        self.is_arg_of_update = false;
526        s.visit_mut_children_with(self);
527        self.is_arg_of_update = old_is_arg_of_update;
528        self.is_modifying = old_is_modifying;
529
530        debug_assert_valid(s);
531    }
532
533    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
534        let mut child = SimplifyExpr {
535            expr_ctx: self.expr_ctx,
536            config: self.config,
537            changed: Default::default(),
538            is_arg_of_update: Default::default(),
539            is_modifying: Default::default(),
540            in_callee: Default::default(),
541        };
542
543        child.maybe_par(cpu_count(), n, |v, n| {
544            n.visit_mut_with(v);
545        });
546        self.changed |= child.changed;
547    }
548
549    fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
550        let old = self.in_callee;
551        self.in_callee = true;
552
553        n.tag.visit_mut_with(self);
554
555        self.in_callee = false;
556        n.tpl.visit_mut_with(self);
557
558        self.in_callee = old;
559    }
560
561    fn visit_mut_update_expr(&mut self, n: &mut UpdateExpr) {
562        let old = self.is_modifying;
563        self.is_modifying = true;
564        n.arg.visit_mut_with(self);
565        self.is_modifying = old;
566    }
567
568    fn visit_mut_with_stmt(&mut self, n: &mut WithStmt) {
569        n.obj.visit_mut_with(self);
570    }
571}
572
573/// make a new boolean expression preserving side effects, if any.
574fn make_bool_expr<I>(ctx: ExprCtx, span: Span, value: bool, orig: I) -> Box<Expr>
575where
576    I: IntoIterator<Item = Box<Expr>>,
577{
578    ctx.preserve_effects(span, Lit::Bool(Bool { value, span }).into(), orig)
579}
580
581fn nth_char(s: &str, mut idx: usize) -> Option<Cow<str>> {
582    if s.chars().any(|c| c.len_utf16() > 1) {
583        return None;
584    }
585
586    if !s.contains("\\ud") && !s.contains("\\uD") {
587        return Some(Cow::Owned(s.chars().nth(idx).unwrap().to_string()));
588    }
589
590    let mut iter = s.chars().peekable();
591
592    while let Some(c) = iter.next() {
593        if c == '\\' && iter.peek().copied() == Some('u') {
594            if idx == 0 {
595                let mut buf = String::new();
596                buf.push('\\');
597                buf.extend(iter.take(5));
598                return Some(Cow::Owned(buf));
599            } else {
600                for _ in 0..5 {
601                    iter.next();
602                }
603            }
604        }
605
606        if idx == 0 {
607            return Some(Cow::Owned(c.to_string()));
608        }
609
610        idx -= 1;
611    }
612
613    unreachable!("string is too short")
614}
615
616fn need_zero_for_this(e: &Expr) -> bool {
617    e.directness_matters() || e.is_seq()
618}
619
620/// Gets the value of the given key from the given object properties, if the key
621/// exists. If the key does exist, `Some` is returned and the property is
622/// removed from the given properties.
623fn get_key_value(key: &str, props: &mut Vec<PropOrSpread>) -> Option<Box<Expr>> {
624    // It's impossible to know the value for certain if a spread property exists.
625    let has_spread = props.iter().any(|prop| prop.is_spread());
626
627    if has_spread {
628        return None;
629    }
630
631    for (i, prop) in props.iter_mut().enumerate().rev() {
632        let prop = match prop {
633            PropOrSpread::Prop(x) => &mut **x,
634            PropOrSpread::Spread(_) => unreachable!(),
635        };
636
637        match prop {
638            Prop::Shorthand(ident) if ident.sym == key => {
639                let prop = match props.remove(i) {
640                    PropOrSpread::Prop(x) => *x,
641                    _ => unreachable!(),
642                };
643                let ident = match prop {
644                    Prop::Shorthand(x) => x,
645                    _ => unreachable!(),
646                };
647                return Some(ident.into());
648            }
649
650            Prop::KeyValue(prop) => {
651                if key != "__proto__" && prop_name_eq(&prop.key, "__proto__") {
652                    // If __proto__ is defined, we need to check the contents of it,
653                    // as well as any nested __proto__ objects
654                    let Expr::Object(ObjectLit { props, .. }) = &mut *prop.value else {
655                        // __proto__ is not an ObjectLiteral. It's unsafe to keep trying to find
656                        // a value for this key, since __proto__ might also contain the key.
657                        return None;
658                    };
659
660                    // Get key value from __props__ object. Only return if
661                    // the result is Some. If None, we keep searching in the
662                    // parent object.
663                    let v = get_key_value(key, props);
664                    if v.is_some() {
665                        return v;
666                    }
667                } else if prop_name_eq(&prop.key, key) {
668                    let prop = match props.remove(i) {
669                        PropOrSpread::Prop(x) => *x,
670                        _ => unreachable!(),
671                    };
672                    let prop = match prop {
673                        Prop::KeyValue(x) => x,
674                        _ => unreachable!(),
675                    };
676                    return Some(prop.value);
677                }
678            }
679
680            _ => {}
681        }
682    }
683
684    None
685}
686
687/// **NOTE**: This is **NOT** a public API. DO NOT USE.
688pub fn optimize_member_expr(
689    expr_ctx: ExprCtx,
690    expr: &mut Expr,
691    is_callee: bool,
692    changed: &mut bool,
693) {
694    let MemberExpr { obj, prop, .. } = match expr {
695        Expr::Member(member) => member,
696        _ => return,
697    };
698
699    #[derive(Clone, PartialEq)]
700    enum KnownOp {
701        /// [a, b].length
702        Len,
703
704        /// [a, b][0]
705        ///
706        /// {0.5: "bar"}[0.5]
707        /// Note: callers need to check `v.fract() == 0.0` in some cases.
708        /// ie non-integer indexes for arrays result in `undefined`
709        /// but not for objects (because indexing an object
710        /// returns the value of the key, ie `0.5` will not
711        /// return `undefined` if a key `0.5` exists
712        /// and its value is not `undefined`).
713        Index(f64),
714
715        /// ({}).foo
716        IndexStr(Atom),
717    }
718    let op = match prop {
719        MemberProp::Ident(IdentName { sym, .. }) if &**sym == "length" && !obj.is_object() => {
720            KnownOp::Len
721        }
722        MemberProp::Ident(IdentName { sym, .. }) => {
723            if is_callee {
724                return;
725            }
726
727            KnownOp::IndexStr(sym.clone())
728        }
729        MemberProp::Computed(ComputedPropName { expr, .. }) => {
730            if is_callee {
731                return;
732            }
733
734            if let Expr::Lit(Lit::Num(Number { value, .. })) = &**expr {
735                // x[5]
736                KnownOp::Index(*value)
737            } else if let Known(s) = expr.as_pure_string(expr_ctx) {
738                if s == "length" && !obj.is_object() {
739                    // Length of non-object type
740                    KnownOp::Len
741                } else if let Ok(n) = s.parse::<f64>() {
742                    // x['0'] is treated as x[0]
743                    KnownOp::Index(n)
744                } else {
745                    // x[''] or x[...] where ... is an expression like [], ie x[[]]
746                    KnownOp::IndexStr(s.into())
747                }
748            } else {
749                return;
750            }
751        }
752        _ => return,
753    };
754
755    // Note: pristine_globals refers to the compress config option pristine_globals.
756    // Any potential cases where globals are not pristine are handled in compress,
757    // e.g. x[-1] is not changed as the object's prototype may be modified.
758    // For example, Array.prototype[-1] = "foo" will result in [][-1] returning
759    // "foo".
760
761    match &mut **obj {
762        Expr::Lit(Lit::Str(Str { value, span, .. })) => match op {
763            // 'foo'.length
764            //
765            // Prototype changes do not affect .length, so we don't need to worry
766            // about pristine_globals here.
767            KnownOp::Len => {
768                *changed = true;
769
770                *expr = Lit::Num(Number {
771                    value: value.chars().map(|c| c.len_utf16()).sum::<usize>() as _,
772                    span: *span,
773                    raw: None,
774                })
775                .into();
776            }
777
778            // 'foo'[1]
779            KnownOp::Index(idx) => {
780                if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= value.len() {
781                    // Prototype changes affect indexing if the index is out of bounds, so we
782                    // don't replace out-of-bound indexes.
783                    return;
784                }
785
786                let Some(value) = nth_char(value, idx as _) else {
787                    return;
788                };
789
790                *changed = true;
791
792                *expr = Lit::Str(Str {
793                    raw: None,
794                    value: value.into(),
795                    span: *span,
796                })
797                .into()
798            }
799
800            // 'foo'['']
801            //
802            // Handled in compress
803            KnownOp::IndexStr(..) => {}
804        },
805
806        // [1, 2, 3].length
807        //
808        // [1, 2, 3][0]
809        Expr::Array(ArrayLit { elems, span }) => {
810            // do nothing if spread exists
811            let has_spread = elems.iter().any(|elem| {
812                elem.as_ref()
813                    .map(|elem| elem.spread.is_some())
814                    .unwrap_or(false)
815            });
816
817            if has_spread {
818                return;
819            }
820
821            match op {
822                KnownOp::Len => {
823                    // do nothing if replacement will have side effects
824                    let may_have_side_effects = elems
825                        .iter()
826                        .filter_map(|e| e.as_ref())
827                        .any(|e| e.expr.may_have_side_effects(expr_ctx));
828
829                    if may_have_side_effects {
830                        return;
831                    }
832
833                    // Prototype changes do not affect .length
834                    *changed = true;
835
836                    *expr = Lit::Num(Number {
837                        value: elems.len() as _,
838                        span: *span,
839                        raw: None,
840                    })
841                    .into();
842                }
843
844                KnownOp::Index(idx) => {
845                    // If the fraction part is non-zero, or if the index is out of bounds,
846                    // then we handle this in compress as Array's prototype may be modified.
847                    if idx.fract() != 0.0 || idx < 0.0 || idx as usize >= elems.len() {
848                        return;
849                    }
850
851                    // Don't change if after has side effects.
852                    let after_has_side_effect =
853                        elems
854                            .iter()
855                            .skip((idx as usize + 1) as _)
856                            .any(|elem| match elem {
857                                Some(elem) => elem.expr.may_have_side_effects(expr_ctx),
858                                None => false,
859                            });
860
861                    if after_has_side_effect {
862                        return;
863                    }
864
865                    *changed = true;
866
867                    // elements before target element
868                    let before: Vec<Option<ExprOrSpread>> = elems.drain(..(idx as usize)).collect();
869                    let mut iter = elems.take().into_iter();
870                    // element at idx
871                    let e = iter.next().flatten();
872                    // elements after target element
873                    let after: Vec<Option<ExprOrSpread>> = iter.collect();
874
875                    // element value
876                    let v = match e {
877                        None => Expr::undefined(*span),
878                        Some(e) => e.expr,
879                    };
880
881                    // Replacement expressions.
882                    let mut exprs = Vec::new();
883
884                    // Add before side effects.
885                    for elem in before.into_iter().flatten() {
886                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
887                    }
888
889                    // Element value.
890                    let val = v;
891
892                    // Add after side effects.
893                    for elem in after.into_iter().flatten() {
894                        expr_ctx.extract_side_effects_to(&mut exprs, *elem.expr);
895                    }
896
897                    // Note: we always replace with a SeqExpr so that
898                    // `this` remains undefined in strict mode.
899
900                    // No side effects exist, replace with:
901                    // (0, val)
902                    if exprs.is_empty() && val.directness_matters() {
903                        exprs.push(0.into());
904                    }
905
906                    // Add value and replace with SeqExpr
907                    exprs.push(val);
908                    *expr = *Expr::from_exprs(exprs);
909                }
910
911                // Handled in compress
912                KnownOp::IndexStr(..) => {}
913            }
914        }
915
916        // { foo: true }['foo']
917        //
918        // { 0.5: true }[0.5]
919        Expr::Object(ObjectLit { props, span }) => {
920            // get key
921            let key = match op {
922                KnownOp::Index(i) => Atom::from(i.to_string()),
923                KnownOp::IndexStr(key) if key != *"yield" && is_literal(props) => key,
924                _ => return,
925            };
926
927            // Get `key`s value. Non-existent keys are handled in compress.
928            // This also checks if spread exists.
929            let Some(v) = get_key_value(&key, props) else {
930                return;
931            };
932
933            *changed = true;
934
935            *expr = *expr_ctx.preserve_effects(
936                *span,
937                v,
938                once(
939                    ObjectLit {
940                        props: props.take(),
941                        span: *span,
942                    }
943                    .into(),
944                ),
945            );
946        }
947
948        _ => {}
949    }
950}
951
952/// **NOTE**: This is **NOT** a public API. DO NOT USE.
953pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
954    let BinExpr {
955        left,
956        op,
957        right,
958        span,
959    } = match expr {
960        Expr::Bin(bin) => bin,
961        _ => return,
962    };
963    let op = *op;
964
965    macro_rules! try_replace {
966        ($v:expr) => {{
967            match $v {
968                Known(v) => {
969                    // TODO: Optimize
970                    *changed = true;
971
972                    *expr = *make_bool_expr(expr_ctx, *span, v, {
973                        iter::once(left.take()).chain(iter::once(right.take()))
974                    });
975                    return;
976                }
977                _ => {}
978            }
979        }};
980        (number, $v:expr) => {{
981            match $v {
982                Known(v) => {
983                    *changed = true;
984
985                    let value_expr = if !v.is_nan() {
986                        Expr::Lit(Lit::Num(Number {
987                            value: v,
988                            span: *span,
989                            raw: None,
990                        }))
991                    } else {
992                        Expr::Ident(Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt))
993                    };
994
995                    *expr = *expr_ctx.preserve_effects(*span, value_expr.into(), {
996                        iter::once(left.take()).chain(iter::once(right.take()))
997                    });
998                    return;
999                }
1000                _ => {}
1001            }
1002        }};
1003    }
1004
1005    match op {
1006        op!(bin, "+") => {
1007            // It's string concatenation if either left or right is string.
1008            if left.is_str() || left.is_array_lit() || right.is_str() || right.is_array_lit() {
1009                if let (Known(l), Known(r)) = (
1010                    left.as_pure_string(expr_ctx),
1011                    right.as_pure_string(expr_ctx),
1012                ) {
1013                    let mut l = l.into_owned();
1014
1015                    l.push_str(&r);
1016
1017                    *changed = true;
1018
1019                    *expr = Lit::Str(Str {
1020                        raw: None,
1021                        value: l.into(),
1022                        span: *span,
1023                    })
1024                    .into();
1025                    return;
1026                }
1027            }
1028
1029            match expr.get_type(expr_ctx) {
1030                // String concatenation
1031                Known(StringType) => match expr {
1032                    Expr::Bin(BinExpr {
1033                        left, right, span, ..
1034                    }) => {
1035                        if !left.may_have_side_effects(expr_ctx)
1036                            && !right.may_have_side_effects(expr_ctx)
1037                        {
1038                            if let (Known(l), Known(r)) = (
1039                                left.as_pure_string(expr_ctx),
1040                                right.as_pure_string(expr_ctx),
1041                            ) {
1042                                *changed = true;
1043
1044                                let value = format!("{}{}", l, r);
1045
1046                                *expr = Lit::Str(Str {
1047                                    raw: None,
1048                                    value: value.into(),
1049                                    span: *span,
1050                                })
1051                                .into();
1052                            }
1053                        }
1054                    }
1055                    _ => unreachable!(),
1056                },
1057                // Numerical calculation
1058                Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => {
1059                    match expr {
1060                        Expr::Bin(BinExpr {
1061                            left, right, span, ..
1062                        }) => {
1063                            if let Known(v) = perform_arithmetic_op(expr_ctx, op, left, right) {
1064                                *changed = true;
1065                                let span = *span;
1066
1067                                let value_expr = if !v.is_nan() {
1068                                    Lit::Num(Number {
1069                                        value: v,
1070                                        span,
1071                                        raw: None,
1072                                    })
1073                                    .into()
1074                                } else {
1075                                    Ident::new("NaN".into(), span, expr_ctx.unresolved_ctxt).into()
1076                                };
1077
1078                                *expr = *expr_ctx.preserve_effects(
1079                                    span,
1080                                    value_expr,
1081                                    iter::once(left.take()).chain(iter::once(right.take())),
1082                                );
1083                            }
1084                        }
1085                        _ => unreachable!(),
1086                    };
1087                }
1088                _ => {}
1089            }
1090
1091            //TODO: try string concat
1092        }
1093
1094        op!("&&") | op!("||") => {
1095            if let (_, Known(val)) = left.cast_to_bool(expr_ctx) {
1096                let node = if op == op!("&&") {
1097                    if val {
1098                        // 1 && $right
1099                        right
1100                    } else {
1101                        *changed = true;
1102
1103                        // 0 && $right
1104                        *expr = *left.take();
1105                        return;
1106                    }
1107                } else if val {
1108                    *changed = true;
1109
1110                    // 1 || $right
1111                    *expr = *(left.take());
1112                    return;
1113                } else {
1114                    // 0 || $right
1115                    right
1116                };
1117
1118                if !left.may_have_side_effects(expr_ctx) {
1119                    *changed = true;
1120
1121                    if node.directness_matters() {
1122                        *expr = SeqExpr {
1123                            span: node.span(),
1124                            exprs: vec![0.into(), node.take()],
1125                        }
1126                        .into();
1127                    } else {
1128                        *expr = *node.take();
1129                    }
1130                } else {
1131                    *changed = true;
1132
1133                    let seq = SeqExpr {
1134                        span: *span,
1135                        exprs: vec![left.take(), node.take()],
1136                    };
1137
1138                    *expr = seq.into()
1139                };
1140            }
1141        }
1142        op!("instanceof") => {
1143            fn is_non_obj(e: &Expr) -> bool {
1144                match e {
1145                    // Non-object types are never instances.
1146                    Expr::Lit(Lit::Str { .. })
1147                    | Expr::Lit(Lit::Num(..))
1148                    | Expr::Lit(Lit::Null(..))
1149                    | Expr::Lit(Lit::Bool(..)) => true,
1150                    Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => true,
1151                    Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => true,
1152                    Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => true,
1153
1154                    Expr::Unary(UnaryExpr {
1155                        op: op!("!"),
1156                        ref arg,
1157                        ..
1158                    })
1159                    | Expr::Unary(UnaryExpr {
1160                        op: op!(unary, "-"),
1161                        ref arg,
1162                        ..
1163                    })
1164                    | Expr::Unary(UnaryExpr {
1165                        op: op!("void"),
1166                        ref arg,
1167                        ..
1168                    }) => is_non_obj(arg),
1169                    _ => false,
1170                }
1171            }
1172
1173            fn is_obj(e: &Expr) -> bool {
1174                matches!(
1175                    *e,
1176                    Expr::Array { .. } | Expr::Object { .. } | Expr::Fn { .. } | Expr::New { .. }
1177                )
1178            }
1179
1180            // Non-object types are never instances.
1181            if is_non_obj(left) {
1182                *changed = true;
1183
1184                *expr = *make_bool_expr(expr_ctx, *span, false, iter::once(right.take()));
1185                return;
1186            }
1187
1188            if is_obj(left) && right.is_global_ref_to(expr_ctx, "Object") {
1189                *changed = true;
1190
1191                *expr = *make_bool_expr(expr_ctx, *span, true, iter::once(left.take()));
1192            }
1193        }
1194
1195        // Arithmetic operations
1196        op!(bin, "-") | op!("/") | op!("%") | op!("**") => {
1197            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right))
1198        }
1199
1200        // Bit shift operations
1201        op!("<<") | op!(">>") | op!(">>>") => {
1202            fn try_fold_shift(ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1203                if !left.is_number() || !right.is_number() {
1204                    return Unknown;
1205                }
1206
1207                let (lv, rv) = match (left.as_pure_number(ctx), right.as_pure_number(ctx)) {
1208                    (Known(lv), Known(rv)) => (lv, rv),
1209                    _ => unreachable!(),
1210                };
1211                let (lv, rv) = (JsNumber::from(lv), JsNumber::from(rv));
1212
1213                Known(match op {
1214                    op!("<<") => *(lv << rv),
1215                    op!(">>") => *(lv >> rv),
1216                    op!(">>>") => *(lv.unsigned_shr(rv)),
1217
1218                    _ => unreachable!("Unknown bit operator {:?}", op),
1219                })
1220            }
1221            try_replace!(number, try_fold_shift(expr_ctx, op, left, right))
1222        }
1223
1224        // These needs one more check.
1225        //
1226        // (a * 1) * 2 --> a * (1 * 2) --> a * 2
1227        op!("*") | op!("&") | op!("|") | op!("^") => {
1228            try_replace!(number, perform_arithmetic_op(expr_ctx, op, left, right));
1229
1230            // Try left.rhs * right
1231            if let Expr::Bin(BinExpr {
1232                span: _,
1233                left: left_lhs,
1234                op: left_op,
1235                right: left_rhs,
1236            }) = &mut **left
1237            {
1238                if *left_op == op {
1239                    if let Known(value) = perform_arithmetic_op(expr_ctx, op, left_rhs, right) {
1240                        let value_expr = if !value.is_nan() {
1241                            Lit::Num(Number {
1242                                value,
1243                                span: *span,
1244                                raw: None,
1245                            })
1246                            .into()
1247                        } else {
1248                            Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt).into()
1249                        };
1250
1251                        *changed = true;
1252                        *left = left_lhs.take();
1253                        *right = Box::new(value_expr);
1254                    }
1255                }
1256            }
1257        }
1258
1259        // Comparisons
1260        op!("<") => {
1261            try_replace!(perform_abstract_rel_cmp(expr_ctx, left, right, false))
1262        }
1263        op!(">") => {
1264            try_replace!(perform_abstract_rel_cmp(expr_ctx, right, left, false))
1265        }
1266        op!("<=") => {
1267            try_replace!(!perform_abstract_rel_cmp(expr_ctx, right, left, true))
1268        }
1269        op!(">=") => {
1270            try_replace!(!perform_abstract_rel_cmp(expr_ctx, left, right, true))
1271        }
1272
1273        op!("==") => try_replace!(perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1274        op!("!=") => try_replace!(!perform_abstract_eq_cmp(expr_ctx, *span, left, right)),
1275        op!("===") => try_replace!(perform_strict_eq_cmp(expr_ctx, left, right)),
1276        op!("!==") => try_replace!(!perform_strict_eq_cmp(expr_ctx, left, right)),
1277        _ => {}
1278    };
1279}
1280
1281/// **NOTE**: This is **NOT** a public API. DO NOT USE.
1282pub fn optimize_unary_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1283    let UnaryExpr { op, arg, span } = match expr {
1284        Expr::Unary(unary) => unary,
1285        _ => return,
1286    };
1287    let may_have_side_effects = arg.may_have_side_effects(expr_ctx);
1288
1289    match op {
1290        op!("typeof") if !may_have_side_effects => {
1291            try_fold_typeof(expr_ctx, expr, changed);
1292        }
1293        op!("!") => {
1294            match &**arg {
1295                // Don't expand booleans.
1296                Expr::Lit(Lit::Num(..)) => return,
1297
1298                // Don't remove ! from negated iifes.
1299                Expr::Call(call) => {
1300                    if let Callee::Expr(callee) = &call.callee {
1301                        if let Expr::Fn(..) = &**callee {
1302                            return;
1303                        }
1304                    }
1305                }
1306                _ => {}
1307            }
1308
1309            if let (_, Known(val)) = arg.cast_to_bool(expr_ctx) {
1310                *changed = true;
1311
1312                *expr = *make_bool_expr(expr_ctx, *span, !val, iter::once(arg.take()));
1313            }
1314        }
1315        op!(unary, "+") => {
1316            if let Known(v) = arg.as_pure_number(expr_ctx) {
1317                *changed = true;
1318
1319                if v.is_nan() {
1320                    *expr = *expr_ctx.preserve_effects(
1321                        *span,
1322                        Ident::new("NaN".into(), *span, expr_ctx.unresolved_ctxt).into(),
1323                        iter::once(arg.take()),
1324                    );
1325                    return;
1326                }
1327
1328                *expr = *expr_ctx.preserve_effects(
1329                    *span,
1330                    Lit::Num(Number {
1331                        value: v,
1332                        span: *span,
1333                        raw: None,
1334                    })
1335                    .into(),
1336                    iter::once(arg.take()),
1337                );
1338            }
1339        }
1340        op!(unary, "-") => match &**arg {
1341            Expr::Ident(Ident { sym, .. }) if &**sym == "Infinity" => {}
1342            // "-NaN" is "NaN"
1343            Expr::Ident(Ident { sym, .. }) if &**sym == "NaN" => {
1344                *changed = true;
1345                *expr = *(arg.take());
1346            }
1347            Expr::Lit(Lit::Num(Number { value: f, .. })) => {
1348                *changed = true;
1349                *expr = Lit::Num(Number {
1350                    value: -f,
1351                    span: *span,
1352                    raw: None,
1353                })
1354                .into();
1355            }
1356            _ => {
1357
1358                // TODO: Report that user is something bad (negating
1359                // non-number value)
1360            }
1361        },
1362        op!("void") if !may_have_side_effects => {
1363            match &**arg {
1364                Expr::Lit(Lit::Num(Number { value, .. })) if *value == 0.0 => return,
1365                _ => {}
1366            }
1367            *changed = true;
1368
1369            *arg = Lit::Num(Number {
1370                value: 0.0,
1371                span: arg.span(),
1372                raw: None,
1373            })
1374            .into();
1375        }
1376
1377        op!("~") => {
1378            if let Known(value) = arg.as_pure_number(expr_ctx) {
1379                if value.fract() == 0.0 {
1380                    *changed = true;
1381                    *expr = Lit::Num(Number {
1382                        span: *span,
1383                        value: if value < 0.0 {
1384                            !(value as i32 as u32) as i32 as f64
1385                        } else {
1386                            !(value as u32) as i32 as f64
1387                        },
1388                        raw: None,
1389                    })
1390                    .into();
1391                }
1392                // TODO: Report error
1393            }
1394        }
1395        _ => {}
1396    }
1397}
1398
1399/// Folds 'typeof(foo)' if foo is a literal, e.g.
1400///
1401/// typeof("bar") --> "string"
1402///
1403/// typeof(6) --> "number"
1404fn try_fold_typeof(_expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) {
1405    let UnaryExpr { op, arg, span } = match expr {
1406        Expr::Unary(unary) => unary,
1407        _ => return,
1408    };
1409    assert_eq!(*op, op!("typeof"));
1410
1411    let val = match &**arg {
1412        Expr::Fn(..) => "function",
1413        Expr::Lit(Lit::Str { .. }) => "string",
1414        Expr::Lit(Lit::Num(..)) => "number",
1415        Expr::Lit(Lit::Bool(..)) => "boolean",
1416        Expr::Lit(Lit::Null(..)) | Expr::Object { .. } | Expr::Array { .. } => "object",
1417        Expr::Unary(UnaryExpr {
1418            op: op!("void"), ..
1419        }) => "undefined",
1420
1421        Expr::Ident(Ident { sym, .. }) if &**sym == "undefined" => {
1422            // We can assume `undefined` is `undefined`,
1423            // because overriding `undefined` is always hard error in swc.
1424            "undefined"
1425        }
1426
1427        _ => {
1428            return;
1429        }
1430    };
1431
1432    *changed = true;
1433
1434    *expr = Lit::Str(Str {
1435        span: *span,
1436        raw: None,
1437        value: val.into(),
1438    })
1439    .into();
1440}
1441
1442/// Try to fold arithmetic binary operators
1443fn perform_arithmetic_op(expr_ctx: ExprCtx, op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
1444    /// Replace only if it becomes shorter
1445    macro_rules! try_replace {
1446        ($value:expr) => {{
1447            let (ls, rs) = (left.span(), right.span());
1448            if ls.is_dummy() || rs.is_dummy() {
1449                Known($value)
1450            } else {
1451                let new_len = format!("{}", $value).len();
1452                if right.span().hi() > left.span().lo() {
1453                    let orig_len =
1454                        right.span().hi() - right.span().lo() + left.span().hi() - left.span().lo();
1455                    if new_len <= orig_len.0 as usize + 1 {
1456                        Known($value)
1457                    } else {
1458                        Unknown
1459                    }
1460                } else {
1461                    Known($value)
1462                }
1463            }
1464        }};
1465        (i32, $value:expr) => {
1466            try_replace!($value as f64)
1467        };
1468    }
1469
1470    let (lv, rv) = (
1471        left.as_pure_number(expr_ctx),
1472        right.as_pure_number(expr_ctx),
1473    );
1474
1475    if (lv.is_unknown() && rv.is_unknown())
1476        || op == op!(bin, "+")
1477            && (!left.get_type(expr_ctx).casted_to_number_on_add()
1478                || !right.get_type(expr_ctx).casted_to_number_on_add())
1479    {
1480        return Unknown;
1481    }
1482
1483    match op {
1484        op!(bin, "+") => {
1485            if let (Known(lv), Known(rv)) = (lv, rv) {
1486                return try_replace!(lv + rv);
1487            }
1488
1489            if lv == Known(0.0) {
1490                return rv;
1491            } else if rv == Known(0.0) {
1492                return lv;
1493            }
1494
1495            return Unknown;
1496        }
1497        op!(bin, "-") => {
1498            if let (Known(lv), Known(rv)) = (lv, rv) {
1499                return try_replace!(lv - rv);
1500            }
1501
1502            // 0 - x => -x
1503            if lv == Known(0.0) {
1504                return rv;
1505            }
1506
1507            // x - 0 => x
1508            if rv == Known(0.0) {
1509                return lv;
1510            }
1511
1512            return Unknown;
1513        }
1514        op!("*") => {
1515            if let (Known(lv), Known(rv)) = (lv, rv) {
1516                return try_replace!(lv * rv);
1517            }
1518            // NOTE: 0*x != 0 for all x, if x==0, then it is NaN.  So we can't take
1519            // advantage of that without some kind of non-NaN proof.  So the special cases
1520            // here only deal with 1*x
1521            if Known(1.0) == lv {
1522                return rv;
1523            }
1524            if Known(1.0) == rv {
1525                return lv;
1526            }
1527
1528            return Unknown;
1529        }
1530
1531        op!("/") => {
1532            if let (Known(lv), Known(rv)) = (lv, rv) {
1533                if rv == 0.0 {
1534                    return Unknown;
1535                }
1536                return try_replace!(lv / rv);
1537            }
1538
1539            // NOTE: 0/x != 0 for all x, if x==0, then it is NaN
1540
1541            if rv == Known(1.0) {
1542                // TODO: cloneTree
1543                // x/1->x
1544                return lv;
1545            }
1546            return Unknown;
1547        }
1548
1549        op!("**") => {
1550            if Known(0.0) == rv {
1551                return Known(1.0);
1552            }
1553
1554            if let (Known(lv), Known(rv)) = (lv, rv) {
1555                let lv: JsNumber = lv.into();
1556                let rv: JsNumber = rv.into();
1557                let result: f64 = lv.pow(rv).into();
1558                return try_replace!(result);
1559            }
1560
1561            return Unknown;
1562        }
1563        _ => {}
1564    }
1565    let (lv, rv) = match (lv, rv) {
1566        (Known(lv), Known(rv)) => (lv, rv),
1567        _ => return Unknown,
1568    };
1569
1570    match op {
1571        op!("&") => try_replace!(i32, to_int32(lv) & to_int32(rv)),
1572        op!("|") => try_replace!(i32, to_int32(lv) | to_int32(rv)),
1573        op!("^") => try_replace!(i32, to_int32(lv) ^ to_int32(rv)),
1574        op!("%") => {
1575            if rv == 0.0 {
1576                return Unknown;
1577            }
1578            try_replace!(lv % rv)
1579        }
1580        _ => unreachable!("unknown binary operator: {:?}", op),
1581    }
1582}
1583
1584/// This actually performs `<`.
1585///
1586/// https://tc39.github.io/ecma262/#sec-abstract-relational-comparison
1587fn perform_abstract_rel_cmp(
1588    expr_ctx: ExprCtx,
1589    left: &Expr,
1590    right: &Expr,
1591    will_negate: bool,
1592) -> Value<bool> {
1593    match (left, right) {
1594        // Special case: `x < x` is always false.
1595        (
1596            &Expr::Ident(
1597                Ident {
1598                    sym: ref li,
1599                    ctxt: l_ctxt,
1600                    ..
1601                },
1602                ..,
1603            ),
1604            &Expr::Ident(Ident {
1605                sym: ref ri,
1606                ctxt: r_ctxt,
1607                ..
1608            }),
1609        ) if !will_negate && li == ri && l_ctxt == r_ctxt => {
1610            return Known(false);
1611        }
1612        // Special case: `typeof a < typeof a` is always false.
1613        (
1614            &Expr::Unary(UnaryExpr {
1615                op: op!("typeof"),
1616                arg: ref la,
1617                ..
1618            }),
1619            &Expr::Unary(UnaryExpr {
1620                op: op!("typeof"),
1621                arg: ref ra,
1622                ..
1623            }),
1624        ) if la.as_ident().is_some()
1625            && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1626        {
1627            return Known(false)
1628        }
1629        _ => {}
1630    }
1631
1632    // Try to evaluate based on the general type.
1633    let (lt, rt) = (left.get_type(expr_ctx), right.get_type(expr_ctx));
1634
1635    if let (Known(StringType), Known(StringType)) = (lt, rt) {
1636        if let (Known(lv), Known(rv)) = (
1637            left.as_pure_string(expr_ctx),
1638            right.as_pure_string(expr_ctx),
1639        ) {
1640            // In JS, browsers parse \v differently. So do not compare strings if one
1641            // contains \v.
1642            if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1643                return Unknown;
1644            } else {
1645                return Known(lv < rv);
1646            }
1647        }
1648    }
1649
1650    // Then, try to evaluate based on the value of the node. Try comparing as
1651    // numbers.
1652    let (lv, rv) = (
1653        try_val!(left.as_pure_number(expr_ctx)),
1654        try_val!(right.as_pure_number(expr_ctx)),
1655    );
1656    if lv.is_nan() || rv.is_nan() {
1657        return Known(will_negate);
1658    }
1659
1660    Known(lv < rv)
1661}
1662
1663/// https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
1664fn perform_abstract_eq_cmp(
1665    expr_ctx: ExprCtx,
1666    span: Span,
1667    left: &Expr,
1668    right: &Expr,
1669) -> Value<bool> {
1670    let (lt, rt) = (
1671        try_val!(left.get_type(expr_ctx)),
1672        try_val!(right.get_type(expr_ctx)),
1673    );
1674
1675    if lt == rt {
1676        return perform_strict_eq_cmp(expr_ctx, left, right);
1677    }
1678
1679    match (lt, rt) {
1680        (NullType, UndefinedType) | (UndefinedType, NullType) => Known(true),
1681        (NumberType, StringType) | (_, BoolType) => {
1682            let rv = try_val!(right.as_pure_number(expr_ctx));
1683            perform_abstract_eq_cmp(
1684                expr_ctx,
1685                span,
1686                left,
1687                &Lit::Num(Number {
1688                    value: rv,
1689                    span,
1690                    raw: None,
1691                })
1692                .into(),
1693            )
1694        }
1695
1696        (StringType, NumberType) | (BoolType, _) => {
1697            let lv = try_val!(left.as_pure_number(expr_ctx));
1698            perform_abstract_eq_cmp(
1699                expr_ctx,
1700                span,
1701                &Lit::Num(Number {
1702                    value: lv,
1703                    span,
1704                    raw: None,
1705                })
1706                .into(),
1707                right,
1708            )
1709        }
1710
1711        (StringType, ObjectType)
1712        | (NumberType, ObjectType)
1713        | (ObjectType, StringType)
1714        | (ObjectType, NumberType) => Unknown,
1715
1716        _ => Known(false),
1717    }
1718}
1719
1720/// https://tc39.github.io/ecma262/#sec-strict-equality-comparison
1721fn perform_strict_eq_cmp(expr_ctx: ExprCtx, left: &Expr, right: &Expr) -> Value<bool> {
1722    // Any strict equality comparison against NaN returns false.
1723    if left.is_nan() || right.is_nan() {
1724        return Known(false);
1725    }
1726    match (left, right) {
1727        // Special case, typeof a == typeof a is always true.
1728        (
1729            &Expr::Unary(UnaryExpr {
1730                op: op!("typeof"),
1731                arg: ref la,
1732                ..
1733            }),
1734            &Expr::Unary(UnaryExpr {
1735                op: op!("typeof"),
1736                arg: ref ra,
1737                ..
1738            }),
1739        ) if la.as_ident().is_some()
1740            && la.as_ident().map(|i| i.to_id()) == ra.as_ident().map(|i| i.to_id()) =>
1741        {
1742            return Known(true)
1743        }
1744        _ => {}
1745    }
1746
1747    let (lt, rt) = (
1748        try_val!(left.get_type(expr_ctx)),
1749        try_val!(right.get_type(expr_ctx)),
1750    );
1751    // Strict equality can only be true for values of the same type.
1752    if lt != rt {
1753        return Known(false);
1754    }
1755
1756    match lt {
1757        UndefinedType | NullType => Known(true),
1758        NumberType => Known(
1759            try_val!(left.as_pure_number(expr_ctx)) == try_val!(right.as_pure_number(expr_ctx)),
1760        ),
1761        StringType => {
1762            let (lv, rv) = (
1763                try_val!(left.as_pure_string(expr_ctx)),
1764                try_val!(right.as_pure_string(expr_ctx)),
1765            );
1766            // In JS, browsers parse \v differently. So do not consider strings
1767            // equal if one contains \v.
1768            if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
1769                return Unknown;
1770            }
1771            Known(lv == rv)
1772        }
1773        BoolType => {
1774            let (lv, rv) = (left.as_pure_bool(expr_ctx), right.as_pure_bool(expr_ctx));
1775
1776            // lv && rv || !lv && !rv
1777
1778            lv.and(rv).or((!lv).and(!rv))
1779        }
1780        ObjectType | SymbolType => Unknown,
1781    }
1782}