Skip to content

Commit

Permalink
Convert simple switch statements into if statements
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed Aug 13, 2021
1 parent 34c1858 commit 2cc073f
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 75 deletions.
154 changes: 108 additions & 46 deletions lib/compress/index.js
Expand Up @@ -1615,41 +1615,6 @@ def_optimize(AST_Switch, function(self, compressor) {
}
}

// If all cases fall through into the final branch, and a default is present anywhere,
// we can collapse all branches.
if (default_branch) {
let i = 0;
for (; i < body.length; i++) {
let branch = body[i];
if (branch.body.length > 0) break;
if (branch === default_branch) continue;
// It's actually ok for the last branch's expression to have a side-effect
if (branch.expression.has_side_effects(compressor)) break;
}
// 3 cases:
// 1. i < body.length - 1, there's a side-effect/non-empty branch, and nothing to optimize.
// 2. i === body.length - 1, the last branch has a side-effect/non-empty, but that can be collapsed.
// 3. i === body.length, all branches are empty, and we can pretend only an empty default branch exists.
if (i >= body.length - 1) {
if (i < body.length) {
let last = body[i];
if (last !== default_branch) {
default_branch.body = make_node(AST_BlockStatement, last, {
body: [
make_node(AST_SimpleStatement, last.expression, { body: last.expression })
].concat(last.body),
}).optimize(compressor);
}
}
body = [default_branch];
}
}

if (body.length > 0) {
body[0].body = decl.concat(body[0].body);
}
self.body = body;

// Try to prune any empty branches at the end of the switch statement.
{
let i = body.length - 1;
Expand Down Expand Up @@ -1684,14 +1649,41 @@ def_optimize(AST_Switch, function(self, compressor) {
body.length = pointer;
}
}

// Collapse branches into the default.
if (default_branch) {
let i = find_significant_branch_index(body, 0, 1);
// 3 cases:
if (i >= body.length - 1) {
// 1. i === body.length - 1, the last branch has a side-effect/non-empty, but that can be collapsed.
// 2. i === body.length, all branches are empty, and we can pretend only an empty default branch exists.
if (i < body.length) {
let last = body[i];
if (last !== default_branch) {
default_branch.body = make_node(AST_BlockStatement, last, {
body: [
make_node(AST_SimpleStatement, last.expression, { body: last.expression })
].concat(last.body),
}).optimize(compressor);
}
}
body = [default_branch];
}
}

if (body.length > 0) {
body[0].body = decl.concat(body[0].body);
}
self.body = body;

if (body.length == 0) {
return make_node(AST_BlockStatement, self, {
body: decl.concat(make_node(AST_SimpleStatement, self.expression, {
body: self.expression
}))
}).optimize(compressor);
}
if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) {
if (body.length == 1) {
var has_break = false;
var tw = new TreeWalker(function(node) {
if (has_break
Expand All @@ -1702,18 +1694,76 @@ def_optimize(AST_Switch, function(self, compressor) {
});
self.walk(tw);
if (!has_break) {
var statements = body[0].body.slice();
var exp = body[0].expression;
if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, {
body: exp
}));
statements.unshift(make_node(AST_SimpleStatement, self.expression, {
body:self.expression
}));
return make_node(AST_BlockStatement, self, {
body: statements
let branch = body[0];
if (branch === exact_match || branch === default_branch) {
let statements = [make_node(AST_SimpleStatement, self.expression, {
body: self.expression
})];
if (branch.expression) {
statements.push(make_node(AST_SimpleStatement, branch.expression, {
body: branch.expression
}));
}
return make_node(AST_BlockStatement, branch, {
body: statements.concat(branch.body)
}).optimize(compressor);
} else {
return make_node(AST_If, self, {
condition: make_node(AST_Binary, self, {
operator: "===",
left: self.expression,
right: branch.expression,
}),
body: make_node(AST_BlockStatement, branch, {
body: branch.body
}),
alternative: null
}).optimize(compressor);
}
}
}
if (body.length === 2 && default_branch) {
let branch = body[0] === default_branch ? body[1] : body[0];
if (aborts(body[0])) {
body[0].body.pop();
return make_node(AST_If, self, {
condition: make_node(AST_Binary, self, {
operator: "===",
left: self.expression,
right: branch.expression,
}),
body: make_node(AST_BlockStatement, branch, {
body: branch.body
}),
alternative: make_node(AST_BlockStatement, default_branch, {
body: default_branch.body
})
}).optimize(compressor);
}
let operator = "===";
let consequent = branch;
let always = default_branch;
if (body[0] === default_branch) {
operator = "!==";
consequent = default_branch;
always = branch;
}
return make_node(AST_BlockStatement, self, {
body: [
make_node(AST_If, self, {
condition: make_node(AST_Binary, self, {
operator: operator,
left: self.expression,
right: branch.expression,
}),
body: make_node(AST_BlockStatement, consequent, {
body: consequent.body,
}),
alternative: null
})
].concat(always.body)
}).optimize(compressor);

}
return self;

Expand All @@ -1735,6 +1785,18 @@ def_optimize(AST_Switch, function(self, compressor) {
let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
return bblock.equivalent_to(pblock);
}
function find_significant_branch_index(body, start_index, direction) {
let i = start_index;
for (; i < body.length && i >= 0; i += direction) {
if (is_significant_branch(body[i])) return i;
}
return i;
}
function is_significant_branch(branch) {
if (branch.body.length > 0) return true;
if (branch instanceof AST_Default) return false;
return branch.expression.has_side_effects(compressor);
}
});

def_optimize(AST_Try, function(self, compressor) {
Expand Down
118 changes: 89 additions & 29 deletions test/compress/switch.js
Expand Up @@ -266,9 +266,7 @@ drop_default_1: {
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
if ("bar" === foo) baz();
}
}

Expand All @@ -285,9 +283,7 @@ drop_default_2: {
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
if ("bar" === foo) baz();
}
}

Expand All @@ -305,11 +301,8 @@ keep_default: {
}
}
expect: {
switch (foo) {
case 'bar': baz();
default:
something();
}
if ('bar' === foo) baz();
something();
}
}

Expand Down Expand Up @@ -540,10 +533,7 @@ collapse_into_default_8: {
}
expect: {
console.log(function (foo) {
switch (foo) {
case 1:
return 1;
}
if (1 === foo) return 1;
}(1));
}
}
Expand Down Expand Up @@ -595,9 +585,7 @@ drop_case: {
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
if ('bar' === foo) baz();
}
}

