allow collapse_vars across conditional branches (#2867)

This commit is contained in:
Alex Lam S.L
2018-02-03 02:44:40 +08:00
committed by GitHub
parent e773f03927
commit e6a2e9e4d0
2 changed files with 213 additions and 38 deletions

View File

@@ -952,32 +952,11 @@ merge(Compressor.prototype, {
var stat_index = statements.length; var stat_index = statements.length;
var scanner = new TreeTransformer(function(node, descend) { var scanner = new TreeTransformer(function(node, descend) {
if (abort) return node; if (abort) return node;
// Scan case expressions first in a switch statement
if (node instanceof AST_Switch) {
if (!hit) {
if (node !== hit_stack[hit_index]) return node;
hit_index++;
}
node.expression = node.expression.transform(scanner);
for (var i = 0, len = node.body.length; !abort && i < len; i++) {
var branch = node.body[i];
if (branch instanceof AST_Case) {
if (!hit) {
if (branch !== hit_stack[hit_index]) continue;
hit_index++;
}
branch.expression = branch.expression.transform(scanner);
if (side_effects || !replace_all) break;
}
}
abort = true;
return node;
}
// Skip nodes before `candidate` as quickly as possible // Skip nodes before `candidate` as quickly as possible
if (!hit) { if (!hit) {
if (node !== hit_stack[hit_index]) return node; if (node !== hit_stack[hit_index]) return node;
hit_index++; hit_index++;
if (hit_index < hit_stack.length) return; if (hit_index < hit_stack.length) return handle_custom_scan_order(node);
hit = true; hit = true;
stop_after = find_stop(node, 0); stop_after = find_stop(node, 0);
if (stop_after === node) abort = true; if (stop_after === node) abort = true;
@@ -997,10 +976,21 @@ merge(Compressor.prototype, {
abort = true; abort = true;
return node; return node;
} }
// Stop only if candidate is found within conditional branches
if (!stop_if_hit && (side_effects || !replace_all)
&& (parent instanceof AST_Binary && lazy_op(parent.operator) && parent.left !== node
|| parent instanceof AST_Conditional && parent.condition !== node
|| parent instanceof AST_If && parent.condition !== node)) {
stop_if_hit = parent;
}
// Replace variable with assignment when found // Replace variable with assignment when found
if (can_replace if (can_replace
&& !(node instanceof AST_SymbolDeclaration) && !(node instanceof AST_SymbolDeclaration)
&& lhs.equivalent_to(node)) { && lhs.equivalent_to(node)) {
if (stop_if_hit) {
abort = true;
return node;
}
if (is_lhs(node, parent)) { if (is_lhs(node, parent)) {
if (value_def) replaced++; if (value_def) replaced++;
return node; return node;
@@ -1056,18 +1046,15 @@ merge(Compressor.prototype, {
|| (sym = is_lhs(node.left, node)) || (sym = is_lhs(node.left, node))
&& (sym instanceof AST_PropAccess || sym.name in lvalues) && (sym instanceof AST_PropAccess || sym.name in lvalues)
|| may_throw || may_throw
&& (in_try ? node.has_side_effects(compressor) : side_effects_external(node)) && (in_try ? node.has_side_effects(compressor) : side_effects_external(node))) {
|| (side_effects || !replace_all)
&& (parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Conditional
|| parent instanceof AST_If)) {
stop_after = node; stop_after = node;
if (node instanceof AST_Scope) abort = true; if (node instanceof AST_Scope) abort = true;
} }
// Skip (non-executed) functions return handle_custom_scan_order(node);
if (node instanceof AST_Scope) return node;
}, function(node) { }, function(node) {
if (!abort && stop_after === node) abort = true; if (abort) return;
if (stop_after === node) abort = true;
if (stop_if_hit === node) stop_if_hit = null;
}); });
var multi_replacer = new TreeTransformer(function(node) { var multi_replacer = new TreeTransformer(function(node) {
if (abort) return node; if (abort) return node;
@@ -1106,6 +1093,7 @@ merge(Compressor.prototype, {
var candidate = hit_stack[hit_stack.length - 1]; var candidate = hit_stack[hit_stack.length - 1];
var value_def = null; var value_def = null;
var stop_after = null; var stop_after = null;
var stop_if_hit = null;
var lhs = get_lhs(candidate); var lhs = get_lhs(candidate);
if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor)) continue; if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor)) continue;
// Locate symbols which may execute code outside of scanning range // Locate symbols which may execute code outside of scanning range
@@ -1149,6 +1137,28 @@ merge(Compressor.prototype, {
} }
} }
function handle_custom_scan_order(node) {
// Skip (non-executed) functions
if (node instanceof AST_Scope) return node;
// Scan case expressions first in a switch statement
if (node instanceof AST_Switch) {
node.expression = node.expression.transform(scanner);
for (var i = 0, len = node.body.length; !abort && i < len; i++) {
var branch = node.body[i];
if (branch instanceof AST_Case) {
if (!hit) {
if (branch !== hit_stack[hit_index]) continue;
hit_index++;
}
branch.expression = branch.expression.transform(scanner);
if (side_effects || !replace_all) break;
}
}
abort = true;
return node;
}
}
function extract_args() { function extract_args() {
var iife, fn = compressor.self(); var iife, fn = compressor.self();
if (fn instanceof AST_Function if (fn instanceof AST_Function
@@ -1265,18 +1275,49 @@ merge(Compressor.prototype, {
hit_stack.pop(); hit_stack.pop();
} }
function find_stop(node, level) { function find_stop(node, level, write_only) {
var parent = scanner.parent(level); var parent = scanner.parent(level);
if (parent instanceof AST_Binary) return node; if (parent instanceof AST_Assign) {
if (write_only
&& !(parent.left instanceof AST_PropAccess
|| parent.left.name in lvalues)) {
return find_stop(parent, level + 1, write_only);
}
return node;
}
if (parent instanceof AST_Binary) {
if (write_only && (!lazy_op(parent.operator) || parent.left === node)) {
return find_stop(parent, level + 1, write_only);
}
return node;
}
if (parent instanceof AST_Call) return node; if (parent instanceof AST_Call) return node;
if (parent instanceof AST_Case) return node; if (parent instanceof AST_Case) return node;
if (parent instanceof AST_Conditional) return node; if (parent instanceof AST_Conditional) {
if (parent instanceof AST_Definitions) return find_stop(parent, level + 1); if (write_only && parent.condition === node) {
if (parent instanceof AST_Exit) return node; return find_stop(parent, level + 1, write_only);
if (parent instanceof AST_If) return node; }
return node;
}
if (parent instanceof AST_Definitions) {
return find_stop(parent, level + 1, true);
}
if (parent instanceof AST_Exit) {
return write_only ? find_stop(parent, level + 1, write_only) : node;
}
if (parent instanceof AST_If) {
if (write_only && parent.condition === node) {
return find_stop(parent, level + 1, write_only);
}
return node;
}
if (parent instanceof AST_IterationStatement) return node; if (parent instanceof AST_IterationStatement) return node;
if (parent instanceof AST_Sequence) return find_stop(parent, level + 1); if (parent instanceof AST_Sequence) {
if (parent instanceof AST_SimpleStatement) return find_stop(parent, level + 1); return find_stop(parent, level + 1, parent.tail_node() !== node);
}
if (parent instanceof AST_SimpleStatement) {
return find_stop(parent, level + 1, true);
}
if (parent instanceof AST_Switch) return node; if (parent instanceof AST_Switch) return node;
if (parent instanceof AST_VarDef) return node; if (parent instanceof AST_VarDef) return node;
return null; return null;

View File

@@ -4249,3 +4249,137 @@ issue_2858: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
cond_branch_1: {
options = {
collapse_vars: true,
sequences: true,
unused: true,
}
input: {
function f1(b, c) {
var log = console.log;
var a = ++c;
if (b) b++;
log(a, b);
}
function f2(b, c) {
var log = console.log;
var a = ++c;
b && b++;
log(a, b);
}
function f3(b, c) {
var log = console.log;
var a = ++c;
b ? b++ : b--;
log(a, b);
}
f1(1, 2);
f2(3, 4);
f3(5, 6);
}
expect: {
function f1(b, c) {
var log = console.log;
if (b) b++;
log(++c, b);
}
function f2(b, c) {
var log = console.log;
b && b++,
log(++c, b);
}
function f3(b, c) {
var log = console.log;
b ? b++ : b--,
log(++c, b);
}
f1(1, 2),
f2(3, 4),
f3(5, 6);
}
expect_stdout: [
"3 2",
"5 4",
"7 6",
]
}
cond_branch_2: {
options = {
collapse_vars: true,
sequences: true,
unused: true,
}
input: {
function f1(b, c) {
var log = console.log;
var a = ++c;
if (b) b += a;
log(a, b);
}
function f2(b, c) {
var log = console.log;
var a = ++c;
b && (b += a);
log(a, b);
}
function f3(b, c) {
var log = console.log;
var a = ++c;
b ? b += a : b--;
log(a, b);
}
f1(1, 2);
f2(3, 4);
f3(5, 6);
}
expect: {
function f1(b, c) {
var log = console.log;
var a = ++c;
if (b) b += a;
log(a, b);
}
function f2(b, c) {
var log = console.log;
var a = ++c;
b && (b += a),
log(a, b);
}
function f3(b, c) {
var log = console.log;
var a = ++c;
b ? b += a : b--,
log(a, b);
}
f1(1, 2),
f2(3, 4),
f3(5, 6);
}
expect_stdout: [
"3 4",
"5 8",
"7 12",
]
}
cond_branch_switch: {
options = {
collapse_vars: true,
}
input: {
var c = 0;
if (c = 1 + c, 0) switch (c = 1 + c) {
}
console.log(c);
}
expect: {
var c = 0;
if (c = 1 + c, 0) switch (c = 1 + c) {
}
console.log(c);
}
expect_stdout: "1"
}