feat(es/minifier): Optimize switch with side effect and termination tests (#9677)

**Description:**

This is learnt from https://github.com/terser/terser/pull/1044
**Key point:** all the cases (statements) after the last side-effect and
non-terminated one are useless.

There are also some points on whether a statement is side-effect free:
- **Var declaration:** I think var declaration always contains side
effect.
- **Function declaration:** In non-strict mode, function declaration is
same as var declaration. In strict mode, function is declared in
enclosing block scope, so it's side-effect free. But there is a bad case
when we call the function before it is declared in the switch case:
```js
"use strict";
const k = (() => {
    let x = 1;
    switch (x) {
        case y():
        case x: {
            async function y() {
                console.log(123);
            }
        }
    }
    return x;
})();
```

**This is an existing bug.** I'm not sure whether we should fix it since
it is a very bad code and I also find some similar bad cases with
terser.

~I'm also not sure it's appropriate to introduce context variable
`in_strict` for `StmtExt:: may_have_side_effects`, because `in_strict`
could change from `false` to `true`. But this is a **conservative**
mistake and does not break the program. Maybe we should use visitor for
context-aware side effect checker in the future.~

**Related issue:**

 - Closes https://github.com/swc-project/swc/issues/8953
This commit is contained in:
CPunisher 2024-10-28 18:39:05 -07:00 committed by GitHub
parent 2bbd1e8485
commit 7344a638b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 307 additions and 197 deletions

View File

@ -0,0 +1,5 @@
---
swc_ecma_utils: major
---
feat(es/minifier): Optimize switch and replace empty test with side effect and termination tests

View File

@ -1,6 +1,2 @@
//// [stringLiteralsWithSwitchStatements03.ts]
var z;
switch(void 0){
case randBool() ? "foo" : "baz":
case z || "baz":
}
randBool();

View File

@ -25,7 +25,6 @@ switch((M || (M = {})).fn = function(x) {
case void 0 === x ? "undefined" : _type_of(x):
case void 0 === M ? "undefined" : _type_of(M):
case M.fn(1):
default:
}
var x, M, C = function C() {
_class_call_check(this, C);

View File

@ -21,7 +21,12 @@ impl Optimizer<'_> {
expr: &mut Expr,
is_ret_val_ignored: bool,
) -> bool {
let cost = negate_cost(&self.expr_ctx, expr, is_ret_val_ignored, is_ret_val_ignored);
let cost = negate_cost(
&self.ctx.expr_ctx,
expr,
is_ret_val_ignored,
is_ret_val_ignored,
);
if cost >= 0 {
return false;
}
@ -72,11 +77,12 @@ impl Optimizer<'_> {
let ctx = Ctx {
in_bool_ctx: true,
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).negate(&mut e.left, false);
self.with_ctx(ctx).negate(&mut e.right, is_ret_val_ignored);
self.with_ctx(ctx.clone()).negate(&mut e.left, false);
self.with_ctx(ctx.clone())
.negate(&mut e.right, is_ret_val_ignored);
dump_change_detail!("{} => {}", start, dump(&*e, false));

View File

@ -30,11 +30,11 @@ impl Optimizer<'_> {
_ => {}
}
if negate_cost(&self.expr_ctx, &stmt.test, true, false) < 0 {
if negate_cost(&self.ctx.expr_ctx, &stmt.test, true, false) < 0 {
report_change!("if_return: Negating `cond` of an if statement which has cons and alt");
let ctx = Ctx {
in_bool_ctx: true,
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).negate(&mut stmt.test, false);
swap(alt, &mut *stmt.cons);
@ -63,7 +63,7 @@ impl Optimizer<'_> {
_ => return,
};
if !cond.cons.may_have_side_effects(&self.expr_ctx) {
if !cond.cons.may_have_side_effects(&self.ctx.expr_ctx) {
self.changed = true;
report_change!("conditionals: `cond ? useless : alt` => `cond || alt`");
*e = BinExpr {
@ -76,7 +76,7 @@ impl Optimizer<'_> {
return;
}
if !cond.alt.may_have_side_effects(&self.expr_ctx) {
if !cond.alt.may_have_side_effects(&self.ctx.expr_ctx) {
self.changed = true;
report_change!("conditionals: `cond ? cons : useless` => `cond && cons`");
*e = BinExpr {
@ -878,7 +878,7 @@ impl Optimizer<'_> {
) = (&*cons, &*alt)
{
// I don't know why, but terser behaves differently
negate(&self.expr_ctx, &mut test, true, false);
negate(&self.ctx.expr_ctx, &mut test, true, false);
swap(&mut cons, &mut alt);
}

View File

@ -163,7 +163,7 @@ impl Optimizer<'_> {
//
for arg in &*args {
if arg.spread.is_some() || arg.expr.may_have_side_effects(&self.expr_ctx) {
if arg.spread.is_some() || arg.expr.may_have_side_effects(&self.ctx.expr_ctx) {
return;
}
}
@ -233,7 +233,7 @@ impl Optimizer<'_> {
return;
}
if let Known(char_code) = args[0].expr.as_pure_number(&self.expr_ctx) {
if let Known(char_code) = args[0].expr.as_pure_number(&self.ctx.expr_ctx) {
let v = char_code.floor() as u32;
if let Some(v) = char::from_u32(v) {
@ -341,7 +341,7 @@ impl Optimizer<'_> {
}
if let Expr::Call(..) = e {
if let Some(value) = eval_as_number(&self.expr_ctx, e) {
if let Some(value) = eval_as_number(&self.ctx.expr_ctx, e) {
self.changed = true;
report_change!("evaluate: Evaluated an expression as `{}`", value);
@ -367,8 +367,8 @@ impl Optimizer<'_> {
match e {
Expr::Bin(bin @ BinExpr { op: op!("**"), .. }) => {
let l = bin.left.as_pure_number(&self.expr_ctx);
let r = bin.right.as_pure_number(&self.expr_ctx);
let l = bin.left.as_pure_number(&self.ctx.expr_ctx);
let r = bin.right.as_pure_number(&self.ctx.expr_ctx);
if let Known(l) = l {
if let Known(r) = r {
@ -395,9 +395,9 @@ impl Optimizer<'_> {
}
Expr::Bin(bin @ BinExpr { op: op!("/"), .. }) => {
let ln = bin.left.as_pure_number(&self.expr_ctx);
let ln = bin.left.as_pure_number(&self.ctx.expr_ctx);
let rn = bin.right.as_pure_number(&self.expr_ctx);
let rn = bin.right.as_pure_number(&self.ctx.expr_ctx);
if let (Known(ln), Known(rn)) = (ln, rn) {
// Prefer `0/0` over NaN.
if ln == 0.0 && rn == 0.0 {
@ -477,7 +477,7 @@ impl Optimizer<'_> {
}
// Remove rhs of lhs if possible.
let v = left.right.as_pure_bool(&self.expr_ctx);
let v = left.right.as_pure_bool(&self.ctx.expr_ctx);
if let Known(v) = v {
// As we used as_pure_bool, we can drop it.
if v && e.op == op!("&&") {

View File

@ -1,7 +1,7 @@
use swc_common::{util::take::Take, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::debug_assert_valid;
use swc_ecma_utils::{StmtExt, StmtLike};
use swc_ecma_utils::StmtLike;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::Optimizer;
@ -116,7 +116,7 @@ impl Optimizer<'_> {
// for stmt in stmts.iter_mut() {
// let ctx = Ctx {
// is_nested_if_return_merging: true,
// ..self.ctx
// ..self.ctx.clone()
// };
// self.with_ctx(ctx).merge_nested_if_returns(stmt, terminate);
// }
@ -396,7 +396,7 @@ impl Optimizer<'_> {
&& seq
.exprs
.last()
.map(|v| is_pure_undefined(&self.expr_ctx, v))
.map(|v| is_pure_undefined(&self.ctx.expr_ctx, v))
.unwrap_or(true) =>
{
let expr = self.ignore_return_value(&mut cur);
@ -570,7 +570,7 @@ fn always_terminates_with_return_arg(s: &Stmt) -> bool {
fn can_merge_as_if_return(s: &Stmt) -> bool {
fn cost(s: &Stmt) -> Option<isize> {
if let Stmt::Block(..) = s {
if !s.terminates() {
if !swc_ecma_utils::StmtExt::terminates(s) {
return None;
}
}

View File

@ -293,7 +293,7 @@ impl Optimizer<'_> {
let ctx = Ctx {
in_fn_like: true,
top_level: false,
..self.ctx
..self.ctx.clone()
};
let mut optimizer = self.with_ctx(ctx);
match find_body(callee) {

View File

@ -77,7 +77,7 @@ impl Optimizer<'_> {
if ref_count == 0 {
self.mode.store(ident.to_id(), &*init);
if init.may_have_side_effects(&self.expr_ctx) {
if init.may_have_side_effects(&self.ctx.expr_ctx) {
// TODO: Inline partially
return;
}
@ -494,7 +494,7 @@ impl Optimizer<'_> {
}
}
if init.may_have_side_effects(&self.expr_ctx) {
if init.may_have_side_effects(&self.ctx.expr_ctx) {
return;
}
@ -531,13 +531,13 @@ impl Optimizer<'_> {
if body.stmts.len() == 1 {
match &body.stmts[0] {
Stmt::Expr(ExprStmt { expr, .. })
if expr.size(self.expr_ctx.unresolved_ctxt) < cost_limit =>
if expr.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
{
return true
}
Stmt::Return(ReturnStmt { arg: Some(arg), .. })
if arg.size(self.expr_ctx.unresolved_ctxt) < cost_limit =>
if arg.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>
{
return true
}
@ -719,7 +719,7 @@ impl Optimizer<'_> {
})
{
if let Decl::Class(ClassDecl { class, .. }) = decl {
if class_has_side_effect(&self.expr_ctx, class) {
if class_has_side_effect(&self.ctx.expr_ctx, class) {
return;
}
}

View File

@ -65,7 +65,7 @@ impl Optimizer<'_> {
match stmt {
Stmt::While(w) => {
let (purity, val) = w.test.cast_to_bool(&self.expr_ctx);
let (purity, val) = w.test.cast_to_bool(&self.ctx.expr_ctx);
if let Known(false) = val {
if purity.is_pure() {
let changed = UnreachableHandler::preserve_vars(stmt);
@ -86,7 +86,7 @@ impl Optimizer<'_> {
}
Stmt::For(f) => {
if let Some(test) = &mut f.test {
let (purity, val) = test.cast_to_bool(&self.expr_ctx);
let (purity, val) = test.cast_to_bool(&self.ctx.expr_ctx);
if let Known(false) = val {
let changed = UnreachableHandler::preserve_vars(&mut f.body);
self.changed |= changed;

View File

@ -70,18 +70,17 @@ pub(super) fn optimizer<'a>(
"top_retain should not contain empty string"
);
let mut ctx = Ctx::default();
if options.module {
ctx.in_strict = true
}
Optimizer {
marks,
let ctx = Ctx {
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: options.module,
},
..Ctx::default()
};
Optimizer {
marks,
changed: false,
options,
mangle_options,
@ -100,8 +99,10 @@ pub(super) fn optimizer<'a>(
/// Syntactic context.
///
/// This should not be modified directly. Use `.with_ctx()` instead.
#[derive(Debug, Default, Clone, Copy)]
#[derive(Debug, Default, Clone)]
struct Ctx {
expr_ctx: ExprCtx,
/// `true` if the [VarDecl] has const annotation.
#[allow(dead_code)]
has_const_ann: bool,
@ -117,9 +118,6 @@ struct Ctx {
var_kind: Option<VarDeclKind>,
/// `true` if we are in the strict mode. This will be set to `true` for
/// statements **after** `'use strict'`
in_strict: bool,
/// `true` if we are try block. `true` means we cannot be sure about control
/// flow.
in_try_block: bool,
@ -175,7 +173,7 @@ struct Ctx {
}
impl Ctx {
pub fn is_top_level_for_block_level_vars(self) -> bool {
pub fn is_top_level_for_block_level_vars(&self) -> bool {
if !self.top_level {
return false;
}
@ -186,14 +184,13 @@ impl Ctx {
true
}
pub fn in_top_level(self) -> bool {
pub fn in_top_level(&self) -> bool {
self.top_level || !self.in_fn_like
}
}
struct Optimizer<'a> {
marks: Marks,
expr_ctx: ExprCtx,
changed: bool,
options: &'a CompressOptions,
@ -378,11 +375,10 @@ impl Optimizer<'_> {
return;
}
let ctx = Ctx { ..self.ctx };
self.with_ctx(self.ctx.clone()).inject_else(stmts);
self.with_ctx(ctx).inject_else(stmts);
self.with_ctx(ctx).handle_stmt_likes(stmts, will_terminate);
self.with_ctx(self.ctx.clone())
.handle_stmt_likes(stmts, will_terminate);
drop_invalid_stmts(stmts);
@ -413,7 +409,7 @@ impl Optimizer<'_> {
let append_stmts = self.append_stmts.take();
{
let mut child_ctx = self.ctx;
let mut child_ctx = self.ctx.clone();
let mut directive_count = 0;
if !stmts.is_empty() {
@ -424,7 +420,7 @@ impl Optimizer<'_> {
match &v.raw {
Some(value) if value == "\"use strict\"" || value == "'use strict'" => {
child_ctx.in_strict = true;
child_ctx.expr_ctx.in_strict = true;
}
Some(value) if value == "\"use asm\"" || value == "'use asm'" => {
child_ctx.in_asm = true;
@ -446,7 +442,7 @@ impl Optimizer<'_> {
// Don't set in_strict for directive itself.
stmt.visit_mut_with(self);
} else {
let child_optimizer = &mut *self.with_ctx(child_ctx);
let child_optimizer = &mut *self.with_ctx(child_ctx.clone());
stmt.visit_mut_with(child_optimizer);
}
@ -722,7 +718,7 @@ impl Optimizer<'_> {
}
let exprs: Vec<Box<Expr>> =
extract_class_side_effect(&self.expr_ctx, *cls.class.take())
extract_class_side_effect(&self.ctx.expr_ctx, *cls.class.take())
.into_iter()
.filter_map(|mut e| self.ignore_return_value(&mut e))
.map(Box::new)
@ -749,7 +745,7 @@ impl Optimizer<'_> {
let ctx = Ctx {
dont_use_negated_iife: self.ctx.dont_use_negated_iife
|| self.options.side_effects,
..self.ctx
..self.ctx.clone()
};
let new_r = self.with_ctx(ctx).ignore_return_value(right);
@ -938,7 +934,7 @@ impl Optimizer<'_> {
}) if left.is_simple() && !op.may_short_circuit() => {
if let AssignTarget::Simple(expr) = left {
if let SimpleAssignTarget::Member(m) = expr {
if !m.obj.may_have_side_effects(&self.expr_ctx)
if !m.obj.may_have_side_effects(&self.ctx.expr_ctx)
&& (m.obj.is_object()
|| m.obj.is_fn_expr()
|| m.obj.is_arrow()
@ -1213,17 +1209,17 @@ impl Optimizer<'_> {
let ctx = Ctx {
dont_use_negated_iife: self.ctx.dont_use_negated_iife
|| self.options.side_effects,
..self.ctx
..self.ctx.clone()
};
let cons_span = cond.cons.span();
let alt_span = cond.alt.span();
let cons = self
.with_ctx(ctx)
.with_ctx(ctx.clone())
.ignore_return_value(&mut cond.cons)
.map(Box::new);
let alt = self
.with_ctx(ctx)
.with_ctx(ctx.clone())
.ignore_return_value(&mut cond.alt)
.map(Box::new);
@ -1268,7 +1264,7 @@ impl Optimizer<'_> {
}
let ctx = Ctx {
dont_use_negated_iife: idx != 0,
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).ignore_return_value(expr)
})
@ -1278,7 +1274,7 @@ impl Optimizer<'_> {
return exprs.pop().map(|v| *v);
} else {
let is_last_undefined =
is_pure_undefined(&self.expr_ctx, exprs.last().unwrap());
is_pure_undefined(&self.ctx.expr_ctx, exprs.last().unwrap());
// (foo(), void 0) => void foo()
if is_last_undefined {
@ -1314,7 +1310,7 @@ impl Optimizer<'_> {
}
}
Expr::Ident(id) if id.ctxt != self.expr_ctx.unresolved_ctxt => {
Expr::Ident(id) if id.ctxt != self.ctx.expr_ctx.unresolved_ctxt => {
report_change!("ignore_return_value: Dropping a declared ident {}", id);
self.changed = true;
return None;
@ -1398,7 +1394,9 @@ impl Optimizer<'_> {
report_change!("optimizer: Unwrapping block stmt");
self.changed = true;
}
Stmt::Decl(Decl::Fn(..)) if allow_fn_decl && !self.ctx.in_strict => {
Stmt::Decl(Decl::Fn(..))
if allow_fn_decl && !self.ctx.expr_ctx.in_strict =>
{
*s = bs.stmts[0].take();
report_change!("optimizer: Unwrapping block stmt in non strcit mode");
self.changed = true;
@ -1508,7 +1506,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
in_param: true,
..self.ctx
..self.ctx.clone()
};
n.params.visit_mut_with(&mut *self.with_ctx(ctx));
@ -1555,7 +1553,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
is_lhs_of_assign: true,
is_exact_lhs_of_assign: true,
..self.ctx
..self.ctx.clone()
};
e.left.visit_mut_with(&mut *self.with_ctx(ctx));
@ -1574,7 +1572,7 @@ impl VisitMut for Optimizer<'_> {
n.visit_mut_children_with(self);
if let Some(value) = &n.value {
if is_pure_undefined(&self.expr_ctx, value) {
if is_pure_undefined(&self.ctx.expr_ctx, value) {
n.value = None;
}
}
@ -1585,7 +1583,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
in_cond: self.ctx.in_cond || n.op.may_short_circuit(),
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -1619,7 +1617,7 @@ impl VisitMut for Optimizer<'_> {
in_block: true,
scope: n.ctxt,
in_param: false,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -1653,7 +1651,7 @@ impl VisitMut for Optimizer<'_> {
is_lhs_of_assign: false,
is_exact_lhs_of_assign: false,
is_update_arg: false,
..self.ctx
..self.ctx.clone()
};
e.callee.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -1685,7 +1683,7 @@ impl VisitMut for Optimizer<'_> {
is_lhs_of_assign: false,
is_exact_lhs_of_assign: false,
is_update_arg: false,
..self.ctx
..self.ctx.clone()
};
// TODO: Prevent inline if callee is unknown.
e.args.visit_mut_with(&mut *self.with_ctx(ctx));
@ -1703,16 +1701,19 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
dont_invoke_iife: true,
is_update_arg: false,
..self.ctx
..self.ctx.clone()
};
n.super_class.visit_mut_with(&mut *self.with_ctx(ctx));
}
{
let ctx = Ctx {
in_strict: true,
is_update_arg: false,
..self.ctx
expr_ctx: ExprCtx {
in_strict: true,
..self.ctx.clone().expr_ctx
},
..self.ctx.clone()
};
n.body.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -1760,7 +1761,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
executed_multiple_time: true,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -1774,7 +1775,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
is_exported: true,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -1782,7 +1783,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_export_default_decl(&mut self, n: &mut ExportDefaultDecl) {
let ctx = Ctx {
is_exported: true,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -1801,7 +1802,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
is_exported: false,
is_callee: false,
..self.ctx
..self.ctx.clone()
};
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
#[cfg(feature = "trace-ast")]
@ -2095,7 +2096,7 @@ impl VisitMut for Optimizer<'_> {
in_fn_like: true,
is_lhs_of_assign: false,
is_exact_lhs_of_assign: false,
..self.ctx
..self.ctx.clone()
};
f.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -2119,7 +2120,7 @@ impl VisitMut for Optimizer<'_> {
in_fn_like: true,
is_lhs_of_assign: false,
is_exact_lhs_of_assign: false,
..self.ctx
..self.ctx.clone()
};
e.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -2132,7 +2133,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
in_var_decl_of_for_in_or_of_loop: true,
is_exact_lhs_of_assign: n.left.is_pat(),
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).visit_with_prepend(&mut n.left);
}
@ -2140,7 +2141,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
executed_multiple_time: true,
..self.ctx
..self.ctx.clone()
};
n.body.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2154,7 +2155,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
in_var_decl_of_for_in_or_of_loop: true,
is_exact_lhs_of_assign: n.left.is_pat(),
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).visit_with_prepend(&mut n.left);
}
@ -2162,7 +2163,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
executed_multiple_time: true,
..self.ctx
..self.ctx.clone()
};
n.body.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2179,12 +2180,12 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
executed_multiple_time: true,
..self.ctx
..self.ctx.clone()
};
s.body.visit_mut_with(&mut *self.with_ctx(ctx));
s.body.visit_mut_with(&mut *self.with_ctx(ctx.clone()));
self.with_ctx(ctx).optimize_init_of_for_stmt(s);
self.with_ctx(ctx.clone()).optimize_init_of_for_stmt(s);
}
#[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
@ -2199,7 +2200,7 @@ impl VisitMut for Optimizer<'_> {
scope: n.ctxt,
top_level: false,
..self.ctx
..self.ctx.clone()
};
let optimizer = &mut *self.with_ctx(ctx);
@ -2235,8 +2236,8 @@ impl VisitMut for Optimizer<'_> {
}
{
let ctx = self.ctx;
self.with_ctx(ctx).optimize_usage_of_arguments(n);
self.with_ctx(self.ctx.clone())
.optimize_usage_of_arguments(n);
}
self.ctx.in_asm = old_in_asm;
@ -2257,12 +2258,12 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
in_cond: true,
..self.ctx
..self.ctx.clone()
};
n.cons.visit_mut_with(&mut *self.with_ctx(ctx));
n.cons.visit_mut_with(&mut *self.with_ctx(ctx.clone()));
n.alt.visit_mut_with(&mut *self.with_ctx(ctx));
n.alt.visit_mut_with(&mut *self.with_ctx(ctx.clone()));
self.negate_if_stmt(n);
@ -2276,7 +2277,7 @@ impl VisitMut for Optimizer<'_> {
&n.body,
n.label.sym.clone(),
),
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2291,7 +2292,7 @@ impl VisitMut for Optimizer<'_> {
in_obj_of_non_computed_member: !n.prop.is_computed(),
is_exact_lhs_of_assign: false,
is_update_arg: false,
..self.ctx
..self.ctx.clone()
};
n.obj.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2300,7 +2301,7 @@ impl VisitMut for Optimizer<'_> {
is_exact_lhs_of_assign: false,
is_lhs_of_assign: false,
is_update_arg: false,
..self.ctx
..self.ctx.clone()
};
c.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2325,7 +2326,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
let ctx = Ctx {
top_level: true,
..self.ctx
..self.ctx.clone()
};
self.with_ctx(ctx).handle_stmt_likes(stmts, true);
@ -2343,7 +2344,7 @@ impl VisitMut for Optimizer<'_> {
is_callee: true,
is_exact_lhs_of_assign: false,
is_lhs_of_assign: false,
..self.ctx
..self.ctx.clone()
};
n.callee.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2352,7 +2353,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
is_exact_lhs_of_assign: false,
is_lhs_of_assign: false,
..self.ctx
..self.ctx.clone()
};
n.args.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2397,7 +2398,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
var_kind: None,
in_param: true,
..self.ctx
..self.ctx.clone()
};
let mut o = self.with_ctx(ctx);
n.visit_mut_children_with(&mut *o);
@ -2446,7 +2447,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_script(&mut self, s: &mut Script) {
let ctx = Ctx {
top_level: true,
..self.ctx
..self.ctx.clone()
};
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2467,7 +2468,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
dont_use_negated_iife: true,
..self.ctx
..self.ctx.clone()
};
let exprs = n
@ -2480,7 +2481,7 @@ impl VisitMut for Optimizer<'_> {
let _span =
tracing::span!(tracing::Level::ERROR, "seq_expr_with_children").entered();
expr.visit_mut_with(&mut *self.with_ctx(ctx));
expr.visit_mut_with(&mut *self.with_ctx(ctx.clone()));
let is_injected_zero = match &**expr {
Expr::Lit(Lit::Num(v)) => v.span.is_dummy(),
_ => false,
@ -2541,7 +2542,7 @@ impl VisitMut for Optimizer<'_> {
in_bang_arg: false,
is_exported: false,
in_obj_of_non_computed_member: false,
..self.ctx
..self.ctx.clone()
};
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2667,7 +2668,7 @@ impl VisitMut for Optimizer<'_> {
debug_assert_eq!(self.append_stmts.len(), append_len);
if let Stmt::Expr(ExprStmt { expr, .. }) = s {
if is_pure_undefined(&self.expr_ctx, expr) {
if is_pure_undefined(&self.ctx.expr_ctx, expr) {
*s = EmptyStmt { span: DUMMY_SP }.into();
return;
}
@ -2676,7 +2677,7 @@ impl VisitMut for Optimizer<'_> {
if self.options.directives
&& is_directive
&& self.ctx.in_strict
&& self.ctx.expr_ctx.in_strict
&& match &**expr {
Expr::Lit(Lit::Str(Str { value, .. })) => *value == *"use strict",
_ => false,
@ -2690,7 +2691,7 @@ impl VisitMut for Optimizer<'_> {
if self.options.unused {
let can_be_removed = !is_directive
&& !expr.is_ident()
&& !expr.may_have_side_effects(&self.expr_ctx);
&& !expr.may_have_side_effects(&self.ctx.expr_ctx);
if can_be_removed {
self.changed = true;
@ -2807,7 +2808,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
is_exact_lhs_of_assign: false,
is_lhs_of_assign: false,
..self.ctx
..self.ctx.clone()
};
c.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -2832,7 +2833,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
n.tag.visit_mut_with(&mut *self.with_ctx(Ctx {
is_this_aware_callee: true,
..self.ctx
..self.ctx.clone()
}));
n.tpl.exprs.visit_mut_with(self);
@ -2852,7 +2853,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
in_tpl_expr: true,
..self.ctx
..self.ctx.clone()
};
let mut o = self.with_ctx(ctx);
n.visit_mut_children_with(&mut *o);
@ -2867,7 +2868,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_try_stmt(&mut self, n: &mut TryStmt) {
let ctx = Ctx {
in_try_block: true,
..self.ctx
..self.ctx.clone()
};
n.block.visit_mut_with(&mut *self.with_ctx(ctx));
@ -2896,7 +2897,7 @@ impl VisitMut for Optimizer<'_> {
let ctx = Ctx {
in_bang_arg: n.op == op!("!"),
is_delete_arg: n.op == op!("delete"),
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2920,7 +2921,7 @@ impl VisitMut for Optimizer<'_> {
fn visit_mut_update_expr(&mut self, n: &mut UpdateExpr) {
let ctx = Ctx {
is_update_arg: true,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2932,11 +2933,11 @@ impl VisitMut for Optimizer<'_> {
is_update_arg: false,
has_const_ann: false,
var_kind: None,
..self.ctx
..self.ctx.clone()
};
for decl in n.decls.iter_mut() {
decl.init.visit_mut_with(&mut *self.with_ctx(ctx));
decl.init.visit_mut_with(&mut *self.with_ctx(ctx.clone()));
}
}
@ -2947,7 +2948,7 @@ impl VisitMut for Optimizer<'_> {
is_update_arg: false,
has_const_ann: self.has_const_ann(n.ctxt),
var_kind: Some(n.kind),
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
@ -2956,7 +2957,7 @@ impl VisitMut for Optimizer<'_> {
if n.kind == VarDeclKind::Let {
n.decls.iter_mut().for_each(|var| {
if let Some(e) = &var.init {
if is_pure_undefined(&self.expr_ctx, e) {
if is_pure_undefined(&self.ctx.expr_ctx, e) {
self.changed = true;
report_change!(
"Dropping explicit initializer which evaluates to `undefined`"
@ -3060,7 +3061,7 @@ impl VisitMut for Optimizer<'_> {
for v in vars.iter_mut() {
if v.init
.as_deref()
.map(|e| !e.is_ident() && !e.may_have_side_effects(&self.expr_ctx))
.map(|e| !e.is_ident() && !e.may_have_side_effects(&self.ctx.expr_ctx))
.unwrap_or(true)
{
self.drop_unused_var_declarator(v, &mut None);
@ -3179,7 +3180,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
executed_multiple_time: true,
..self.ctx
..self.ctx.clone()
};
n.visit_mut_children_with(&mut *self.with_ctx(ctx));
}
@ -3192,7 +3193,7 @@ impl VisitMut for Optimizer<'_> {
{
let ctx = Ctx {
in_with_stmt: true,
..self.ctx
..self.ctx.clone()
};
n.body.visit_mut_with(&mut *self.with_ctx(ctx));
}
@ -3205,7 +3206,7 @@ impl VisitMut for Optimizer<'_> {
if let Some(arg) = &mut n.arg {
self.compress_undefined(arg);
if !n.delegate && is_pure_undefined(&self.expr_ctx, arg) {
if !n.delegate && is_pure_undefined(&self.ctx.expr_ctx, arg) {
n.arg = None;
}
}

View File

@ -132,20 +132,20 @@ impl Optimizer<'_> {
(lt, rt) if lt != rt => {}
(Type::Obj, Type::Obj) => {}
(Type::Num, Type::Num) => {
let l = n.left.as_pure_number(&self.expr_ctx).opt()?;
let r = n.right.as_pure_number(&self.expr_ctx).opt()?;
let l = n.left.as_pure_number(&self.ctx.expr_ctx).opt()?;
let r = n.right.as_pure_number(&self.ctx.expr_ctx).opt()?;
report_change!("Optimizing: literal comparison => num");
return make_lit_bool(l == r);
}
(Type::Str, Type::Str) => {
let l = &n.left.as_pure_string(&self.expr_ctx).opt()?;
let r = &n.right.as_pure_string(&self.expr_ctx).opt()?;
let l = &n.left.as_pure_string(&self.ctx.expr_ctx).opt()?;
let r = &n.right.as_pure_string(&self.ctx.expr_ctx).opt()?;
report_change!("Optimizing: literal comparison => str");
return make_lit_bool(l == r);
}
(_, _) => {
let l = n.left.as_pure_bool(&self.expr_ctx).opt()?;
let r = n.right.as_pure_bool(&self.expr_ctx).opt()?;
let l = n.left.as_pure_bool(&self.ctx.expr_ctx).opt()?;
let r = n.right.as_pure_bool(&self.ctx.expr_ctx).opt()?;
report_change!("Optimizing: literal comparison => bool");
return make_lit_bool(l == r);
}
@ -201,7 +201,12 @@ impl Optimizer<'_> {
}
pub(super) fn negate(&mut self, e: &mut Expr, is_ret_val_ignored: bool) {
negate(&self.expr_ctx, e, self.ctx.in_bool_ctx, is_ret_val_ignored)
negate(
&self.ctx.expr_ctx,
e,
self.ctx.in_bool_ctx,
is_ret_val_ignored,
)
}
/// This method does
@ -309,7 +314,7 @@ impl Optimizer<'_> {
match bin.op {
op!("&&") => {
let rb = bin.right.as_pure_bool(&self.expr_ctx);
let rb = bin.right.as_pure_bool(&self.ctx.expr_ctx);
let rb = match rb {
Value::Known(v) => v,
_ => return,
@ -324,7 +329,7 @@ impl Optimizer<'_> {
}
}
op!("||") => {
let rb = bin.right.as_pure_bool(&self.expr_ctx);
let rb = bin.right.as_pure_bool(&self.ctx.expr_ctx);
let rb = match rb {
Value::Known(v) => v,
_ => return,

View File

@ -299,11 +299,11 @@ impl Optimizer<'_> {
Prop::Shorthand(..) => false,
Prop::KeyValue(p) => {
p.key.is_computed()
|| p.value.may_have_side_effects(&self.expr_ctx)
|| p.value.may_have_side_effects(&self.ctx.expr_ctx)
|| deeply_contains_this_expr(&p.value)
}
Prop::Assign(p) => {
p.value.may_have_side_effects(&self.expr_ctx)
p.value.may_have_side_effects(&self.ctx.expr_ctx)
|| deeply_contains_this_expr(&p.value)
}
Prop::Getter(p) => p.key.is_computed(),

View File

@ -458,7 +458,7 @@ impl Optimizer<'_> {
}) = e
{
if let (Some(id), Expr::Seq(seq)) = (left.as_ident(), &mut **right) {
if id.ctxt == self.expr_ctx.unresolved_ctxt {
if id.ctxt == self.ctx.expr_ctx.unresolved_ctxt {
return;
}
// Do we really need this?
@ -545,7 +545,7 @@ impl Optimizer<'_> {
}
if let Some(last) = e.exprs.last() {
if is_pure_undefined(&self.expr_ctx, last) {
if is_pure_undefined(&self.ctx.expr_ctx, last) {
self.changed = true;
report_change!("sequences: Shifting void");
@ -1101,7 +1101,7 @@ impl Optimizer<'_> {
}
fn is_ident_skippable_for_seq(&self, a: Option<&Mergable>, e: &Ident) -> bool {
if e.ctxt == self.expr_ctx.unresolved_ctxt
if e.ctxt == self.ctx.expr_ctx.unresolved_ctxt
&& self.options.pristine_globals
&& is_global_var_with_pure_property_access(&e.sym)
{
@ -1389,7 +1389,7 @@ impl Optimizer<'_> {
}
if let Callee::Expr(callee) = &e.callee {
if callee.is_pure_callee(&self.expr_ctx) {
if callee.is_pure_callee(&self.ctx.expr_ctx) {
if !self.is_skippable_for_seq(a, callee) {
return false;
}
@ -1426,7 +1426,7 @@ impl Optimizer<'_> {
Expr::Update(..) => false,
Expr::SuperProp(..) => false,
Expr::Class(_) => e.may_have_side_effects(&self.expr_ctx),
Expr::Class(_) => e.may_have_side_effects(&self.ctx.expr_ctx),
Expr::Paren(e) => self.is_skippable_for_seq(a, &e.expr),
Expr::Unary(e) => self.is_skippable_for_seq(a, &e.arg),
@ -1452,7 +1452,7 @@ impl Optimizer<'_> {
false
}
OptChainBase::Call(e) => {
if e.callee.is_pure_callee(&self.expr_ctx) {
if e.callee.is_pure_callee(&self.ctx.expr_ctx) {
if !self.is_skippable_for_seq(a, &e.callee) {
return false;
}
@ -1496,9 +1496,9 @@ impl Optimizer<'_> {
Mergable::Expr(a) => {
let has_side_effect = match a {
Expr::Assign(a) if a.is_simple_assign() => {
a.right.may_have_side_effects(&self.expr_ctx)
a.right.may_have_side_effects(&self.ctx.expr_ctx)
}
_ => a.may_have_side_effects(&self.expr_ctx),
_ => a.may_have_side_effects(&self.ctx.expr_ctx),
};
if has_side_effect && !usgae.is_fn_local && (usgae.exported || usgae.reassigned) {
log_abort!("a (expr) has side effect");
@ -1507,7 +1507,7 @@ impl Optimizer<'_> {
}
Mergable::Var(a) => {
if let Some(init) = &a.init {
if init.may_have_side_effects(&self.expr_ctx)
if init.may_have_side_effects(&self.ctx.expr_ctx)
&& !usgae.is_fn_local
&& (usgae.exported || usgae.reassigned)
{
@ -1611,7 +1611,7 @@ impl Optimizer<'_> {
return Ok(false);
}
if a.may_have_side_effects(&self.expr_ctx) {
if a.may_have_side_effects(&self.ctx.expr_ctx) {
return Ok(false);
}
}
@ -1713,7 +1713,7 @@ impl Optimizer<'_> {
return Ok(true);
}
if obj.may_have_side_effects(&self.expr_ctx) {
if obj.may_have_side_effects(&self.ctx.expr_ctx) {
return Ok(false);
}
@ -1758,7 +1758,7 @@ impl Optimizer<'_> {
b_assign.left = match AssignTarget::try_from(b_left_expr) {
Ok(v) => v,
Err(b_left_expr) => {
if is_pure_undefined(&self.expr_ctx, &b_left_expr) {
if is_pure_undefined(&self.ctx.expr_ctx, &b_left_expr) {
*b = *b_assign.right.take();
return Ok(true);
}

View File

@ -18,7 +18,7 @@ impl Optimizer<'_> {
{
if args
.iter()
.any(|arg| arg.expr.may_have_side_effects(&self.expr_ctx))
.any(|arg| arg.expr.may_have_side_effects(&self.ctx.expr_ctx))
{
return;
}
@ -57,7 +57,7 @@ impl Optimizer<'_> {
_ => {}
}
let value = n.as_pure_string(&self.expr_ctx);
let value = n.as_pure_string(&self.ctx.expr_ctx);
if let Known(value) = value {
let span = n.span();

View File

@ -1,6 +1,6 @@
use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{prepend_stmt, ExprExt, ExprFactory, StmtExt};
use swc_ecma_utils::{extract_var_ids, prepend_stmt, ExprExt, ExprFactory, StmtExt};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use super::Optimizer;
@ -30,7 +30,7 @@ impl Optimizer<'_> {
let discriminant = &mut stmt.discriminant;
let tail = if let Some(e) = is_primitive(&self.expr_ctx, tail_expr(discriminant)) {
let tail = if let Some(e) = is_primitive(&self.ctx.expr_ctx, tail_expr(discriminant)) {
e
} else {
return;
@ -43,7 +43,7 @@ impl Optimizer<'_> {
for (idx, case) in stmt.cases.iter_mut().enumerate() {
if let Some(test) = case.test.as_ref() {
if let Some(e) = is_primitive(&self.expr_ctx, tail_expr(test)) {
if let Some(e) = is_primitive(&self.ctx.expr_ctx, tail_expr(test)) {
if match (e, tail) {
(Expr::Lit(Lit::Num(e)), Expr::Lit(Lit::Num(tail))) => {
e.value == tail.value
@ -55,7 +55,7 @@ impl Optimizer<'_> {
exact = Some(idx);
break;
} else {
var_ids.extend(case.cons.extract_var_ids())
var_ids.extend(extract_var_ids(&case.cons))
}
} else {
if !may_match_other_than_exact
@ -74,12 +74,12 @@ impl Optimizer<'_> {
if let Some(exact) = exact {
let exact_case = cases.last_mut().unwrap();
let mut terminate = exact_case.cons.terminates();
let mut terminate = exact_case.cons.iter().rev().any(|s| s.terminates());
for case in stmt.cases[(exact + 1)..].iter_mut() {
if terminate {
var_ids.extend(case.cons.extract_var_ids())
var_ids.extend(extract_var_ids(&case.cons))
} else {
terminate |= case.cons.terminates();
terminate |= case.cons.iter().rev().any(|s| s.terminates());
exact_case.cons.extend(case.cons.take())
}
}
@ -90,7 +90,7 @@ impl Optimizer<'_> {
if case.test.is_some() {
true
} else {
var_ids.extend(case.cons.extract_var_ids());
var_ids.extend(extract_var_ids(&case.cons));
false
}
});
@ -202,12 +202,16 @@ impl Optimizer<'_> {
self.merge_cases_with_same_cons(cases);
// last case with no empty body
let mut last = cases.len();
let mut last = 0;
for (idx, case) in cases.iter_mut().enumerate().rev() {
self.changed |= remove_last_break(&mut case.cons);
if !case.cons.is_empty() {
if case
.cons
.iter()
.any(|stmt| stmt.may_have_side_effects(&self.ctx.expr_ctx) || stmt.terminates())
{
last = idx + 1;
break;
}
@ -216,7 +220,7 @@ impl Optimizer<'_> {
let has_side_effect = cases.iter().skip(last).rposition(|case| {
case.test
.as_deref()
.map(|test| test.may_have_side_effects(&self.expr_ctx))
.map(|test| test.may_have_side_effects(&self.ctx.expr_ctx))
.unwrap_or(false)
});
@ -234,10 +238,18 @@ impl Optimizer<'_> {
}
if let Some(default) = default {
if cases.is_empty() {
return;
}
let end = cases
.iter()
.skip(default)
.position(|case| !case.cons.is_empty())
.position(|case| {
case.cons.iter().any(|stmt| {
stmt.may_have_side_effects(&self.ctx.expr_ctx) || stmt.terminates()
})
})
.unwrap_or(0)
+ default;
@ -247,9 +259,12 @@ impl Optimizer<'_> {
let start = cases.iter().enumerate().rposition(|(idx, case)| {
case.test
.as_deref()
.map(|test| test.may_have_side_effects(&self.expr_ctx))
.map(|test| test.may_have_side_effects(&self.ctx.expr_ctx))
.unwrap_or(false)
|| (idx != end && !case.cons.is_empty())
|| (idx != end
&& case.cons.iter().any(|stmt| {
stmt.may_have_side_effects(&self.ctx.expr_ctx) || stmt.terminates()
}))
});
let start = start.map(|s| s + 1).unwrap_or(0);
@ -289,10 +304,10 @@ impl Optimizer<'_> {
cannot_cross_block |= cases[j]
.test
.as_deref()
.map(|test| is_primitive(&self.expr_ctx, test).is_none())
.map(|test| is_primitive(&self.ctx.expr_ctx, test).is_none())
.unwrap_or(false)
|| !(cases[j].cons.is_empty()
|| cases[j].cons.terminates()
|| cases[j].cons.iter().rev().any(|s| s.terminates())
|| j == cases.len() - 1);
if cases[j].cons.is_empty() {
@ -419,7 +434,7 @@ impl Optimizer<'_> {
}
self.changed = true;
report_change!("switches: Turn two cases switch into if else");
let terminate = first.cons.terminates();
let terminate = first.cons.iter().rev().any(|s| s.terminates());
if terminate {
remove_last_break(&mut first.cons);

View File

@ -503,7 +503,8 @@ impl Optimizer<'_> {
);
// This will remove the declaration.
let class = decl.take().class().unwrap();
let mut side_effects = extract_class_side_effect(&self.expr_ctx, *class.class);
let mut side_effects =
extract_class_side_effect(&self.ctx.expr_ctx, *class.class);
if !side_effects.is_empty() {
self.prepend_stmts.push(
@ -687,7 +688,7 @@ impl Optimizer<'_> {
&& !var.exported
&& var.usage_count == 0
&& var.declared
&& (!var.declared_as_fn_param || !used_arguments || self.ctx.in_strict)
&& (!var.declared_as_fn_param || !used_arguments || self.ctx.expr_ctx.in_strict)
{
report_change!(
"unused: Dropping assignment to var '{}{:?}', which is never used",
@ -827,7 +828,7 @@ impl Optimizer<'_> {
PropOrSpread::Prop(p) => match &**p {
Prop::Shorthand(_) => false,
Prop::KeyValue(p) => {
p.key.is_computed() || p.value.may_have_side_effects(&self.expr_ctx)
p.key.is_computed() || p.value.may_have_side_effects(&self.ctx.expr_ctx)
}
Prop::Assign(_) => true,
Prop::Getter(p) => p.key.is_computed(),

View File

@ -95,8 +95,7 @@ impl<'b> Optimizer<'b> {
}
}
let orig_ctx = self.ctx;
self.ctx = ctx;
let orig_ctx = std::mem::replace(&mut self.ctx, ctx);
WithCtx {
reducer: self,
orig_ctx,
@ -158,7 +157,7 @@ impl DerefMut for WithCtx<'_, '_> {
impl Drop for WithCtx<'_, '_> {
fn drop(&mut self) {
self.reducer.ctx = self.orig_ctx;
self.reducer.ctx = self.orig_ctx.clone();
}
}

View File

@ -195,7 +195,7 @@ impl Pure<'_> {
if delete.arg.may_have_side_effects(&ExprCtx {
is_unresolved_ref_safe: true,
..self.expr_ctx.clone()
..self.expr_ctx
}) {
return;
}

View File

@ -59,6 +59,7 @@ pub(crate) fn pure_optimizer<'a>(
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: options.module,
},
ctx: Default::default(),
changed: Default::default(),

View File

@ -48,6 +48,7 @@ fn assert_negate_cost(s: &str, in_bool_ctx: bool, is_ret_val_ignored: bool, expe
let expr_ctx = ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
is_unresolved_ref_safe: false,
in_strict: false,
};
let real = {

View File

@ -31,6 +31,7 @@ impl Evaluator {
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: true,
},
module,

View File

@ -13,7 +13,6 @@
switch(x){
case x:
case y = "FAIL":
default:
}
console.log(y);
})(), console.log("PASS 3"), console.log("PASS 4"), (()=>{
@ -21,7 +20,6 @@
switch('asdf'){
case y = "PASS 5":
case z = "PASS 5":
default:
}
console.log(y, z);
})();

View File

@ -0,0 +1,10 @@
"use strict";
const k = (() => {
let x = 1;
switch (x) {
case x:
async function x() {}
}
return x;
})();
console.log(k);

View File

@ -0,0 +1,2 @@
"use strict";
console.log(1);

View File

@ -1,4 +1,3 @@
var a = 0,
b = 1;
if (0 === a) a = 3;
var a = 0, b = 1;
a = 3;
console.log(a, b);

View File

@ -1,2 +1,2 @@
if (1 === id(123)) "no side effect";
id(123);
console.log("PASS");

View File

@ -1,6 +1,5 @@
var a = 100,
b = 10;
switch (b) {
var a = 100, b = 10;
switch(b){
case a--:
break;
case b:

View File

@ -32,6 +32,7 @@ pub fn dead_branch_remover(unresolved_mark: Mark) -> impl RepeatedJsPass + Visit
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: false,
},
})
}

View File

@ -25,6 +25,7 @@ macro_rules! test_stmt {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
// This is hack
is_unresolved_ref_safe: true,
in_strict: false,
},
})
)

View File

@ -35,6 +35,7 @@ pub fn dce(
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: false,
},
config,
changed: false,

View File

@ -48,6 +48,7 @@ pub fn expr_simplifier(
expr_ctx: ExprCtx {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
is_unresolved_ref_safe: false,
in_strict: false,
},
config,
changed: false,

View File

@ -22,6 +22,7 @@ fn fold(src: &str, expected: &str) {
unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
// This is hack
is_unresolved_ref_safe: true,
in_strict: false,
},
config: super::Config {},
changed: false,

@ -1 +1 @@
Subproject commit a03c69227413dfcac8a1e9f89d93554fa3b8b7fe
Subproject commit 8e9c0b0fb3d548f378420aabbd351087efb5d5e5

View File

@ -35,6 +35,7 @@ where
unresolved_ctxt: SyntaxContext::empty()
.apply_mark(marks.map(|m| m.unresolved_mark).unwrap_or_default()),
is_unresolved_ref_safe: false,
in_strict: false,
},
used_recursively: AHashMap::default(),
};
@ -1188,7 +1189,7 @@ where
} else {
self.with_ctx(ctx).visit_in_cond(case);
}
fallthrough = !case.cons.terminates()
fallthrough = !case.cons.iter().rev().any(|s| s.terminates())
}
}

View File

@ -434,6 +434,8 @@ pub fn extract_var_ids<T: VisitWith<Hoister>>(node: &T) -> Vec<Ident> {
}
pub trait StmtExt {
fn as_stmt(&self) -> &Stmt;
/// Extracts hoisted variables
fn extract_var_ids(&self) -> Vec<Ident>;
@ -460,9 +462,69 @@ pub trait StmtExt {
/// stmts contain top level return/break/continue/throw
fn terminates(&self) -> bool;
fn may_have_side_effects(&self, ctx: &ExprCtx) -> bool {
match self.as_stmt() {
Stmt::Block(block_stmt) => block_stmt
.stmts
.iter()
.any(|stmt| stmt.may_have_side_effects(ctx)),
Stmt::Empty(_) => false,
Stmt::Labeled(labeled_stmt) => labeled_stmt.body.may_have_side_effects(ctx),
Stmt::If(if_stmt) => {
if_stmt.test.may_have_side_effects(ctx)
|| if_stmt.cons.may_have_side_effects(ctx)
|| if_stmt
.alt
.as_ref()
.map_or(false, |stmt| stmt.may_have_side_effects(ctx))
}
Stmt::Switch(switch_stmt) => {
switch_stmt.discriminant.may_have_side_effects(ctx)
|| switch_stmt.cases.iter().any(|case| {
case.test
.as_ref()
.map_or(false, |expr| expr.may_have_side_effects(ctx))
|| case.cons.iter().any(|con| con.may_have_side_effects(ctx))
})
}
Stmt::Try(try_stmt) => {
try_stmt
.block
.stmts
.iter()
.any(|stmt| stmt.may_have_side_effects(ctx))
|| try_stmt.handler.as_ref().map_or(false, |handler| {
handler
.body
.stmts
.iter()
.any(|stmt| stmt.may_have_side_effects(ctx))
})
|| try_stmt.finalizer.as_ref().map_or(false, |finalizer| {
finalizer
.stmts
.iter()
.any(|stmt| stmt.may_have_side_effects(ctx))
})
}
Stmt::Decl(decl) => match decl {
Decl::Class(class_decl) => class_has_side_effect(ctx, &class_decl.class),
Decl::Fn(_) => !ctx.in_strict,
Decl::Var(var_decl) => var_decl.kind == VarDeclKind::Var,
_ => false,
},
Stmt::Expr(expr_stmt) => expr_stmt.expr.may_have_side_effects(ctx),
_ => true,
}
}
}
impl StmtExt for Stmt {
fn as_stmt(&self) -> &Stmt {
self
}
fn extract_var_ids(&self) -> Vec<Ident> {
extract_var_ids(self)
}
@ -470,7 +532,7 @@ impl StmtExt for Stmt {
fn terminates(&self) -> bool {
match self {
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Throw(_) | Stmt::Return(_) => true,
Stmt::Block(block) => block.stmts.terminates(),
Stmt::Block(block) => block.stmts.iter().rev().any(|s| s.terminates()),
Stmt::If(IfStmt {
cons,
alt: Some(alt),
@ -482,6 +544,10 @@ impl StmtExt for Stmt {
}
impl StmtExt for Box<Stmt> {
fn as_stmt(&self) -> &Stmt {
self
}
fn extract_var_ids(&self) -> Vec<Ident> {
extract_var_ids(&**self)
}
@ -491,16 +557,6 @@ impl StmtExt for Box<Stmt> {
}
}
impl StmtExt for Vec<Stmt> {
fn extract_var_ids(&self) -> Vec<Ident> {
extract_var_ids(self)
}
fn terminates(&self) -> bool {
self.iter().rev().any(|s| s.terminates())
}
}
pub struct Hoister {
vars: Vec<Ident>,
}
@ -541,7 +597,7 @@ impl Visit for Hoister {
fn visit_fn_expr(&mut self, _n: &FnExpr) {}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct ExprCtx {
/// This [SyntaxContext] should be applied only to unresolved references.
@ -553,6 +609,10 @@ pub struct ExprCtx {
/// True for argument of `typeof`.
pub is_unresolved_ref_safe: bool,
/// True if we are in the strict mode. This will be set to `true` for
/// statements **after** `'use strict'`
pub in_strict: bool,
}
/// Extension methods for [Expr].
@ -1364,6 +1424,7 @@ pub trait ExprExt {
// Function expression does not have any side effect if it's not used.
Expr::Fn(..) | Expr::Arrow(..) => false,
// It's annoying to pass in_strict
Expr::Class(c) => class_has_side_effect(ctx, &c.class),
Expr::Array(ArrayLit { elems, .. }) => elems
.iter()
@ -1567,7 +1628,11 @@ pub fn class_has_side_effect(expr_ctx: &ExprCtx, c: &Class) -> bool {
}
}
ClassMember::StaticBlock(s) => {
if !s.body.stmts.is_empty() {
if s.body
.stmts
.iter()
.any(|stmt| stmt.may_have_side_effects(expr_ctx))
{
return true;
}
}
@ -1577,6 +1642,7 @@ pub fn class_has_side_effect(expr_ctx: &ExprCtx, c: &Class) -> bool {
false
}
fn and(lt: Value<Type>, rt: Value<Type>) -> Value<Type> {
if lt == rt {
return lt;