Expand All @@ -621,6 +609,84 @@ keep_case: {
}
}

if_else: {
options = {
dead_code: true,
switches: true,
}
input: {
switch (foo) {
case 'bar':
bar();
break;
default:
other();
}
}
expect: {
if ('bar' === foo) bar();
else other();
}
}

if_else2: {
options = {
dead_code: true,
switches: true,
}
input: {
switch (foo) {
case 'bar':
bar();
default:
other();
}
}
expect: {
if ('bar' === foo) bar();
other();
}
}

if_else3: {
options = {
dead_code: true,
switches: true,
}
input: {
switch (foo) {
default:
other();
break;
case 'bar':
bar();
}
}
expect: {
if ('bar' === foo) bar();
else other();
}
}

if_else4: {
options = {
dead_code: true,
switches: true,
}
input: {
switch (foo) {
default:
other();
case 'bar':
bar();
}
}
expect: {
if ('bar' !== foo) other();
bar();
}
}

issue_376: {
options = {
dead_code: true,
Expand All @@ -638,10 +704,7 @@ issue_376: {
}
}
expect: {
switch (true) {
case boolCondition:
console.log(1);
}
if (true === boolCondition) console.log(1);
}
}

Expand Down Expand Up @@ -801,6 +864,8 @@ issue_1679: {
dead_code: true,
evaluate: true,
switches: true,
conditionals: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
Expand Down Expand Up @@ -830,15 +895,14 @@ issue_1679: {
case !function x() {}:
break;
case b--:
0;
a--;
case (a++):
}
}
f();
console.log(a, b);
}
expect_stdout: true
expect_stdout: ["99 8"]
}

issue_1680_1: {
Expand Down Expand Up @@ -998,11 +1062,7 @@ issue_1705_1: {
}
expect: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
}
if (0 !== a) console.log("FAIL");
}
expect_stdout: true
}
Expand Down

0 comments on commit 2cc073f

Please sign in to comment.