mirror of
https://github.com/swc-project/swc.git
synced 2024-11-29 03:32:32 +03:00
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:
parent
2bbd1e8485
commit
7344a638b5
5
.changeset/happy-planes-dance.md
Normal file
5
.changeset/happy-planes-dance.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
swc_ecma_utils: major
|
||||
---
|
||||
|
||||
feat(es/minifier): Optimize switch and replace empty test with side effect and termination tests
|
@ -1,6 +1,2 @@
|
||||
//// [stringLiteralsWithSwitchStatements03.ts]
|
||||
var z;
|
||||
switch(void 0){
|
||||
case randBool() ? "foo" : "baz":
|
||||
case z || "baz":
|
||||
}
|
||||
randBool();
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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!("&&") {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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 = {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
})();
|
||||
|
10
crates/swc_ecma_minifier/tests/fixture/issues/8953/input.js
Normal file
10
crates/swc_ecma_minifier/tests/fixture/issues/8953/input.js
Normal file
@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
const k = (() => {
|
||||
let x = 1;
|
||||
switch (x) {
|
||||
case x:
|
||||
async function x() {}
|
||||
}
|
||||
return x;
|
||||
})();
|
||||
console.log(k);
|
@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
console.log(1);
|
@ -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);
|
||||
|
@ -1,2 +1,2 @@
|
||||
if (1 === id(123)) "no side effect";
|
||||
id(123);
|
||||
console.log("PASS");
|
||||
|
@ -1,6 +1,5 @@
|
||||
var a = 100,
|
||||
b = 10;
|
||||
switch (b) {
|
||||
var a = 100, b = 10;
|
||||
switch(b){
|
||||
case a--:
|
||||
break;
|
||||
case b:
|
||||
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user