From 08c386f363c8e3549e63c2d996c0e4f11c4cf116 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 29 Jul 2022 22:25:17 +0100 Subject: [PATCH] enhance `if_return` (#5582) --- lib/compress.js | 70 ++++++++----- test/compress/if_return.js | 195 +++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 25 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 27002f0a..327b359a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1965,6 +1965,12 @@ Compressor.prototype.compress = function(node) { block = compressor.parent(level++); } else if (block instanceof AST_LabeledStatement) { block = block.body; + } else if (block instanceof AST_SwitchBranch) { + var branches = compressor.parent(level); + if (branches.body[branches.body.length - 1] === block || has_break(block.body)) { + level++; + block = branches; + } } do { stat = block; @@ -1975,8 +1981,16 @@ Compressor.prototype.compress = function(node) { && (block instanceof AST_BlockStatement || block instanceof AST_Catch || block instanceof AST_Scope + || block instanceof AST_SwitchBranch || block instanceof AST_Try) && is_last_statement(block.body, stat)); + + function has_break(stats) { + for (var i = stats.length; --i >= 0;) { + if (stats[i] instanceof AST_Break) return true; + } + return false; + } } function find_loop_scope_try() { @@ -3437,7 +3451,7 @@ Compressor.prototype.compress = function(node) { var changed = false; var parent = compressor.parent(); var self = compressor.self(); - var exit, merge_exit; + var jump, merge_jump; var in_iife = in_lambda && parent && parent.TYPE == "Call" && parent.expression === self; var chain_if_returns = in_lambda && compressor.option("conditionals") && compressor.option("sequences"); var multiple_if_returns = has_multiple_if_returns(statements); @@ -3447,6 +3461,7 @@ Compressor.prototype.compress = function(node) { var next = statements[j]; if (in_lambda && !next && stat instanceof AST_Return + && !(self instanceof AST_SwitchBranch) && !(in_try && in_try.bfinally && in_async_generator(in_lambda))) { var body = stat.value; if (!body) { @@ -3494,7 +3509,7 @@ Compressor.prototype.compress = function(node) { stat.condition = cond; statements[j] = stat.body; stat.body = next; - if (next === exit) exit = null; + if (next === jump) jump = null; statements[i] = stat; statements[i] = stat.transform(compressor); continue; @@ -3538,7 +3553,7 @@ Compressor.prototype.compress = function(node) { changed = true; stat = stat.clone(); stat.alternative = next; - if (next === exit) exit = null; + if (next === jump) jump = null; statements.splice(i, 1, stat.transform(compressor)); statements.splice(j, 1); continue; @@ -3583,12 +3598,12 @@ Compressor.prototype.compress = function(node) { } } - if (stat instanceof AST_Exit) { - exit = stat; + if (stat instanceof AST_Break || stat instanceof AST_Exit) { + jump = stat; continue; } - if (exit && exit === next) eliminate_returns(stat); + if (jump && jump === next) eliminate_returns(stat); } return changed; @@ -3610,38 +3625,41 @@ Compressor.prototype.compress = function(node) { } function match_return(ab, exact) { - if (!exit) return false; - if (exit.TYPE != ab.TYPE) return false; + if (!jump) return false; + if (jump.TYPE != ab.TYPE) return false; var value = ab.value; if (!value) return false; - var equals = exit.equals(ab); + var equals = jump.equals(ab); if (!equals && value instanceof AST_Sequence) { value = value.tail_node(); - if (exit.value && exit.value.equals(value)) equals = 2; + if (jump.value && jump.value.equals(value)) equals = 2; } - if (!equals && !exact && exit.value instanceof AST_Sequence) { - if (exit.value.tail_node().equals(value)) equals = 3; + if (!equals && !exact && jump.value instanceof AST_Sequence) { + if (jump.value.tail_node().equals(value)) equals = 3; } return equals; } function can_drop_abort(ab) { if (ab instanceof AST_Exit) { - if (merge_exit = match_return(ab)) return true; + if (merge_jump = match_return(ab)) return true; if (!in_lambda) return false; if (!(ab instanceof AST_Return)) return false; - if (is_undefined(ab.value)) return true; - return ab.value instanceof AST_Sequence && is_undefined(ab.value.tail_node()); + var value = ab.value; + if (value && !is_undefined(value.tail_node())) return false; + if (self instanceof AST_SwitchBranch) merge_jump = 4; + return true; } if (!(ab instanceof AST_LoopControl)) return false; var lct = compressor.loopcontrol_target(ab); if (ab instanceof AST_Continue) return match_target(loop_body(lct)); if (lct instanceof AST_IterationStatement) return false; + if (jump) merge_jump = jump.equals(ab); return match_target(lct); } function can_merge_flow(ab) { - merge_exit = false; + merge_jump = false; if (!can_drop_abort(ab)) return false; for (var j = statements.length; --j > i;) { var stat = statements[j]; @@ -3663,12 +3681,12 @@ Compressor.prototype.compress = function(node) { var lexical = false; var start = i + 1; var end; - if (merge_exit) { - end = statements.lastIndexOf(exit); + if (merge_jump) { + end = statements.lastIndexOf(jump); if (end < 0) end = statements.length; } else { end = statements.length; - exit = null; + jump = null; } var tail = statements.splice(start, end - start).filter(function(stat) { if (stat instanceof AST_LambdaDefinition) { @@ -3678,11 +3696,11 @@ Compressor.prototype.compress = function(node) { if (is_lexical_definition(stat)) lexical = true; return true; }); - if (merge_exit === 3) { - tail.push(make_node(AST_SimpleStatement, exit.value, { - body: make_sequence(exit.value, exit.value.expressions.slice(0, -1)), + if (merge_jump === 3) { + tail.push(make_node(AST_SimpleStatement, jump.value, { + body: make_sequence(jump.value, jump.value.expressions.slice(0, -1)), })); - exit.value = exit.value.tail_node(); + jump.value = jump.value.tail_node(); } [].push.apply(lexical ? tail : statements, defuns); return tail; @@ -3690,6 +3708,8 @@ Compressor.prototype.compress = function(node) { function trim_return(value, mode) { if (value) switch (mode) { + case 4: + return value; case 3: if (!(value instanceof AST_Sequence)) break; case 2: @@ -3705,7 +3725,7 @@ Compressor.prototype.compress = function(node) { } block.pop(); var value = ab.value; - if (merge_exit) value = trim_return(value, merge_exit); + if (merge_jump) value = trim_return(value, merge_jump); if (value) block.push(make_node(AST_SimpleStatement, value, { body: value })); return body; } @@ -3757,7 +3777,7 @@ Compressor.prototype.compress = function(node) { } else if (stat instanceof AST_LabeledStatement) { stat.body = eliminate_returns(stat.body); } else if (stat instanceof AST_Try) { - if (!stat.bfinally || !exit.value || exit.value.is_constant()) { + if (!stat.bfinally || !jump.value || jump.value.is_constant()) { if (stat.bcatch) eliminate_returns(stat.bcatch); var trimmed = eliminate_returns(stat.body.pop(), true); if (trimmed) stat.body.push(trimmed); diff --git a/test/compress/if_return.js b/test/compress/if_return.js index c56c61d4..ac7cb5cd 100644 --- a/test/compress/if_return.js +++ b/test/compress/if_return.js @@ -1393,3 +1393,198 @@ void_match: { "foo", ] } + +switch_break: { + options = { + conditionals: true, + if_return: true, + } + input: { + function f(a) { + switch (a) { + default: + if (console.log("foo")) + break; + while (console.log("bar")); + case 42: + if (console.log("baz")) + break; + while (console.log("moo")); + break; + case null: + if (console.log("moz")) + break; + } + } + f(); + f(42); + f(null); + } + expect: { + function f(a) { + switch (a) { + default: + if (console.log("foo")) + break; + while (console.log("bar")); + case 42: + if (!console.log("baz")) + while (console.log("moo")); + break; + case null: + console.log("moz"); + } + } + f(); + f(42); + f(null); + } + expect_stdout: [ + "foo", + "bar", + "baz", + "moo", + "baz", + "moo", + "moz", + ] +} + +switch_return_1: { + options = { + dead_code: true, + if_return: true, + } + input: { + function f(a) { + switch (a) { + case console.log("PASS"): + return; + break; + case 42: + FAIL; + } + } + f(); + } + expect: { + function f(a) { + switch (a) { + case console.log("PASS"): + return; + case 42: + FAIL; + } + } + f(); + } + expect_stdout: "PASS" +} + +switch_return_2: { + options = { + if_return: true, + } + input: { + function f(a) { + switch (a) { + case console.log("PASS"): + if (console) + return; + break; + case 42: + FAIL; + } + } + f(); + } + expect: { + function f(a) { + switch (a) { + case console.log("PASS"): + if (console); + break; + case 42: + FAIL; + } + } + f(); + } + expect_stdout: "PASS" +} + +switch_return_3: { + options = { + if_return: true, + side_effects: true, + } + input: { + function f(a) { + switch (a) { + case console.log("foo"): + if (console) + return void console.log("bar"); + break; + case 42: + FAIL; + } + } + f(); + } + expect: { + function f(a) { + switch (a) { + case console.log("foo"): + if (console) + console.log("bar"); + break; + case 42: + FAIL; + } + } + f(); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +switch_return_4: { + options = { + conditionals: true, + if_return: true, + side_effects: true, + } + input: { + function f(a) { + switch (a) { + case console.log("foo"): + if (console) { + console.log("bar"); + return; + } + break; + case 42: + FAIL; + } + } + f(); + } + expect: { + function f(a) { + switch (a) { + case console.log("foo"): + console && console.log("bar"); + break; + case 42: + FAIL; + } + } + f(); + } + expect_stdout: [ + "foo", + "bar", + ] +}