diff --git a/lib/compress.js b/lib/compress.js index 3b735b96..b1554863 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1950,20 +1950,18 @@ Compressor.prototype.compress = function(node) { return statements; function last_of(compressor, predicate) { - var block = compressor.self(), level = 0; + var block = compressor.self(), level = 0, stat; do { if (block instanceof AST_Catch) { block = compressor.parent(level++); } else if (block instanceof AST_LabeledStatement) { block = block.body; } - var stat = null; - while (true) { - if (predicate(block)) return true; - block = compressor.parent(level++); - if (!(block instanceof AST_If)) break; + do { stat = block; - } + if (predicate(stat)) return stat; + block = compressor.parent(level++); + } while (block instanceof AST_If); } while (stat && (block instanceof AST_BlockStatement || block instanceof AST_Catch @@ -3411,7 +3409,8 @@ Compressor.prototype.compress = function(node) { var j = next_index(i); var next = statements[j]; - if (in_lambda && !next && stat instanceof AST_Return) { + if (in_lambda && !next && stat instanceof AST_Return + && !(in_try && in_try.bfinally && in_async_generator(in_lambda))) { if (!stat.value) { changed = true; statements.splice(i, 1); @@ -3550,7 +3549,10 @@ Compressor.prototype.compress = function(node) { if (stat instanceof AST_Exit) { exit = stat; exit_defs = null; + continue; } + + if (exit && exit === next) eliminate_returns(stat); } return changed; @@ -3571,24 +3573,36 @@ Compressor.prototype.compress = function(node) { }); } + function match_return(ab) { + if (!exit) return false; + if (exit.TYPE != ab.TYPE) return false; + var value = ab.value; + var equals = exit.equivalent_to(ab); + if (!equals && value instanceof AST_Sequence) { + value = value.tail_node(); + equals = exit.value ? exit.value.equivalent_to(value) : is_undefined(value); + } + if (!equals) return false; + if (exit_defs == null) { + exit_defs = new Dictionary(); + exit.walk(new TreeWalker(function(node) { + if (node instanceof AST_SymbolRef) exit_defs.set(node.name, node.definition()); + })); + if (!exit_defs.size()) exit_defs = false; + } + var abort = false; + if (value && exit_defs) value.walk(new TreeWalker(function(node) { + if (abort) return true; + if (node instanceof AST_SymbolRef && exit_defs.get(node.name) !== node.definition()) { + return abort = true; + } + })); + return !abort; + } + function can_drop_abort(ab) { if (ab instanceof AST_Exit) { - if (exit && exit.equivalent_to(ab)) { - if (!exit_defs) { - exit_defs = new Dictionary(); - exit.walk(new TreeWalker(function(node) { - if (node instanceof AST_SymbolRef) exit_defs.set(node.name, node.definition()); - })); - } - var abort = false; - ab.walk(new TreeWalker(function(node) { - if (abort) return true; - if (node instanceof AST_SymbolRef && exit_defs.get(node.name) !== node.definition()) { - return abort = true; - } - })); - if (!abort) return merge_exit = true; - } + if (match_return(ab)) return merge_exit = true; return in_lambda && ab instanceof AST_Return && is_undefined(ab.value); } if (!(ab instanceof AST_LoopControl)) return false; @@ -3640,6 +3654,15 @@ Compressor.prototype.compress = function(node) { return tail; } + function trim_return(value) { + if (!value) return; + if (exit.value) { + if (exit.value.TYPE == value.TYPE) return; + if (value instanceof AST_Sequence) return make_sequence(value, value.expressions.slice(0, -1)); + } + return value; + } + function as_statement_array_with_return(node, ab) { var body = as_statement_array(node); var block = body, last; @@ -3647,7 +3670,9 @@ Compressor.prototype.compress = function(node) { block = last.body; } block.pop(); - if (!merge_exit && ab.value) block.push(make_node(AST_SimpleStatement, ab.value, { body: ab.value })); + var value = ab.value; + if (merge_exit) value = trim_return(value); + if (value) block.push(make_node(AST_SimpleStatement, value, { body: value })); return body; } @@ -3683,6 +3708,31 @@ Compressor.prototype.compress = function(node) { if (var_defs.length > 0) args.push(make_node(AST_Var, stat, { definitions: var_defs })); return args; } + + function eliminate_returns(stat, in_block) { + if (stat instanceof AST_Exit) { + if (match_return(stat)) { + var value = trim_return(stat.value); + if (value) return make_node(AST_SimpleStatement, value, { body: value }); + return in_block ? null : make_node(AST_EmptyStatement, stat); + } + } else if (stat instanceof AST_If) { + stat.body = eliminate_returns(stat.body); + if (stat.alternative) stat.alternative = eliminate_returns(stat.alternative); + } 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.bcatch) eliminate_returns(stat.bcatch); + var trimmed = eliminate_returns(stat.body.pop(), true); + if (trimmed) stat.body.push(trimmed); + } + } else if (stat instanceof AST_Block && !(stat instanceof AST_Scope || stat instanceof AST_Switch)) { + var trimmed = eliminate_returns(stat.body.pop(), true); + if (trimmed) stat.body.push(trimmed); + } + return stat; + } } function eliminate_dead_code(statements, compressor) { @@ -10867,7 +10917,10 @@ Compressor.prototype.compress = function(node) { append_var(decls, expressions, symbol); if (value) expressions.push(value); } else { - if (!value && in_loop && argname === name) value = make_node(AST_Undefined, self); + if (!value && argname === name && (in_loop + || name.name == "arguments" && !is_arrow(fn) && is_arrow(scope))) { + value = make_node(AST_Undefined, self); + } append_var(decls, expressions, symbol, value); } } diff --git a/test/compress/arrows.js b/test/compress/arrows.js index b0c820b3..b8837fb7 100644 --- a/test/compress/arrows.js +++ b/test/compress/arrows.js @@ -1073,7 +1073,7 @@ issue_5414_2: { node_version: ">=4" } -issue_5416: { +issue_5416_1: { options = { dead_code: true, evaluate: true, @@ -1095,10 +1095,11 @@ issue_5416: { expect: { var f = () => { { - arguments = void 0; console; + arguments = void 0, console.log(arguments); var arguments; + return; } }; f(); @@ -1107,6 +1108,97 @@ issue_5416: { node_version: ">=4" } +issue_5416_2: { + options = { + dead_code: true, + evaluate: true, + inline: true, + loops: true, + unused: true, + } + input: { + var f = () => { + while ((() => { + console; + var a = function g(arguments) { + while (console.log(arguments)); + }(); + })()); + }; + f(); + } + expect: { + var f = () => { + { + console; + var arguments = void 0; + for (; console.log(arguments);); + return; + } + }; + f(); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +issue_5416_3: { + options = { + inline: true, + side_effects: true, + unused: true, + } + input: { + var f = () => { + (() => { + var a = function g(arguments) { + console.log(arguments); + }(); + })(); + }; + f(); + } + expect: { + var f = () => { + arguments = void 0, + console.log(arguments); + var arguments; + }; + f(); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +issue_5416_4: { + options = { + arrows: true, + inline: true, + side_effects: true, + unused: true, + } + input: { + var f = () => { + (() => { + var a = function g(arguments) { + while (console.log(arguments)); + }(); + })(); + }; + f(); + } + expect: { + var f = () => { + var arguments = void 0; + while (console.log(arguments)); + return; + }; + f(); + } + expect_stdout: "undefined" + node_version: ">=4" +} + issue_5495: { input: { console.log((() => { diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index d977f82f..89b790a1 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1731,7 +1731,7 @@ issue_3576: { expect_stdout: "PASS" } -issue_3668: { +issue_3668_1: { options = { conditionals: true, if_return: true, @@ -1748,6 +1748,38 @@ issue_3668: { } console.log(f()); } + expect: { + function f() { + try { + var undefined = typeof f; + if (!f) return undefined; + } catch (e) { + return "FAIL"; + } + } + console.log(f()); + } + expect_stdout: "undefined" +} + +issue_3668_2: { + options = { + conditionals: true, + if_return: true, + } + input: { + function f() { + try { + var undefined = typeof f; + if (!f) return undefined; + return; + } catch (e) { + return "FAIL"; + } + FAIL; + } + console.log(f()); + } expect: { function f() { try { @@ -1756,6 +1788,7 @@ issue_3668: { } catch (e) { return "FAIL"; } + FAIL; } console.log(f()); } diff --git a/test/compress/if_return.js b/test/compress/if_return.js index 7c15c927..e4e32f1c 100644 --- a/test/compress/if_return.js +++ b/test/compress/if_return.js @@ -1017,3 +1017,302 @@ issue_5523: { } expect_stdout: "undefined" } + +drop_catch: { + options = { + if_return: true, + } + input: { + function f() { + try { + throw 42; + } catch (e) { + return console.log("foo"), "bar"; + } finally { + console.log("baz"); + } + return "bar"; + } + console.log(f()); + } + expect: { + function f() { + try { + throw 42; + } catch (e) { + console.log("foo"); + } finally { + console.log("baz"); + } + return "bar"; + } + console.log(f()); + } + expect_stdout: [ + "foo", + "baz", + "bar", + ] +} + +retain_catch: { + options = { + if_return: true, + } + input: { + function f() { + try { + throw 42; + } catch (e) { + return console.log("foo"); + } finally { + console.log("bar"); + } + return console.log("foo"); + } + f(); + } + expect: { + function f() { + try { + throw 42; + } catch (e) { + return console.log("foo"); + } finally { + console.log("bar"); + } + return console.log("foo"); + } + f(); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +retain_finally: { + options = { + if_return: true, + } + input: { + function f() { + try { + return console.log("foo"), FAIL; + } catch (e) { + return console.log("bar"), "FAIL"; + } finally { + return console.log("baz"), console.log("moo"); + } + return console.log("moo"); + } + console.log(f()); + } + expect: { + function f() { + try { + return console.log("foo"), FAIL; + } catch (e) { + return console.log("bar"), "FAIL"; + } finally { + return console.log("baz"), console.log("moo"); + } + return console.log("moo"); + } + console.log(f()); + } + expect_stdout: [ + "foo", + "bar", + "baz", + "moo", + "undefined", + ] +} + +drop_try: { + options = { + if_return: true, + } + input: { + function f() { + try { + return console.log("foo"), "bar"; + } finally { + console.log("baz"); + } + return "bar"; + } + console.log(f()); + } + expect: { + function f() { + try { + console.log("foo"); + } finally { + console.log("baz"); + } + return "bar"; + } + console.log(f()); + } + expect_stdout: [ + "foo", + "baz", + "bar", + ] +} + +retain_try: { + options = { + if_return: true, + } + input: { + function f() { + try { + return console.log("foo"); + } finally { + console.log("bar"); + } + return console.log("foo"); + } + f(); + } + expect: { + function f() { + try { + return console.log("foo"); + } finally { + console.log("bar"); + } + return console.log("foo"); + } + f(); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +drop_try_catch: { + options = { + if_return: true, + } + input: { + function f(a) { + try { + if (a()) + return console.log("foo"), console.log("baz"); + } catch (e) { + return console.log("bar"), console.log("baz"); + } + return console.log("baz"); + } + f(function() { + return 42; + }); + f(function() {}); + f(); + } + expect: { + function f(a) { + try { + if (a()) + console.log("foo"); + } catch (e) { + console.log("bar"); + } + return console.log("baz"); + } + f(function() { + return 42; + }); + f(function() {}); + f(); + } + expect_stdout: [ + "foo", + "baz", + "baz", + "bar", + "baz", + ] +} + +empty_try: { + options = { + if_return: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(function() { + return f; + function f() { + try {} finally {} + return "PASS"; + } + }()()); + } + expect: { + console.log(function() { + return function() { + try {} finally {} + return "PASS"; + }; + }()()); + } + expect_stdout: "PASS" +} + +sequence_void_1: { + options = { + if_return: true, + } + input: { + function f() { + { + if (console) + return console, void console.log("PASS"); + return; + } + } + f(); + } + expect: { + function f() { + if (console) + return console, void console.log("PASS"); + } + f(); + } + expect_stdout: "PASS" +} + +sequence_void_2: { + options = { + if_return: true, + } + input: { + function f() { + { + if (console) + return console, void console.log("PASS"); + return; + } + FAIL; + } + f(); + } + expect: { + function f() { + if (console) + console, void console.log("PASS"); + return; + FAIL; + } + f(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/let.js b/test/compress/let.js index 83e69e85..b8181c4b 100644 --- a/test/compress/let.js +++ b/test/compress/let.js @@ -1659,7 +1659,7 @@ issue_4438: { function f() { if (console) { let a = console.log; - void a("PASS"); + a("PASS"); } } f(); diff --git a/test/compress/yields.js b/test/compress/yields.js index e4ca2bd8..aec847bd 100644 --- a/test/compress/yields.js +++ b/test/compress/yields.js @@ -1183,6 +1183,35 @@ issue_4641_2: { node_version: ">=10" } +issue_4641_3: { + options = { + if_return: true, + } + input: { + console.log(typeof async function*() { + try { + return void "FAIL"; + } finally { + console.log("PASS"); + } + }().next().then); + } + expect: { + console.log(typeof async function*() { + try { + return void "FAIL"; + } finally { + console.log("PASS"); + } + }().next().then); + } + expect_stdout: [ + "function", + "PASS", + ] + node_version: ">=10" +} + issue_4769_1: { options = { side_effects: true,