swc_ecma_transforms_optimization/simplify/branch/
mod.rs

1use std::{borrow::Cow, iter::once, mem::take};
2
3use swc_common::{
4    pass::{CompilerPass, Repeated},
5    util::{move_map::MoveMap, take::Take},
6    Mark, Spanned, SyntaxContext, DUMMY_SP,
7};
8use swc_ecma_ast::*;
9use swc_ecma_transforms_base::perf::{cpu_count, Parallel, ParallelExt};
10use swc_ecma_utils::{
11    extract_var_ids, is_literal, prepend_stmt, ExprCtx, ExprExt, ExprFactory, Hoister, IsEmpty,
12    StmtExt, StmtLike, Value::Known,
13};
14use swc_ecma_visit::{
15    noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
16};
17use tracing::{debug, trace};
18
19#[cfg(test)]
20mod tests;
21
22/// Not intended for general use. Use [simplifier] instead.
23///
24/// Ported from `PeepholeRemoveDeadCode` of google closure compiler.
25pub fn dead_branch_remover(
26    unresolved_mark: Mark,
27) -> impl Repeated + Pass + CompilerPass + VisitMut + 'static {
28    visit_mut_pass(Remover {
29        changed: false,
30        normal_block: Default::default(),
31        expr_ctx: ExprCtx {
32            unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
33            is_unresolved_ref_safe: false,
34            in_strict: false,
35            remaining_depth: 3,
36        },
37    })
38}
39
40impl CompilerPass for Remover {
41    fn name(&self) -> Cow<'static, str> {
42        Cow::Borrowed("dead_branch_remover")
43    }
44}
45
46impl Repeated for Remover {
47    fn changed(&self) -> bool {
48        self.changed
49    }
50
51    fn reset(&mut self) {
52        self.changed = false;
53    }
54}
55
56#[derive(Debug)]
57struct Remover {
58    changed: bool,
59    normal_block: bool,
60
61    expr_ctx: ExprCtx,
62}
63
64impl Parallel for Remover {
65    fn create(&self) -> Self {
66        Self { ..*self }
67    }
68
69    fn merge(&mut self, _: Self) {}
70}
71
72impl VisitMut for Remover {
73    noop_visit_mut_type!();
74
75    fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
76        self.maybe_par(cpu_count(), members, |v, member| {
77            member.visit_mut_with(v);
78        });
79    }
80
81    fn visit_mut_expr(&mut self, e: &mut Expr) {
82        e.visit_mut_children_with(self);
83
84        match e {
85            Expr::Seq(s) => {
86                if s.exprs.is_empty() {
87                    *e = Expr::dummy();
88                } else if s.exprs.len() == 1 {
89                    *e = *s.exprs.pop().unwrap();
90                }
91            }
92
93            Expr::Assign(AssignExpr {
94                op: op!("="),
95                left: AssignTarget::Simple(l),
96                right: r,
97                ..
98            }) if match &*l {
99                SimpleAssignTarget::Ident(l) => match &**r {
100                    Expr::Ident(r) => l.sym == r.sym && l.ctxt == r.ctxt,
101                    _ => false,
102                },
103                _ => false,
104            } =>
105            {
106                if cfg!(feature = "debug") {
107                    debug!("Dropping assignment to the same variable");
108                }
109                *e = r.take().ident().unwrap().into();
110            }
111
112            Expr::Assign(AssignExpr {
113                op: op!("="),
114                left: AssignTarget::Pat(left),
115                right,
116                ..
117            }) if match &*left {
118                AssignTargetPat::Array(arr) => {
119                    arr.elems.is_empty() || arr.elems.iter().all(|v| v.is_none())
120                }
121                _ => false,
122            } =>
123            {
124                if cfg!(feature = "debug") {
125                    debug!("Dropping assignment to an empty array pattern");
126                }
127                *e = *right.take();
128            }
129
130            Expr::Assign(AssignExpr {
131                op: op!("="),
132                left: AssignTarget::Pat(left),
133                right,
134                ..
135            }) if match &*left {
136                AssignTargetPat::Object(obj) => obj.props.is_empty(),
137                _ => false,
138            } =>
139            {
140                if cfg!(feature = "debug") {
141                    debug!("Dropping assignment to an empty object pattern");
142                }
143                *e = *right.take();
144            }
145
146            Expr::Cond(cond)
147                if !cond.test.may_have_side_effects(self.expr_ctx)
148                    && (cond.cons.is_undefined(self.expr_ctx)
149                        || matches!(*cond.cons, Expr::Unary(UnaryExpr {
150                                op: op!("void"),
151                                ref arg,
152                                ..
153                            }) if !arg.may_have_side_effects(self.expr_ctx)))
154                    && (cond.alt.is_undefined(self.expr_ctx)
155                        || matches!(*cond.alt, Expr::Unary(UnaryExpr {
156                                op: op!("void"),
157                                ref arg,
158                                ..
159                            }) if !arg.may_have_side_effects(self.expr_ctx))) =>
160            {
161                if cfg!(feature = "debug") {
162                    debug!("Dropping side-effect-free expressions");
163                }
164                *e = *cond.cons.take();
165            }
166
167            Expr::Bin(be) => match (be.left.is_invalid(), be.right.is_invalid()) {
168                (true, true) => {
169                    *e = Expr::dummy();
170                }
171                (true, false) => {
172                    *e = *be.right.take();
173                }
174                (false, true) => {
175                    *e = *be.left.take();
176                }
177                _ => {}
178            },
179
180            _ => {}
181        }
182    }
183
184    fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
185        self.maybe_par(cpu_count() * 8, n, |v, n| {
186            n.visit_mut_with(v);
187        })
188    }
189
190    fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
191        self.maybe_par(cpu_count() * 8, n, |v, n| {
192            n.visit_mut_with(v);
193        })
194    }
195
196    fn visit_mut_for_stmt(&mut self, s: &mut ForStmt) {
197        s.visit_mut_children_with(self);
198
199        s.init = s.init.take().and_then(|e| match e {
200            VarDeclOrExpr::Expr(e) => {
201                ignore_result(e, true, self.expr_ctx).map(VarDeclOrExpr::from)
202            }
203            _ => Some(e),
204        });
205
206        s.update = s
207            .update
208            .take()
209            .and_then(|e| ignore_result(e, true, self.expr_ctx));
210
211        s.test = s.test.take().and_then(|e| {
212            let span = e.span();
213            if let Known(value) = e.as_pure_bool(self.expr_ctx) {
214                return if value {
215                    None
216                } else {
217                    Some(Lit::Bool(Bool { span, value: false }).into())
218                };
219            }
220
221            Some(e)
222        });
223    }
224
225    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
226        if cfg!(feature = "debug") {
227            debug!("Removing dead branches");
228        }
229        self.fold_stmt_like(n);
230    }
231
232    fn visit_mut_object_pat(&mut self, p: &mut ObjectPat) {
233        p.visit_mut_children_with(self);
234
235        // Don't remove if there exists a rest pattern
236        if p.props.iter().any(|p| matches!(p, ObjectPatProp::Rest(..))) {
237            return;
238        }
239
240        fn is_computed(k: &PropName) -> bool {
241            matches!(k, PropName::Computed(..))
242        }
243
244        p.props.retain(|p| match p {
245            ObjectPatProp::KeyValue(KeyValuePatProp { key, value, .. })
246                if match &**value {
247                    Pat::Object(p) => !is_computed(key) && p.props.is_empty(),
248                    _ => false,
249                } =>
250            {
251                if cfg!(feature = "debug") {
252                    debug!(
253                        "Dropping key-value pattern property because it's an empty object pattern"
254                    );
255                }
256                false
257            }
258
259            ObjectPatProp::KeyValue(KeyValuePatProp { key, value, .. })
260                if match &**value {
261                    Pat::Array(p) => !is_computed(key) && p.elems.is_empty(),
262                    _ => false,
263                } =>
264            {
265                if cfg!(feature = "debug") {
266                    debug!(
267                        "Dropping key-value pattern property because it's an empty array pattern"
268                    );
269                }
270                false
271            }
272            _ => true,
273        });
274    }
275
276    fn visit_mut_object_pat_prop(&mut self, p: &mut ObjectPatProp) {
277        p.visit_mut_children_with(self);
278
279        match p {
280            ObjectPatProp::Assign(AssignPatProp {
281                span,
282                key,
283                value: Some(expr),
284            }) if expr.is_undefined(self.expr_ctx)
285                || match **expr {
286                    Expr::Unary(UnaryExpr {
287                        op: op!("void"),
288                        ref arg,
289                        ..
290                    }) => is_literal(&**arg),
291                    _ => false,
292                } =>
293            {
294                *p = ObjectPatProp::Assign(AssignPatProp {
295                    span: *span,
296                    key: key.take(),
297                    value: None,
298                });
299            }
300
301            _ => {}
302        }
303    }
304
305    fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
306        n.visit_mut_children_with(self);
307
308        if let Some(VarDeclOrExpr::Expr(e)) = n {
309            if e.is_invalid() {
310                *n = None;
311            }
312        }
313    }
314
315    fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
316        self.maybe_par(cpu_count() * 8, n, |v, n| {
317            n.visit_mut_with(v);
318        })
319    }
320
321    fn visit_mut_pat(&mut self, p: &mut Pat) {
322        p.visit_mut_children_with(self);
323
324        match p {
325            Pat::Assign(assign)
326                if assign.right.is_undefined(self.expr_ctx)
327                    || match *assign.right {
328                        Expr::Unary(UnaryExpr {
329                            op: op!("void"),
330                            ref arg,
331                            ..
332                        }) => is_literal(&**arg),
333                        _ => false,
334                    } =>
335            {
336                *p = *assign.left.take();
337            }
338
339            _ => {}
340        }
341    }
342
343    fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
344        self.maybe_par(cpu_count() * 8, n, |v, n| {
345            n.visit_mut_with(v);
346        })
347    }
348
349    fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
350        e.visit_mut_children_with(self);
351        if e.exprs.is_empty() {
352            return;
353        }
354
355        let last = e.exprs.pop().unwrap();
356
357        let should_preserved_this = last.directness_matters();
358
359        let mut exprs = if should_preserved_this {
360            e.exprs
361                .take()
362                .into_iter()
363                .enumerate()
364                .filter_map(|(idx, e)| {
365                    if idx == 0 {
366                        return Some(e);
367                    }
368
369                    ignore_result(e, true, self.expr_ctx)
370                })
371                .collect()
372        } else {
373            e.exprs
374                .take()
375                .move_flat_map(|e| ignore_result(e, false, self.expr_ctx))
376        };
377
378        exprs.push(last);
379
380        e.exprs = exprs;
381    }
382
383    fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
384        stmt.visit_mut_children_with(self);
385
386        stmt.map_with_mut(|stmt| {
387            match stmt {
388                Stmt::If(IfStmt {
389                    span,
390                    test,
391                    cons,
392                    alt,
393                }) => {
394                    if let Stmt::If(IfStmt { alt: Some(..), .. }) = *cons {
395                        return IfStmt {
396                            test,
397                            cons: Box::new(
398                                BlockStmt {
399                                    stmts: vec![*cons],
400                                    ..Default::default()
401                                }
402                                .into(),
403                            ),
404                            alt,
405                            span,
406                        }
407                        .into();
408                    }
409
410                    let mut stmts = Vec::new();
411                    if let (p, Known(v)) = test.cast_to_bool(self.expr_ctx) {
412                        if cfg!(feature = "debug") {
413                            trace!("The condition for if statement is always {}", v);
414                        }
415
416                        // Preserve effect of the test
417                        if !p.is_pure() {
418                            if let Some(expr) = ignore_result(test, true, self.expr_ctx) {
419                                stmts.push(ExprStmt { span, expr }.into())
420                            }
421                        }
422
423                        if v {
424                            // Preserve variables
425                            if let Some(var) = alt.and_then(|alt| alt.extract_var_ids_as_var()) {
426                                stmts.push(var.into())
427                            }
428                            stmts.push(*cons);
429                        } else {
430                            if let Some(var) = cons.extract_var_ids_as_var() {
431                                stmts.push(var.into())
432                            }
433
434                            if let Some(alt) = alt {
435                                stmts.push(*alt)
436                            }
437                        }
438
439                        if stmts.is_empty() {
440                            return EmptyStmt { span }.into();
441                        }
442
443                        if cfg!(feature = "debug") {
444                            debug!("Optimized an if statement with known condition");
445                        }
446
447                        self.changed = true;
448
449                        let mut block: Stmt = BlockStmt {
450                            span,
451                            stmts,
452                            ..Default::default()
453                        }
454                        .into();
455                        block.visit_mut_with(self);
456                        return block;
457                    }
458
459                    let alt = match &alt {
460                        Some(stmt) if stmt.is_empty() => None,
461                        _ => alt,
462                    };
463                    if alt.is_none() {
464                        if let Stmt::Empty(..) = *cons {
465                            self.changed = true;
466
467                            return if let Some(expr) = ignore_result(test, true, self.expr_ctx) {
468                                ExprStmt { span, expr }.into()
469                            } else {
470                                EmptyStmt { span }.into()
471                            };
472                        }
473                    }
474
475                    IfStmt {
476                        span,
477                        test,
478                        cons,
479                        alt,
480                    }
481                    .into()
482                }
483
484                Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
485                    if cfg!(feature = "debug") {
486                        debug!("Dropping an empty var declaration");
487                    }
488                    EmptyStmt { span: v.span }.into()
489                }
490
491                Stmt::Labeled(LabeledStmt {
492                    label, span, body, ..
493                }) if body.is_empty() => {
494                    debug!("Dropping an empty label statement: `{}`", label);
495                    EmptyStmt { span }.into()
496                }
497
498                Stmt::Labeled(LabeledStmt {
499                    span,
500                    body,
501                    ref label,
502                    ..
503                }) if match &*body {
504                    Stmt::Break(BreakStmt { label: Some(b), .. }) => label.sym == b.sym,
505                    _ => false,
506                } =>
507                {
508                    debug!("Dropping a label statement with instant break: `{}`", label);
509                    EmptyStmt { span }.into()
510                }
511
512                // `1;` -> `;`
513                Stmt::Expr(ExprStmt { span, expr, .. }) => {
514                    // Directives
515                    if let Expr::Lit(Lit::Str(..)) = &*expr {
516                        return ExprStmt { span, expr }.into();
517                    }
518
519                    match ignore_result(expr, false, self.expr_ctx) {
520                        Some(e) => ExprStmt { span, expr: e }.into(),
521                        None => EmptyStmt { span: DUMMY_SP }.into(),
522                    }
523                }
524
525                Stmt::Block(BlockStmt { span, stmts, ctxt }) => {
526                    if stmts.is_empty() {
527                        if cfg!(feature = "debug") {
528                            debug!("Drooping an empty block statement");
529                        }
530
531                        EmptyStmt { span }.into()
532                    } else if stmts.len() == 1
533                        && !is_block_scoped_stuff(&stmts[0])
534                        && stmt_depth(&stmts[0]) <= 1
535                    {
536                        if cfg!(feature = "debug") {
537                            debug!("Optimizing a block statement with a single statement");
538                        }
539
540                        let mut v = stmts.into_iter().next().unwrap();
541                        v.visit_mut_with(self);
542                        v
543                    } else {
544                        BlockStmt { span, stmts, ctxt }.into()
545                    }
546                }
547                Stmt::Try(s) => {
548                    let TryStmt {
549                        span,
550                        block,
551                        handler,
552                        finalizer,
553                    } = *s;
554
555                    // Only leave the finally block if try block is empty
556                    if block.is_empty() {
557                        let var = handler.and_then(|h| Stmt::from(h.body).extract_var_ids_as_var());
558
559                        return if let Some(mut finalizer) = finalizer {
560                            if let Some(var) = var.map(Box::new).map(Decl::from).map(Stmt::from) {
561                                prepend_stmt(&mut finalizer.stmts, var);
562                            }
563                            finalizer.into()
564                        } else {
565                            var.map(Box::new)
566                                .map(Decl::from)
567                                .map(Stmt::from)
568                                .unwrap_or_else(|| EmptyStmt { span }.into())
569                        };
570                    }
571
572                    // If catch block is not specified and finally block is empty, fold it to simple
573                    // block.
574                    if handler.is_none() && finalizer.is_empty() {
575                        if cfg!(feature = "debug") {
576                            debug!("Converting a try statement to a block statement");
577                        }
578
579                        return block.into();
580                    }
581
582                    TryStmt {
583                        span,
584                        block,
585                        handler,
586                        finalizer,
587                    }
588                    .into()
589                }
590
591                Stmt::Switch(mut s) => {
592                    if s.cases
593                        .iter()
594                        .any(|case| matches!(case.test.as_deref(), Some(Expr::Update(..))))
595                    {
596                        return s.into();
597                    }
598                    if let Expr::Update(..) = &*s.discriminant {
599                        if s.cases.len() != 1 {
600                            return s.into();
601                        }
602                    }
603
604                    let remove_break = |stmts: Vec<Stmt>| {
605                        debug_assert!(
606                            !has_conditional_stopper(&stmts) || has_unconditional_stopper(&stmts)
607                        );
608
609                        let mut done = false;
610                        stmts.move_flat_map(|s| {
611                            if done {
612                                match s {
613                                    Stmt::Decl(Decl::Var(var))
614                                        if matches!(
615                                            &*var,
616                                            VarDecl {
617                                                kind: VarDeclKind::Var,
618                                                ..
619                                            }
620                                        ) =>
621                                    {
622                                        return Some(
623                                            VarDecl {
624                                                span: DUMMY_SP,
625                                                kind: VarDeclKind::Var,
626                                                decls: var.decls.move_map(|decl| VarDeclarator {
627                                                    init: None,
628                                                    ..decl
629                                                }),
630                                                ..Default::default()
631                                            }
632                                            .into(),
633                                        );
634                                    }
635                                    _ => (),
636                                }
637
638                                return None;
639                            }
640                            match s {
641                                Stmt::Break(BreakStmt { label: None, .. }) => {
642                                    done = true;
643                                    None
644                                }
645                                Stmt::Return(..) | Stmt::Throw(..) => {
646                                    done = true;
647                                    Some(s)
648                                }
649                                _ => Some(s),
650                            }
651                        })
652                    };
653
654                    let is_matching_literal = match *s.discriminant {
655                        Expr::Lit(Lit::Str(..))
656                        | Expr::Lit(Lit::Null(..))
657                        | Expr::Lit(Lit::Num(..)) => true,
658                        ref e if e.is_nan() || e.is_global_ref_to(self.expr_ctx, "undefined") => {
659                            true
660                        }
661                        _ => false,
662                    };
663
664                    // Remove empty switch
665                    if s.cases.is_empty() {
666                        if cfg!(feature = "debug") {
667                            debug!("Removing an empty switch statement");
668                        }
669                        return match ignore_result(s.discriminant, true, self.expr_ctx) {
670                            Some(expr) => ExprStmt { span: s.span, expr }.into(),
671                            None => EmptyStmt { span: s.span }.into(),
672                        };
673                    }
674
675                    // Handle a switch statement with only default.
676                    if s.cases.len() == 1
677                        && s.cases[0].test.is_none()
678                        && !has_conditional_stopper(&s.cases[0].cons)
679                    {
680                        if cfg!(feature = "debug") {
681                            debug!("Switch -> Block as default is the only case");
682                        }
683
684                        let mut stmts = remove_break(s.cases.remove(0).cons);
685                        if let Some(expr) = ignore_result(s.discriminant, true, self.expr_ctx) {
686                            prepend_stmt(&mut stmts, expr.into_stmt());
687                        }
688
689                        let mut block: Stmt = BlockStmt {
690                            span: s.span,
691                            stmts,
692                            ..Default::default()
693                        }
694                        .into();
695                        block.visit_mut_with(self);
696                        return block;
697                    }
698
699                    let mut non_constant_case_idx = None;
700                    let selected = {
701                        let mut i = 0;
702                        s.cases.iter().position(|case| {
703                            if non_constant_case_idx.is_some() {
704                                i += 1;
705                                return false;
706                            }
707
708                            if let Some(ref test) = case.test {
709                                let v = match (&**test, &*s.discriminant) {
710                                    (
711                                        &Expr::Lit(Lit::Str(Str {
712                                            value: ref test, ..
713                                        })),
714                                        &Expr::Lit(Lit::Str(Str { value: ref d, .. })),
715                                    ) => *test == *d,
716                                    (
717                                        &Expr::Lit(Lit::Num(Number { value: test, .. })),
718                                        &Expr::Lit(Lit::Num(Number { value: d, .. })),
719                                    ) => (test - d).abs() < 1e-10,
720                                    (
721                                        &Expr::Lit(Lit::Bool(Bool { value: test, .. })),
722                                        &Expr::Lit(Lit::Bool(Bool { value: d, .. })),
723                                    ) => test == d,
724                                    (&Expr::Lit(Lit::Null(..)), &Expr::Lit(Lit::Null(..))) => true,
725
726                                    _ => {
727                                        if !test.is_nan()
728                                            && !test.is_global_ref_to(self.expr_ctx, "undefined")
729                                        {
730                                            non_constant_case_idx = Some(i);
731                                        }
732
733                                        false
734                                    }
735                                };
736
737                                i += 1;
738                                return v;
739                            }
740
741                            i += 1;
742                            false
743                        })
744                    };
745
746                    let are_all_tests_known = s
747                        .cases
748                        .iter()
749                        .map(|case| case.test.as_deref())
750                        .all(|s| matches!(s, Some(Expr::Lit(..)) | None));
751
752                    let mut var_ids = Vec::new();
753                    if let Some(i) = selected {
754                        if !has_conditional_stopper(&s.cases[i].cons) {
755                            let mut exprs = Vec::new();
756                            exprs.extend(ignore_result(s.discriminant, true, self.expr_ctx));
757
758                            let mut stmts = s.cases[i].cons.take();
759                            let mut cases = s.cases.drain((i + 1)..);
760
761                            for case in cases.by_ref() {
762                                let should_stop = has_unconditional_stopper(&case.cons);
763                                stmts.extend(case.cons);
764                                //
765                                if should_stop {
766                                    break;
767                                }
768                            }
769
770                            let mut stmts = remove_break(stmts);
771
772                            let decls = cases
773                                .flat_map(|case| {
774                                    exprs.extend(
775                                        case.test
776                                            .and_then(|e| ignore_result(e, true, self.expr_ctx)),
777                                    );
778                                    case.cons
779                                })
780                                .flat_map(|stmt| stmt.extract_var_ids())
781                                .map(|i| VarDeclarator {
782                                    span: DUMMY_SP,
783                                    name: i.into(),
784                                    init: None,
785                                    definite: false,
786                                })
787                                .collect::<Vec<_>>();
788
789                            if !decls.is_empty() {
790                                prepend_stmt(
791                                    &mut stmts,
792                                    VarDecl {
793                                        span: DUMMY_SP,
794                                        kind: VarDeclKind::Var,
795                                        decls,
796                                        declare: false,
797                                        ..Default::default()
798                                    }
799                                    .into(),
800                                );
801                            }
802
803                            if !exprs.is_empty() {
804                                prepend_stmt(
805                                    &mut stmts,
806                                    ExprStmt {
807                                        span: DUMMY_SP,
808                                        expr: if exprs.len() == 1 {
809                                            exprs.remove(0)
810                                        } else {
811                                            SeqExpr {
812                                                span: DUMMY_SP,
813                                                exprs,
814                                            }
815                                            .into()
816                                        },
817                                    }
818                                    .into(),
819                                );
820                            }
821
822                            if cfg!(feature = "debug") {
823                                debug!("Switch -> Block as we know discriminant");
824                            }
825                            let mut block: Stmt = BlockStmt {
826                                span: s.span,
827                                stmts,
828                                ..Default::default()
829                            }
830                            .into();
831                            block.visit_mut_with(self);
832                            return block;
833                        }
834                    } else if are_all_tests_known {
835                        let mut vars = Vec::new();
836
837                        if let Expr::Lit(..) = *s.discriminant {
838                            let idx = s.cases.iter().position(|v| v.test.is_none());
839
840                            if let Some(i) = idx {
841                                for case in &s.cases[..i] {
842                                    for cons in &case.cons {
843                                        vars.extend(cons.extract_var_ids().into_iter().map(
844                                            |name| VarDeclarator {
845                                                span: DUMMY_SP,
846                                                name: name.into(),
847                                                init: None,
848                                                definite: Default::default(),
849                                            },
850                                        ));
851                                    }
852                                }
853
854                                if !has_conditional_stopper(&s.cases[i].cons) {
855                                    let stmts = s.cases.remove(i).cons;
856                                    let mut stmts = remove_break(stmts);
857
858                                    if !vars.is_empty() {
859                                        prepend_stmt(
860                                            &mut stmts,
861                                            VarDecl {
862                                                span: DUMMY_SP,
863                                                kind: VarDeclKind::Var,
864                                                declare: Default::default(),
865                                                decls: take(&mut vars),
866                                                ..Default::default()
867                                            }
868                                            .into(),
869                                        )
870                                    }
871
872                                    let mut block: Stmt = BlockStmt {
873                                        span: s.span,
874                                        stmts,
875                                        ..Default::default()
876                                    }
877                                    .into();
878
879                                    block.visit_mut_with(self);
880
881                                    if cfg!(feature = "debug") {
882                                        debug!("Switch -> Block as the discriminant is a literal");
883                                    }
884                                    return block;
885                                }
886                            }
887                        }
888                    }
889
890                    if is_matching_literal {
891                        let mut idx = 0usize;
892                        let mut breaked = false;
893                        // Remove unmatchable cases.
894                        s.cases = s.cases.move_flat_map(|case| {
895                            if non_constant_case_idx.is_some()
896                                && idx >= non_constant_case_idx.unwrap()
897                            {
898                                idx += 1;
899                                return Some(case);
900                            }
901
902                            // Detect unconditional break;
903                            if selected.is_some() && selected <= Some(idx) {
904                                // Done.
905                                if breaked {
906                                    idx += 1;
907                                    if cfg!(feature = "debug") {
908                                        debug!("Dropping case because it is unreachable");
909                                    }
910                                    return None;
911                                }
912
913                                if !breaked {
914                                    // has unconditional break
915                                    breaked |= has_unconditional_stopper(&case.cons);
916                                }
917
918                                idx += 1;
919                                return Some(case);
920                            }
921
922                            let res = match case.test {
923                                Some(e)
924                                    if matches!(
925                                        &*e,
926                                        Expr::Lit(Lit::Num(..))
927                                            | Expr::Lit(Lit::Str(..))
928                                            | Expr::Lit(Lit::Null(..))
929                                    ) =>
930                                {
931                                    case.cons
932                                        .into_iter()
933                                        .for_each(|stmt| var_ids.extend(stmt.extract_var_ids()));
934
935                                    if cfg!(feature = "debug") {
936                                        debug!(
937                                            "Dropping case because it is unreachable (literal \
938                                             test)"
939                                        );
940                                    }
941                                    None
942                                }
943                                _ => Some(case),
944                            };
945                            idx += 1;
946                            res
947                        });
948                    }
949
950                    let is_default_last =
951                        matches!(s.cases.last(), Some(SwitchCase { test: None, .. }));
952
953                    {
954                        // True if all cases except default is empty.
955                        let is_all_case_empty = s
956                            .cases
957                            .iter()
958                            .all(|case| case.test.is_none() || case.cons.is_empty());
959
960                        let is_all_case_side_effect_free = s.cases.iter().all(|case| {
961                            case.test
962                                .as_ref()
963                                .map(|e| e.is_ident() || !e.may_have_side_effects(self.expr_ctx))
964                                .unwrap_or(true)
965                        });
966
967                        if is_default_last
968                            && is_all_case_empty
969                            && is_all_case_side_effect_free
970                            && !has_conditional_stopper(&s.cases.last().unwrap().cons)
971                        {
972                            let mut exprs = Vec::new();
973                            exprs.extend(ignore_result(s.discriminant, true, self.expr_ctx));
974
975                            exprs.extend(
976                                s.cases
977                                    .iter_mut()
978                                    .filter_map(|case| case.test.take())
979                                    .filter_map(|e| ignore_result(e, true, self.expr_ctx)),
980                            );
981
982                            let stmts = s.cases.pop().unwrap().cons;
983                            let mut stmts = remove_break(stmts);
984
985                            if !exprs.is_empty() {
986                                prepend_stmt(
987                                    &mut stmts,
988                                    ExprStmt {
989                                        span: DUMMY_SP,
990                                        expr: if exprs.len() == 1 {
991                                            exprs.remove(0)
992                                        } else {
993                                            SeqExpr {
994                                                span: DUMMY_SP,
995                                                exprs,
996                                            }
997                                            .into()
998                                        },
999                                    }
1000                                    .into(),
1001                                );
1002                            }
1003
1004                            if cfg!(feature = "debug") {
1005                                debug!("Stmt -> Block as all cases are empty");
1006                            }
1007                            let mut block: Stmt = BlockStmt {
1008                                span: s.span,
1009                                stmts,
1010                                ..Default::default()
1011                            }
1012                            .into();
1013                            block.visit_mut_with(self);
1014                            return block;
1015                        }
1016                    }
1017
1018                    if is_matching_literal
1019                        && s.cases.iter().all(|case| match &case.test {
1020                            Some(e)
1021                                if matches!(
1022                                    &**e,
1023                                    Expr::Lit(Lit::Str(..))
1024                                        | Expr::Lit(Lit::Null(..))
1025                                        | Expr::Lit(Lit::Num(..))
1026                                ) =>
1027                            {
1028                                true
1029                            }
1030                            _ => false,
1031                        })
1032                    {
1033                        // No case can be matched.
1034                        if s.cases
1035                            .iter()
1036                            .all(|case| !has_conditional_stopper(&case.cons))
1037                        {
1038                            if cfg!(feature = "debug") {
1039                                debug!("Removing swtich because all cases are unreachable");
1040                            }
1041
1042                            // Preserve variables
1043                            let decls: Vec<_> = s
1044                                .cases
1045                                .into_iter()
1046                                .flat_map(|case| extract_var_ids(&case.cons))
1047                                .chain(var_ids)
1048                                .map(|i| VarDeclarator {
1049                                    span: i.span,
1050                                    name: i.into(),
1051                                    init: None,
1052                                    definite: false,
1053                                })
1054                                .collect();
1055                            if !decls.is_empty() {
1056                                return VarDecl {
1057                                    span: DUMMY_SP,
1058                                    kind: VarDeclKind::Var,
1059                                    decls,
1060                                    declare: false,
1061                                    ..Default::default()
1062                                }
1063                                .into();
1064                            }
1065                            return EmptyStmt { span: s.span }.into();
1066                        }
1067                    }
1068
1069                    s.into()
1070                }
1071
1072                Stmt::For(s)
1073                    if match &s.test {
1074                        Some(test) => {
1075                            matches!(&**test, Expr::Lit(Lit::Bool(Bool { value: false, .. })))
1076                        }
1077                        _ => false,
1078                    } =>
1079                {
1080                    if cfg!(feature = "debug") {
1081                        debug!("Optimizing a for statement with a false test");
1082                    }
1083
1084                    let decl = s.body.extract_var_ids_as_var();
1085                    let body = if let Some(var) = decl {
1086                        var.into()
1087                    } else {
1088                        EmptyStmt { span: s.span }.into()
1089                    };
1090
1091                    if s.init.is_some() {
1092                        ForStmt {
1093                            body: Box::new(body),
1094                            update: None,
1095                            ..s
1096                        }
1097                        .into()
1098                    } else {
1099                        body
1100                    }
1101                }
1102
1103                Stmt::While(s) => {
1104                    if let (purity, Known(v)) = s.test.cast_to_bool(self.expr_ctx) {
1105                        if v {
1106                            if purity.is_pure() {
1107                                WhileStmt {
1108                                    test: Lit::Bool(Bool {
1109                                        span: s.test.span(),
1110                                        value: true,
1111                                    })
1112                                    .into(),
1113                                    ..s
1114                                }
1115                                .into()
1116                            } else {
1117                                s.into()
1118                            }
1119                        } else {
1120                            let body = s.body.extract_var_ids_as_var();
1121                            let body = body.map(Box::new).map(Decl::Var).map(Stmt::Decl);
1122                            let body = body.unwrap_or(EmptyStmt { span: s.span }.into());
1123
1124                            if purity.is_pure() {
1125                                body
1126                            } else {
1127                                WhileStmt {
1128                                    body: Box::new(body),
1129                                    ..s
1130                                }
1131                                .into()
1132                            }
1133                        }
1134                    } else {
1135                        s.into()
1136                    }
1137                }
1138
1139                Stmt::DoWhile(s) => {
1140                    if has_conditional_stopper(&[s.clone().into()]) {
1141                        return s.into();
1142                    }
1143
1144                    if let Known(v) = s.test.as_pure_bool(self.expr_ctx) {
1145                        if v {
1146                            // `for(;;);` is shorter than `do ; while(true);`
1147                            ForStmt {
1148                                span: s.span,
1149                                init: None,
1150                                test: None,
1151                                update: None,
1152                                body: s.body,
1153                            }
1154                            .into()
1155                        } else {
1156                            let mut body = prepare_loop_body_for_inlining(*s.body);
1157                            body.visit_mut_with(self);
1158
1159                            if let Some(test) = ignore_result(s.test, true, self.expr_ctx) {
1160                                BlockStmt {
1161                                    span: s.span,
1162                                    stmts: vec![body, test.into_stmt()],
1163                                    ..Default::default()
1164                                }
1165                                .into()
1166                            } else {
1167                                body
1168                            }
1169                        }
1170                    } else {
1171                        s.into()
1172                    }
1173                }
1174
1175                Stmt::Decl(Decl::Var(v)) => {
1176                    let decls = v.decls.move_flat_map(|v| {
1177                        if !is_literal(&v.init) {
1178                            return Some(v);
1179                        }
1180
1181                        //
1182                        match &v.name {
1183                            Pat::Object(o) if o.props.is_empty() => {
1184                                if cfg!(feature = "debug") {
1185                                    debug!("Dropping an object pattern in a var declaration");
1186                                }
1187
1188                                None
1189                            }
1190                            Pat::Array(a) if a.elems.is_empty() => {
1191                                if cfg!(feature = "debug") {
1192                                    debug!("Dropping an array pattern in a var declaration");
1193                                }
1194
1195                                None
1196                            }
1197
1198                            _ => Some(v),
1199                        }
1200                    });
1201
1202                    if decls.is_empty() {
1203                        if cfg!(feature = "debug") {
1204                            debug!("Dropping a useless variable declaration");
1205                        }
1206
1207                        return EmptyStmt { span: v.span }.into();
1208                    }
1209
1210                    VarDecl { decls, ..*v }.into()
1211                }
1212
1213                _ => stmt,
1214            }
1215        })
1216    }
1217
1218    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
1219        self.fold_stmt_like(n)
1220    }
1221
1222    fn visit_mut_switch_stmt(&mut self, s: &mut SwitchStmt) {
1223        s.visit_mut_children_with(self);
1224
1225        if s.cases
1226            .iter()
1227            .any(|case| matches!(case.test.as_deref(), Some(Expr::Update(..))))
1228        {
1229            return;
1230        }
1231
1232        if s.cases.iter().all(|case| {
1233            if let Some(test) = case.test.as_deref() {
1234                if test.may_have_side_effects(self.expr_ctx) {
1235                    return false;
1236                }
1237            }
1238
1239            if case.cons.is_empty() {
1240                return true;
1241            }
1242
1243            matches!(case.cons[0], Stmt::Break(BreakStmt { label: None, .. }))
1244        }) {
1245            s.cases.clear();
1246        }
1247    }
1248
1249    fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
1250        self.maybe_par(cpu_count() * 8, n, |v, n| {
1251            n.visit_mut_with(v);
1252        })
1253    }
1254}
1255
1256impl Remover {
1257    fn fold_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
1258    where
1259        T: StmtLike + VisitWith<Hoister> + VisitMutWith<Self>,
1260    {
1261        let orig_len = stmts.len();
1262
1263        let is_block_stmt = self.normal_block;
1264        self.normal_block = false;
1265
1266        let mut new_stmts = Vec::with_capacity(stmts.len());
1267
1268        self.maybe_par(cpu_count() * 8, &mut *stmts, |visitor, stmt| {
1269            visitor.normal_block = true;
1270            stmt.visit_mut_with(visitor);
1271        });
1272
1273        let mut iter = stmts.take().into_iter();
1274        while let Some(stmt_like) = iter.next() {
1275            let stmt_like = match stmt_like.try_into_stmt() {
1276                Ok(stmt) => {
1277                    let stmt = match stmt {
1278                        // Remove empty statements.
1279                        Stmt::Empty(..) => continue,
1280
1281                        Stmt::Expr(ExprStmt { ref expr, .. })
1282                            if match &**expr {
1283                                Expr::Lit(Lit::Str(..)) => false,
1284                                Expr::Lit(..) => true,
1285                                _ => false,
1286                            } && is_block_stmt =>
1287                        {
1288                            continue
1289                        }
1290
1291                        // Control flow
1292                        Stmt::Throw(..)
1293                        | Stmt::Return { .. }
1294                        | Stmt::Continue { .. }
1295                        | Stmt::Break { .. } => {
1296                            // Hoist function and `var` declarations above return.
1297                            let mut decls = Vec::new();
1298                            let mut hoisted_fns = Vec::new();
1299                            for t in iter {
1300                                match t.try_into_stmt() {
1301                                    Ok(Stmt::Decl(Decl::Fn(f))) => {
1302                                        hoisted_fns.push(T::from(f.into()));
1303                                    }
1304                                    Ok(t) => {
1305                                        let ids = extract_var_ids(&t).into_iter().map(|i| {
1306                                            VarDeclarator {
1307                                                span: i.span,
1308                                                name: i.into(),
1309                                                init: None,
1310                                                definite: false,
1311                                            }
1312                                        });
1313                                        decls.extend(ids);
1314                                    }
1315                                    Err(item) => new_stmts.push(item),
1316                                }
1317                            }
1318
1319                            if !decls.is_empty() {
1320                                new_stmts.push(T::from(
1321                                    VarDecl {
1322                                        span: DUMMY_SP,
1323                                        kind: VarDeclKind::Var,
1324                                        decls,
1325                                        declare: false,
1326                                        ..Default::default()
1327                                    }
1328                                    .into(),
1329                                ));
1330                            }
1331
1332                            let stmt_like = T::from(stmt);
1333                            new_stmts.push(stmt_like);
1334                            new_stmts.extend(hoisted_fns);
1335
1336                            *stmts = new_stmts;
1337                            if stmts.len() != orig_len {
1338                                self.changed = true;
1339
1340                                if cfg!(feature = "debug") {
1341                                    debug!("Dropping statements after a control keyword");
1342                                }
1343                            }
1344
1345                            return;
1346                        }
1347
1348                        Stmt::Block(BlockStmt {
1349                            span,
1350                            mut stmts,
1351                            ctxt,
1352                            ..
1353                        }) => {
1354                            if stmts.is_empty() {
1355                                continue;
1356                            }
1357
1358                            if !is_ok_to_inline_block(&stmts) {
1359                                stmts.visit_mut_with(self);
1360                                BlockStmt { span, stmts, ctxt }.into()
1361                            } else {
1362                                new_stmts.extend(
1363                                    stmts
1364                                        .into_iter()
1365                                        .filter(|s| !matches!(s, Stmt::Empty(..)))
1366                                        .map(T::from),
1367                                );
1368                                continue;
1369                            }
1370                        }
1371
1372                        // Optimize if statement.
1373                        Stmt::If(IfStmt {
1374                            test,
1375                            cons,
1376                            alt,
1377                            span,
1378                        }) => {
1379                            // check if
1380                            match test.cast_to_bool(self.expr_ctx) {
1381                                (purity, Known(val)) => {
1382                                    self.changed = true;
1383                                    if !purity.is_pure() {
1384                                        let expr = ignore_result(test, true, self.expr_ctx);
1385
1386                                        if let Some(expr) = expr {
1387                                            new_stmts.push(T::from(
1388                                                ExprStmt {
1389                                                    span: DUMMY_SP,
1390                                                    expr,
1391                                                }
1392                                                .into(),
1393                                            ));
1394                                        }
1395                                    }
1396
1397                                    if val {
1398                                        // Hoist vars from alt
1399                                        if let Some(var) =
1400                                            alt.and_then(|alt| alt.extract_var_ids_as_var())
1401                                        {
1402                                            new_stmts.push(T::from(var.into()))
1403                                        }
1404                                        *cons
1405                                    } else {
1406                                        // Hoist vars from cons
1407                                        if let Some(var) = cons.extract_var_ids_as_var() {
1408                                            new_stmts.push(T::from(var.into()))
1409                                        }
1410                                        match alt {
1411                                            Some(alt) => *alt,
1412                                            None => continue,
1413                                        }
1414                                    }
1415                                }
1416                                _ => IfStmt {
1417                                    test,
1418                                    cons,
1419                                    alt,
1420                                    span,
1421                                }
1422                                .into(),
1423                            }
1424                        }
1425
1426                        _ => stmt,
1427                    };
1428
1429                    T::from(stmt)
1430                }
1431                Err(stmt_like) => stmt_like,
1432            };
1433
1434            new_stmts.push(stmt_like);
1435        }
1436
1437        *stmts = new_stmts
1438    }
1439}
1440
1441/// Ignores the result.
1442///
1443/// Returns
1444///  - [Some] if `e` has a side effect.
1445///  - [None] if `e` does not have a side effect.
1446#[inline(never)]
1447fn ignore_result(e: Box<Expr>, drop_str_lit: bool, ctx: ExprCtx) -> Option<Box<Expr>> {
1448    match *e {
1449        Expr::Lit(Lit::Num(..))
1450        | Expr::Lit(Lit::Bool(..))
1451        | Expr::Lit(Lit::Null(..))
1452        | Expr::Lit(Lit::Regex(..)) => None,
1453
1454        Expr::Lit(Lit::Str(ref v)) if drop_str_lit || v.value.is_empty() => None,
1455
1456        Expr::Paren(ParenExpr { expr, .. }) => ignore_result(expr, true, ctx),
1457
1458        Expr::Assign(AssignExpr {
1459            op: op!("="),
1460            left: AssignTarget::Simple(left),
1461            right,
1462            ..
1463        }) if match &left {
1464            SimpleAssignTarget::Ident(l) => match &*right {
1465                Expr::Ident(r) => l.sym == r.sym && l.ctxt == r.ctxt,
1466                _ => false,
1467            },
1468            _ => false,
1469        } =>
1470        {
1471            None
1472        }
1473
1474        Expr::Bin(BinExpr {
1475            span,
1476            left,
1477            op,
1478            right,
1479        }) if !op.may_short_circuit() => {
1480            let left = ignore_result(left, true, ctx);
1481            let right = ignore_result(right, true, ctx);
1482
1483            match (left, right) {
1484                (Some(l), Some(r)) => ignore_result(
1485                    ctx.preserve_effects(span, Expr::undefined(span), vec![l, r]),
1486                    true,
1487                    ctx,
1488                ),
1489                (Some(l), None) => Some(l),
1490                (None, Some(r)) => Some(r),
1491                (None, None) => None,
1492            }
1493        }
1494
1495        Expr::Bin(BinExpr {
1496            span,
1497            left,
1498            op,
1499            right,
1500        }) => {
1501            if op == op!("&&") {
1502                let right = if let Some(right) = ignore_result(right, true, ctx) {
1503                    right
1504                } else {
1505                    return ignore_result(left, true, ctx);
1506                };
1507
1508                let l = left.as_pure_bool(ctx);
1509
1510                if let Known(l) = l {
1511                    if l {
1512                        Some(right)
1513                    } else {
1514                        None
1515                    }
1516                } else {
1517                    Some(
1518                        BinExpr {
1519                            span,
1520                            left,
1521                            op,
1522                            right,
1523                        }
1524                        .into(),
1525                    )
1526                }
1527            } else {
1528                debug_assert!(op == op!("||") || op == op!("??"));
1529
1530                let l = left.as_pure_bool(ctx);
1531
1532                if let Known(l) = l {
1533                    if l {
1534                        None
1535                    } else {
1536                        ignore_result(right, true, ctx)
1537                    }
1538                } else {
1539                    let right = ignore_result(right, true, ctx);
1540                    if let Some(right) = right {
1541                        Some(
1542                            BinExpr {
1543                                span,
1544                                left,
1545                                op,
1546                                right,
1547                            }
1548                            .into(),
1549                        )
1550                    } else {
1551                        ignore_result(left, true, ctx)
1552                    }
1553                }
1554            }
1555        }
1556
1557        Expr::Unary(UnaryExpr { span, op, arg }) => match op {
1558            // Don't remove ! from negated iifes.
1559            op!("!")
1560                if match &*arg {
1561                    Expr::Call(call) => match &call.callee {
1562                        Callee::Expr(callee) => matches!(&**callee, Expr::Fn(..)),
1563                        _ => false,
1564                    },
1565                    _ => false,
1566                } =>
1567            {
1568                Some(UnaryExpr { span, op, arg }.into())
1569            }
1570
1571            op!("void") | op!(unary, "+") | op!(unary, "-") | op!("!") | op!("~") => {
1572                ignore_result(arg, true, ctx)
1573            }
1574            _ => Some(UnaryExpr { span, op, arg }.into()),
1575        },
1576
1577        Expr::Array(ArrayLit { span, elems, .. }) => {
1578            let mut has_spread = false;
1579            let elems = elems.move_flat_map(|v| match v {
1580                Some(ExprOrSpread {
1581                    spread: Some(..), ..
1582                }) => {
1583                    has_spread = true;
1584                    Some(v)
1585                }
1586                None => None,
1587                Some(ExprOrSpread { spread: None, expr }) => ignore_result(expr, true, ctx)
1588                    .map(|expr| Some(ExprOrSpread { spread: None, expr })),
1589            });
1590
1591            if elems.is_empty() {
1592                None
1593            } else if has_spread {
1594                Some(ArrayLit { span, elems }.into())
1595            } else {
1596                ignore_result(
1597                    ctx.preserve_effects(
1598                        span,
1599                        Expr::undefined(span),
1600                        elems.into_iter().map(|v| v.unwrap().expr),
1601                    ),
1602                    true,
1603                    ctx,
1604                )
1605            }
1606        }
1607
1608        Expr::Object(ObjectLit { span, props, .. }) => {
1609            let props = props.move_flat_map(|v| match v {
1610                PropOrSpread::Spread(..) => Some(v),
1611                PropOrSpread::Prop(ref p) => {
1612                    if is_literal(&**p) {
1613                        None
1614                    } else {
1615                        Some(v)
1616                    }
1617                }
1618            });
1619
1620            if props.is_empty() {
1621                None
1622            } else {
1623                ignore_result(
1624                    ctx.preserve_effects(
1625                        span,
1626                        Expr::undefined(DUMMY_SP),
1627                        once(ObjectLit { span, props }.into()),
1628                    ),
1629                    true,
1630                    ctx,
1631                )
1632            }
1633        }
1634
1635        Expr::New(NewExpr {
1636            span,
1637            ref callee,
1638            args,
1639            ..
1640        }) if callee.is_pure_callee(ctx) => ignore_result(
1641            ArrayLit {
1642                span,
1643                elems: args
1644                    .map(|args| args.into_iter().map(Some).collect())
1645                    .unwrap_or_else(Default::default),
1646            }
1647            .into(),
1648            true,
1649            ctx,
1650        ),
1651
1652        Expr::Call(CallExpr {
1653            span,
1654            callee: Callee::Expr(ref callee),
1655            args,
1656            ..
1657        }) if callee.is_pure_callee(ctx) => ignore_result(
1658            ArrayLit {
1659                span,
1660                elems: args.into_iter().map(Some).collect(),
1661            }
1662            .into(),
1663            true,
1664            ctx,
1665        ),
1666
1667        Expr::Tpl(Tpl { span, exprs, .. }) => ignore_result(
1668            ctx.preserve_effects(span, Expr::undefined(span), exprs),
1669            true,
1670            ctx,
1671        ),
1672
1673        Expr::TaggedTpl(TaggedTpl { span, tag, tpl, .. }) if tag.is_pure_callee(ctx) => {
1674            ignore_result(
1675                ctx.preserve_effects(span, Expr::undefined(span), tpl.exprs),
1676                true,
1677                ctx,
1678            )
1679        }
1680
1681        //
1682        // Function expressions are useless if they are not used.
1683        //
1684        // As function expressions cannot start with 'function',
1685        // this will be reached only if other things
1686        // are removed while folding children.
1687        Expr::Fn(..) => None,
1688
1689        Expr::Seq(SeqExpr {
1690            span, mut exprs, ..
1691        }) => {
1692            if exprs.is_empty() {
1693                return None;
1694            }
1695
1696            let last = ignore_result(exprs.pop().unwrap(), true, ctx);
1697
1698            exprs.extend(last);
1699
1700            if exprs.is_empty() {
1701                return None;
1702            }
1703
1704            if exprs.len() == 1 {
1705                return Some(exprs.pop().unwrap());
1706            }
1707
1708            Some(SeqExpr { span, exprs }.into())
1709        }
1710
1711        Expr::Cond(CondExpr {
1712            span,
1713            test,
1714            cons,
1715            alt,
1716        }) => {
1717            let alt = if let Some(alt) = ignore_result(alt, true, ctx) {
1718                alt
1719            } else {
1720                return ignore_result(
1721                    BinExpr {
1722                        span,
1723                        left: test,
1724                        op: op!("&&"),
1725                        right: cons,
1726                    }
1727                    .into(),
1728                    true,
1729                    ctx,
1730                );
1731            };
1732
1733            let cons = if let Some(cons) = ignore_result(cons, true, ctx) {
1734                cons
1735            } else {
1736                return ignore_result(
1737                    BinExpr {
1738                        span,
1739                        left: test,
1740                        op: op!("||"),
1741                        right: alt,
1742                    }
1743                    .into(),
1744                    true,
1745                    ctx,
1746                );
1747            };
1748
1749            Some(
1750                CondExpr {
1751                    span,
1752                    test,
1753                    cons,
1754                    alt,
1755                }
1756                .into(),
1757            )
1758        }
1759
1760        _ => Some(e),
1761    }
1762}
1763
1764/// # Returns true for
1765///
1766/// ```js
1767/// {
1768///    var x = 1;
1769/// }
1770/// ```
1771///
1772/// ```js
1773/// {
1774///    var x;
1775///    var y;
1776///    var z;
1777///    {
1778///        var a;
1779///        var b;
1780///    }
1781/// }
1782/// ```
1783///
1784/// ```js
1785/// {
1786///    var a = 0;
1787///    foo();
1788/// }
1789/// ```
1790///
1791/// # Returns false for
1792///
1793/// ```js
1794/// a: {
1795///    break a;
1796///    var x = 1;
1797/// }
1798/// ```
1799fn is_ok_to_inline_block(s: &[Stmt]) -> bool {
1800    // TODO: This may be inlinable if return / throw / break / continue exists
1801    if s.iter().any(is_block_scoped_stuff) {
1802        return false;
1803    }
1804
1805    // variable declared as `var` is hoisted
1806    let last_var = s.iter().rposition(|s| match s {
1807        Stmt::Decl(Decl::Var(v))
1808            if matches!(
1809                &**v,
1810                VarDecl {
1811                    kind: VarDeclKind::Var,
1812                    ..
1813                },
1814            ) =>
1815        {
1816            true
1817        }
1818        _ => false,
1819    });
1820
1821    let last_var = if let Some(pos) = last_var {
1822        pos
1823    } else {
1824        return true;
1825    };
1826
1827    let last_stopper = s.iter().rposition(|s| {
1828        matches!(
1829            s,
1830            Stmt::Return(..) | Stmt::Throw(..) | Stmt::Break(..) | Stmt::Continue(..)
1831        )
1832    });
1833
1834    if let Some(last_stopper) = last_stopper {
1835        last_stopper > last_var
1836    } else {
1837        true
1838    }
1839}
1840
1841fn is_block_scoped_stuff(s: &Stmt) -> bool {
1842    match s {
1843        Stmt::Decl(Decl::Var(v)) if v.kind == VarDeclKind::Const || v.kind == VarDeclKind::Let => {
1844            true
1845        }
1846        Stmt::Decl(Decl::Fn(..)) | Stmt::Decl(Decl::Class(..)) => true,
1847        _ => false,
1848    }
1849}
1850
1851fn prepare_loop_body_for_inlining(stmt: Stmt) -> Stmt {
1852    let span = stmt.span();
1853    let mut stmts = match stmt {
1854        Stmt::Block(BlockStmt { stmts, .. }) => stmts,
1855        _ => vec![stmt],
1856    };
1857
1858    let mut done = false;
1859    stmts.retain(|stmt| {
1860        if done {
1861            return false;
1862        }
1863
1864        match stmt {
1865            Stmt::Break(BreakStmt { label: None, .. })
1866            | Stmt::Continue(ContinueStmt { label: None, .. }) => {
1867                done = true;
1868                false
1869            }
1870
1871            Stmt::Return(..) | Stmt::Throw(..) => {
1872                done = true;
1873                true
1874            }
1875
1876            _ => true,
1877        }
1878    });
1879
1880    BlockStmt {
1881        span,
1882        stmts,
1883        ..Default::default()
1884    }
1885    .into()
1886}
1887
1888fn has_unconditional_stopper(s: &[Stmt]) -> bool {
1889    check_for_stopper(s, false)
1890}
1891
1892fn has_conditional_stopper(s: &[Stmt]) -> bool {
1893    check_for_stopper(s, true)
1894}
1895
1896fn check_for_stopper(s: &[Stmt], only_conditional: bool) -> bool {
1897    struct Visitor {
1898        in_cond: bool,
1899        found: bool,
1900    }
1901
1902    impl Visit for Visitor {
1903        noop_visit_type!();
1904
1905        fn visit_switch_case(&mut self, node: &SwitchCase) {
1906            let old = self.in_cond;
1907            self.in_cond = true;
1908            node.cons.visit_with(self);
1909            self.in_cond = old;
1910        }
1911
1912        fn visit_break_stmt(&mut self, s: &BreakStmt) {
1913            if self.in_cond && s.label.is_none() {
1914                self.found = true
1915            }
1916        }
1917
1918        fn visit_continue_stmt(&mut self, s: &ContinueStmt) {
1919            if self.in_cond && s.label.is_none() {
1920                self.found = true
1921            }
1922        }
1923
1924        fn visit_return_stmt(&mut self, _: &ReturnStmt) {
1925            if self.in_cond {
1926                self.found = true
1927            }
1928        }
1929
1930        fn visit_throw_stmt(&mut self, _: &ThrowStmt) {
1931            if self.in_cond {
1932                self.found = true
1933            }
1934        }
1935
1936        fn visit_class(&mut self, _: &Class) {}
1937
1938        fn visit_function(&mut self, _: &Function) {}
1939
1940        fn visit_if_stmt(&mut self, node: &IfStmt) {
1941            let old = self.in_cond;
1942            self.in_cond = true;
1943            node.cons.visit_with(self);
1944            self.in_cond = true;
1945            node.alt.visit_with(self);
1946            self.in_cond = old;
1947        }
1948    }
1949
1950    let mut v = Visitor {
1951        in_cond: !only_conditional,
1952        found: false,
1953    };
1954    v.visit_stmts(s);
1955    v.found
1956}
1957
1958/// Finds the depth of statements without hitting a block
1959fn stmt_depth(s: &Stmt) -> u32 {
1960    let mut depth = 0;
1961
1962    match s {
1963        // Stop when hitting a statement we know can't increase statement depth.
1964        Stmt::Block(_) | Stmt::Labeled(_) | Stmt::Switch(_) | Stmt::Decl(_) | Stmt::Expr(_) => {}
1965        // Take the max depth of if statements
1966        Stmt::If(i) => {
1967            depth += 1;
1968            if let Some(alt) = &i.alt {
1969                depth += std::cmp::max(stmt_depth(&i.cons), stmt_depth(alt));
1970            } else {
1971                depth += stmt_depth(&i.cons);
1972            }
1973        }
1974        // These statements can have bodies without a wrapping block
1975        Stmt::With(WithStmt { body, .. })
1976        | Stmt::While(WhileStmt { body, .. })
1977        | Stmt::DoWhile(DoWhileStmt { body, .. })
1978        | Stmt::For(ForStmt { body, .. })
1979        | Stmt::ForIn(ForInStmt { body, .. })
1980        | Stmt::ForOf(ForOfStmt { body, .. }) => {
1981            depth += 1;
1982            depth += stmt_depth(body);
1983        }
1984        // All other statements increase the depth by 1
1985        _ => depth += 1,
1986    }
1987
1988    depth
1989}