From dd3b81dec669dd316cfbd7b429d92d49a1d03474 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 21 Feb 2022 03:02:19 +0000 Subject: [PATCH] enhance `booleans` (#5365) --- lib/ast.js | 53 +++--- lib/compress.js | 331 ++++++++++++++++++++-------------- test/compress/booleans.js | 17 ++ test/compress/destructured.js | 6 +- test/compress/functions.js | 2 +- test/compress/join_vars.js | 45 ++++- 6 files changed, 287 insertions(+), 167 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 70acb69d1d..8ba095d51f 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -2085,33 +2085,40 @@ TreeWalker.prototype = { } }, in_boolean_context: function() { - var self = this.self(); - for (var i = 0, p; p = this.parent(i); i++) { - if (p instanceof AST_Conditional && p.condition === self - || p instanceof AST_DWLoop && p.condition === self - || p instanceof AST_For && p.condition === self - || p instanceof AST_If && p.condition === self - || p instanceof AST_Return && p.in_bool - || p instanceof AST_Sequence && p.tail_node() !== self - || p instanceof AST_SimpleStatement - || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) { - return true; + for (var drop = true, level = 0, parent, self = this.self(); parent = this.parent(level++); self = parent) { + if (parent instanceof AST_Binary) switch (parent.operator) { + case "&&": + case "||": + if (parent.left === self) drop = false; + continue; + default: + return false; + } + if (parent instanceof AST_Conditional) { + if (parent.condition === self) return true; + continue; } - if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||") - || p instanceof AST_Conditional - || p.tail_node() === self) { - self = p; - } else if (p instanceof AST_Return) { - for (var call, fn = p; call = this.parent(++i); fn = call) { - if (call.TYPE == "Call") { - if (!(fn instanceof AST_Lambda) || fn.name) return false; - } else if (fn instanceof AST_Lambda) { - return false; + if (parent instanceof AST_DWLoop) return parent.condition === self; + if (parent instanceof AST_For) return parent.condition === self; + if (parent instanceof AST_If) return parent.condition === self; + if (parent instanceof AST_Return) { + if (parent.in_bool) return true; + while (parent = this.parent(level++)) { + if (parent instanceof AST_Lambda) { + if (parent.name) return false; + parent = this.parent(level++); + if (parent.TYPE != "Call") return false; + break; } } - } else { - return false; } + if (parent instanceof AST_Sequence) { + if (parent.tail_node() === self) continue; + return drop ? "d" : true; + } + if (parent instanceof AST_SimpleStatement) return drop ? "d" : true; + if (parent instanceof AST_UnaryPrefix) return parent.operator == "!"; + return false; } } }; diff --git a/lib/compress.js b/lib/compress.js index 6bab55792a..c9ca15ebf5 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -189,7 +189,7 @@ Compressor.prototype = new TreeTransformer(function(node, descend, in_list) { if (is_scope) { node.hoist_properties(this); node.hoist_declarations(this); - node.process_boolean_returns(this); + node.process_returns(this); } // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize() // would call AST_Node.transform() if a different instance of AST_Node is @@ -475,7 +475,8 @@ Compressor.prototype.compress = function(node) { function reset_def(tw, compressor, def) { def.assignments = 0; - def.bool_fn = 0; + def.bool_return = 0; + def.drop_return = 0; def.cross_loop = false; def.direct_access = false; def.escaped = []; @@ -1057,12 +1058,11 @@ Compressor.prototype.compress = function(node) { if (iife) delete exp.reduce_vars; return true; } - if (node.TYPE == "Call" && tw.in_boolean_context()) { - if (exp instanceof AST_SymbolRef) { - exp.definition().bool_fn++; - } else if (exp instanceof AST_Assign && exp.operator == "=" && exp.left instanceof AST_SymbolRef) { - exp.left.definition().bool_fn++; - } + if (node.TYPE == "Call") switch (tw.in_boolean_context()) { + case "d": + var drop = true; + case true: + mark_refs(exp, drop); } exp.walk(tw); var optional = node.optional; @@ -1078,6 +1078,25 @@ Compressor.prototype.compress = function(node) { tw.find_parent(AST_Scope).may_call_this(); } return true; + + function mark_refs(node, drop) { + if (node instanceof AST_Assign) { + if (node.operator != "=") return; + mark_refs(node.left, drop); + mark_refs(node.right, drop); + } else if (node instanceof AST_Binary) { + if (!lazy_op[node.operator]) return; + mark_refs(node.left, drop); + mark_refs(node.right, drop); + } else if (node instanceof AST_Conditional) { + mark_refs(node.consequent, drop); + mark_refs(node.alternative, drop); + } else if (node instanceof AST_SymbolRef) { + var def = node.definition(); + def.bool_return++; + if (drop) def.drop_return++; + } + } }); def(AST_Class, function(tw, descend, compressor) { var node = this; @@ -7923,7 +7942,7 @@ Compressor.prototype.compress = function(node) { })); } - function map_bool_returns(fn) { + function map_self_returns(fn) { var map = Object.create(null); scan_local_returns(fn, function(node) { var value = node.value; @@ -7936,9 +7955,14 @@ Compressor.prototype.compress = function(node) { return map; } - function all_bool(def, bool_returns, compressor) { - return def.bool_fn + (bool_returns[def.id] || 0) === def.references.length - def.replaced - && !compressor.exposed(def); + function can_trim_returns(def, self_returns, compressor) { + if (compressor.exposed(def)) return false; + switch (def.references.length - def.replaced - (self_returns[def.id] || 0)) { + case def.drop_return: + return "d"; + case def.bool_return: + return true; + } } function process_boolean_returns(fn, compressor) { @@ -7964,39 +7988,74 @@ Compressor.prototype.compress = function(node) { }); } - AST_Scope.DEFMETHOD("process_boolean_returns", noop); - AST_Defun.DEFMETHOD("process_boolean_returns", function(compressor) { + AST_Scope.DEFMETHOD("process_returns", noop); + AST_Defun.DEFMETHOD("process_returns", function(compressor) { if (!compressor.option("booleans")) return; - var bool_returns = map_bool_returns(this); - if (!all_bool(this.name.definition(), bool_returns, compressor)) return; if (compressor.parent() instanceof AST_ExportDefault) return; - process_boolean_returns(this, compressor); + switch (can_trim_returns(this.name.definition(), map_self_returns(this), compressor)) { + case "d": + drop_returns(compressor, this, true); + break; + case true: + process_boolean_returns(this, compressor); + break; + } }); - AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) { + AST_Function.DEFMETHOD("process_returns", function(compressor) { if (!compressor.option("booleans")) return; - var bool_returns = map_bool_returns(this); - if (this.name && !all_bool(this.name.definition(), bool_returns, compressor)) return; + var drop = true; + var self_returns = map_self_returns(this); + if (this.name && !can_trim(this.name.definition())) return; var parent = compressor.parent(); if (parent instanceof AST_Assign) { if (parent.operator != "=") return; var sym = parent.left; if (!(sym instanceof AST_SymbolRef)) return; - if (!all_bool(sym.definition(), bool_returns, compressor)) return; + if (!can_trim(sym.definition())) return; } else if (parent instanceof AST_Call && parent.expression !== this) { var exp = parent.expression; if (exp instanceof AST_SymbolRef) exp = exp.fixed_value(); if (!(exp instanceof AST_Lambda)) return; if (exp.uses_arguments || exp.pinned()) return; - var sym = exp.argnames[parent.args.indexOf(this)]; + var args = parent.args, sym; + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (arg === this) { + sym = exp.argnames[i]; + if (!sym && exp.rest) return; + break; + } + if (arg instanceof AST_Spread) return; + } if (sym instanceof AST_DefaultValue) sym = sym.name; - if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return; + if (sym instanceof AST_SymbolFunarg && !can_trim(sym.definition())) return; } else if (parent.TYPE == "Call") { compressor.pop(); var in_bool = compressor.in_boolean_context(); compressor.push(this); - if (!in_bool) return; + switch (in_bool) { + case true: + drop = false; + case "d": + break; + default: + return; + } } else return; - process_boolean_returns(this, compressor); + if (drop) { + drop_returns(compressor, this, true); + } else { + process_boolean_returns(this, compressor); + } + + function can_trim(def) { + switch (can_trim_returns(def, self_returns, compressor)) { + case true: + drop = false; + case "d": + return true; + } + } }); AST_BlockScope.DEFMETHOD("var_names", function() { @@ -8155,6 +8214,118 @@ Compressor.prototype.compress = function(node) { }); } + function drop_returns(compressor, exp, ignore_name) { + if (!(exp instanceof AST_Lambda)) return; + var arrow = is_arrow(exp); + var async = is_async(exp); + var changed = false; + var drop_body = false; + if (arrow && compressor.option("arrows")) { + if (!exp.value) { + drop_body = true; + } else if (!async || is_primitive(compressor, exp.value)) { + var dropped = exp.value.drop_side_effect_free(compressor); + if (dropped !== exp.value) { + changed = true; + exp.value = dropped; + } + } + } else if (!is_generator(exp)) { + if (!ignore_name && exp.name) { + var def = exp.name.definition(); + drop_body = def.references.length == def.replaced; + } else { + drop_body = true; + } + } + if (drop_body) { + exp.process_expression(false, function(node) { + var value = node.value; + if (value) { + if (async && !is_primitive(compressor, value)) return node; + value = value.drop_side_effect_free(compressor, true); + } + changed = true; + if (!value) return make_node(AST_EmptyStatement, node); + return make_node(AST_SimpleStatement, node, { body: value }); + }); + scan_local_returns(exp, function(node) { + var value = node.value; + if (value) { + if (async && !is_primitive(compressor, value)) return; + var dropped = value.drop_side_effect_free(compressor); + if (dropped !== value) { + changed = true; + node.value = dropped; + } + } + }); + } + if (async && compressor.option("awaits")) { + if (drop_body) exp.process_expression("awaits", function(node) { + var body = node.body; + if (body instanceof AST_Await) { + if (is_primitive(compressor, body.expression)) { + changed = true; + body = body.expression.drop_side_effect_free(compressor, true); + if (!body) return make_node(AST_EmptyStatement, node); + node.body = body; + } + } else if (body instanceof AST_Sequence) { + var exprs = body.expressions; + for (var i = exprs.length; --i >= 0;) { + var tail = exprs[i]; + if (!(tail instanceof AST_Await)) break; + var value = tail.expression; + if (!is_primitive(compressor, value)) break; + changed = true; + if (exprs[i] = value.drop_side_effect_free(compressor)) break; + } + switch (i) { + case -1: + return make_node(AST_EmptyStatement, node); + case 0: + node.body = exprs[0]; + break; + default: + exprs.length = i + 1; + break; + } + } + return node; + }); + var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value); + var tw = new TreeWalker(function(node) { + if (abort) return true; + if (tw.parent() === exp && node.may_throw(compressor)) return abort = true; + if (node instanceof AST_Await) return abort = true; + if (node instanceof AST_ForAwaitOf) return abort = true; + if (node instanceof AST_Return) { + if (node.value && !is_primitive(compressor, node.value)) return abort = true; + return; + } + if (node instanceof AST_Scope && node !== exp) return true; + }); + exp.walk(tw); + if (!abort) { + var ctor; + switch (exp.CTOR) { + case AST_AsyncArrow: + ctor = AST_Arrow; + break; + case AST_AsyncFunction: + ctor = AST_Function; + break; + case AST_AsyncGeneratorFunction: + ctor = AST_GeneratorFunction; + break; + } + return make_node(ctor, exp, exp); + } + } + return changed && exp.clone(); + } + // drop_side_effect_free() // remove side-effect-free parts which only affects return value (function(def) { @@ -8261,116 +8432,6 @@ Compressor.prototype.compress = function(node) { if (!rhs) return lhs; return make_sequence(this, [ lhs, rhs ]); }); - function drop_returns(compressor, exp) { - var arrow = is_arrow(exp); - var async = is_async(exp); - var changed = false; - var drop_body = false; - if (arrow && compressor.option("arrows")) { - if (!exp.value) { - drop_body = true; - } else if (!async || is_primitive(compressor, exp.value)) { - var dropped = exp.value.drop_side_effect_free(compressor); - if (dropped !== exp.value) { - changed = true; - exp.value = dropped; - } - } - } else if (exp instanceof AST_AsyncFunction || exp instanceof AST_Function) { - if (exp.name) { - var def = exp.name.definition(); - drop_body = def.references.length == def.replaced; - } else { - drop_body = true; - } - } - if (drop_body) { - exp.process_expression(false, function(node) { - var value = node.value; - if (value) { - if (async && !is_primitive(compressor, value)) return node; - value = value.drop_side_effect_free(compressor, true); - } - changed = true; - if (!value) return make_node(AST_EmptyStatement, node); - return make_node(AST_SimpleStatement, node, { body: value }); - }); - scan_local_returns(exp, function(node) { - var value = node.value; - if (value) { - if (async && !is_primitive(compressor, value)) return; - var dropped = value.drop_side_effect_free(compressor); - if (dropped !== value) { - changed = true; - node.value = dropped; - } - } - }); - } - if (async && compressor.option("awaits")) { - if (drop_body) exp.process_expression("awaits", function(node) { - var body = node.body; - if (body instanceof AST_Await) { - if (is_primitive(compressor, body.expression)) { - changed = true; - body = body.expression.drop_side_effect_free(compressor, true); - if (!body) return make_node(AST_EmptyStatement, node); - node.body = body; - } - } else if (body instanceof AST_Sequence) { - var exprs = body.expressions; - for (var i = exprs.length; --i >= 0;) { - var tail = exprs[i]; - if (!(tail instanceof AST_Await)) break; - var value = tail.expression; - if (!is_primitive(compressor, value)) break; - changed = true; - if (exprs[i] = value.drop_side_effect_free(compressor)) break; - } - switch (i) { - case -1: - return make_node(AST_EmptyStatement, node); - case 0: - node.body = exprs[0]; - break; - default: - exprs.length = i + 1; - break; - } - } - return node; - }); - var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value); - var tw = new TreeWalker(function(node) { - if (abort) return true; - if (tw.parent() === exp && node.may_throw(compressor)) return abort = true; - if (node instanceof AST_Await) return abort = true; - if (node instanceof AST_ForAwaitOf) return abort = true; - if (node instanceof AST_Return) { - if (node.value && !is_primitive(compressor, node.value)) return abort = true; - return; - } - if (node instanceof AST_Scope && node !== exp) return true; - }); - exp.walk(tw); - if (!abort) { - var ctor; - switch (exp.CTOR) { - case AST_AsyncArrow: - ctor = AST_Arrow; - break; - case AST_AsyncFunction: - ctor = AST_Function; - break; - case AST_AsyncGeneratorFunction: - ctor = AST_GeneratorFunction; - break; - } - return make_node(ctor, exp, exp); - } - } - return changed && exp.clone(); - } function assign_this_only(fn, compressor) { fn.new = true; var result = all(fn.body, function(stat) { diff --git a/test/compress/booleans.js b/test/compress/booleans.js index a27eed08c8..d6b93fe525 100644 --- a/test/compress/booleans.js +++ b/test/compress/booleans.js @@ -448,6 +448,23 @@ concat_truthy: { ] } +process_returns: { + options = { + booleans: true, + } + input: { + (function() { + return 42; + })() && console.log("PASS"); + } + expect: { + (function() { + return 42; + })() && console.log("PASS"); + } + expect_stdout: "PASS" +} + issue_3465_1: { options = { booleans: true, diff --git a/test/compress/destructured.js b/test/compress/destructured.js index 5c0e9654e1..86a112cf04 100644 --- a/test/compress/destructured.js +++ b/test/compress/destructured.js @@ -692,7 +692,7 @@ funarg_inline: { node_version: ">=6" } -process_boolean_returns: { +process_returns: { options = { booleans: true, } @@ -706,9 +706,7 @@ process_boolean_returns: { expect: { console.log(function({ length }) { return length ? "FAIL" : "PASS"; - }(function() { - return 42; - })); + }(function() {})); } expect_stdout: "PASS" node_version: ">=6" diff --git a/test/compress/functions.js b/test/compress/functions.js index 3cbb2a3c12..75131fcdcb 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -6341,7 +6341,7 @@ issue_4612_4: { expect: { console.log(function() { function f() { - return h(); + h(); } function g() { return h(); diff --git a/test/compress/join_vars.js b/test/compress/join_vars.js index 243517f1d2..d9be3d0aee 100644 --- a/test/compress/join_vars.js +++ b/test/compress/join_vars.js @@ -1144,7 +1144,7 @@ conditional_assignments_3: { expect_stdout: "PASS" } -issue_3856: { +issue_3856_1: { options = { booleans: true, conditionals: true, @@ -1169,9 +1169,46 @@ issue_3856: { console.log(function() { (function() { var a, b; - if (a) return a, 1; - for (a = 0; !console;); - return 0; + if (a) a; + else { + a = 0; + for (; !console;); + } + })(); + }()); + } + expect_stdout: "undefined" +} + +issue_3856_2: { + options = { + booleans: true, + conditionals: true, + if_return: true, + join_vars: true, + passes: 2, + sequences: true, + side_effects: true, + } + input: { + console.log(function() { + (function() { + var a; + if (!a) { + a = 0; + for (var b; !console;); + return 0; + } + if (a) return 1; + })(); + }()); + } + expect: { + console.log(function() { + (function() { + var a, b; + if (!a) + for (a = 0; !console;); })(); }()); }