From 20ca0f5906c68df11d2f44e8b381212672afc6e1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 16 Mar 2018 06:12:59 +0800 Subject: [PATCH] improve truthy compression (#3009) --- lib/compress.js | 92 +++++++++++++++++++++++------------ test/compress/conditionals.js | 7 +-- test/compress/evaluate.js | 42 +++++++++++++++- test/compress/issue-1261.js | 2 +- test/compress/negate-iife.js | 4 +- test/compress/reduce_vars.js | 27 ++++++---- test/compress/transform.js | 2 +- 7 files changed, 127 insertions(+), 49 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index cacffd37..39220d68 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2041,6 +2041,28 @@ merge(Compressor.prototype, { && !node.expression.has_side_effects(compressor); } + // is_truthy() + // return true if `!!node === true` + (function(def) { + def(AST_Node, return_false); + def(AST_Array, return_true); + def(AST_Assign, function() { + return this.operator == "=" && this.right.is_truthy(); + }); + def(AST_Lambda, return_true); + def(AST_Object, return_true); + def(AST_RegExp, return_true); + def(AST_Sequence, function() { + return this.tail_node().is_truthy(); + }); + def(AST_SymbolRef, function() { + var fixed = this.fixed_value(); + return fixed && fixed.is_truthy(); + }); + })(function(node, func) { + node.DEFMETHOD("is_truthy", func); + }); + // may_throw_on_access() // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { @@ -3821,7 +3843,7 @@ merge(Compressor.prototype, { OPT(AST_Do, function(self, compressor){ if (!compressor.option("loops")) return self; - var cond = self.condition.tail_node().evaluate(compressor); + var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); if (!(cond instanceof AST_Node)) { if (cond) return make_node(AST_For, self, { body: make_node(AST_BlockStatement, self.body, { @@ -3943,9 +3965,11 @@ merge(Compressor.prototype, { self.condition = best_of_expression(self.condition.transform(compressor), orig); } } - if (compressor.option("dead_code")) { - if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); - if (!cond) { + if (cond instanceof AST_Node) { + cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + } + if (!cond) { + if (compressor.option("dead_code")) { var body = []; extract_declarations_from_unreachable_code(compressor, self.body, body); if (self.init instanceof AST_Statement) { @@ -3960,6 +3984,16 @@ merge(Compressor.prototype, { })); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } + } else if (self.condition && !(cond instanceof AST_Node)) { + self.body = make_node(AST_BlockStatement, self.body, { + body: [ + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }), + self.body + ] + }); + self.condition = null; } } return if_break_in_loop(self, compressor); @@ -3980,7 +4014,9 @@ merge(Compressor.prototype, { self.condition = best_of_expression(self.condition.transform(compressor), orig); } if (compressor.option("dead_code")) { - if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); + if (cond instanceof AST_Node) { + cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + } if (!cond) { compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); var body = []; @@ -4864,8 +4900,10 @@ merge(Compressor.prototype, { return make_node(AST_Undefined, self).optimize(compressor); } } - if (compressor.in_boolean_context()) { - switch (self.operator) { + if (compressor.option("booleans")) { + if (self.operator == "!" && e.is_truthy()) { + return make_sequence(self, [ e, make_node(AST_False, self) ]).optimize(compressor); + } else if (compressor.in_boolean_context()) switch (self.operator) { case "!": if (e instanceof AST_UnaryPrefix && e.operator == "!") { // !!foo ==> foo, if we're in boolean context @@ -5106,7 +5144,7 @@ merge(Compressor.prototype, { if (compressor.option("evaluate")) { switch (self.operator) { case "&&": - var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor); + var ll = fuzzy_eval(self.left); if (!ll) { compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); @@ -5141,7 +5179,7 @@ merge(Compressor.prototype, { } break; case "||": - var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor); + var ll = fuzzy_eval(self.left); if (!ll) { compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, self.right ]).optimize(compressor); @@ -5379,6 +5417,13 @@ merge(Compressor.prototype, { return best_of(compressor, ev, self); } return self; + + function fuzzy_eval(node) { + if (node.truthy) return true; + if (node.falsy) return false; + if (node.is_truthy()) return true; + return node.evaluate(compressor); + } }); function recursive_ref(compressor, def) { @@ -5674,15 +5719,13 @@ merge(Compressor.prototype, { expressions.push(self); return make_sequence(self, expressions); } - var cond = self.condition.evaluate(compressor); - if (cond !== self.condition) { - if (cond) { - compressor.warn("Condition always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent); - } else { - compressor.warn("Condition always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative); - } + var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + if (!cond) { + compressor.warn("Condition always false [{file}:{line},{col}]", self.start); + return make_sequence(self, [ self.condition, self.alternative ]).optimize(compressor); + } else if (!(cond instanceof AST_Node)) { + compressor.warn("Condition always true [{file}:{line},{col}]", self.start); + return make_sequence(self, [ self.condition, self.consequent ]).optimize(compressor); } var negated = cond.negate(compressor, first_in_statement(compressor)); if (best_of(compressor, cond, negated) === negated) { @@ -6122,19 +6165,6 @@ merge(Compressor.prototype, { return self; }); - function literals_in_boolean_context(self, compressor) { - if (compressor.in_boolean_context()) { - return best_of(compressor, self, make_sequence(self, [ - self, - make_node(AST_True, self) - ]).optimize(compressor)); - } - return self; - }; - OPT(AST_Array, literals_in_boolean_context); - OPT(AST_Object, literals_in_boolean_context); - OPT(AST_RegExp, literals_in_boolean_context); - OPT(AST_Return, function(self, compressor){ if (self.value && is_undefined(self.value, compressor)) { self.value = null; diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index abb39697..03386d18 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -703,10 +703,11 @@ ternary_boolean_alternative: { trivial_boolean_ternary_expressions : { options = { + booleans: true, conditionals: true, - evaluate : true, - booleans : true - }; + evaluate: true, + side_effects: true, + } input: { f('foo' in m ? true : false); f('foo' in m ? false : true); diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 0547e6d9..9d35ffa3 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -745,7 +745,7 @@ in_boolean_context: { !b("foo"), !b([1, 2]), !b(/foo/), - ![1, foo()], + (foo(), !1), (foo(), !1) ); } @@ -1566,3 +1566,43 @@ issue_2968: { } expect_stdout: "PASS" } + +truthy_conditionals: { + options = { + conditionals: true, + evaluate: true, + } + input: { + if (a = {}) x(); + (b = /foo/) && y(); + (c = function() {}) || z(); + } + expect: { + a = {}, x(); + b = /foo/, y(); + c = function() {}; + } +} + +truthy_loops: { + options = { + evaluate: true, + loops: true, + } + input: { + while ([]) x(); + do { + y(); + } while(a = {}); + } + expect: { + for (;;) { + []; + x(); + } + for (;;) { + y(); + a = {}; + } + } +} diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index 9f4f466f..888fde40 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -175,8 +175,8 @@ should_warn: { "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:141,31]", "WARN: Condition always true [test/compress/issue-1261.js:141,8]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:142,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:143,24]", "WARN: Condition always true [test/compress/issue-1261.js:143,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:143,24]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:144,31]", "WARN: Condition always false [test/compress/issue-1261.js:144,8]", ] diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 66d24270..17148cad 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -67,7 +67,7 @@ negate_iife_3_evaluate: { (function(){ return true })() ? console.log(true) : console.log(false); } expect: { - console.log(true); + true, console.log(true); } expect_stdout: true } @@ -110,7 +110,7 @@ negate_iife_3_off_evaluate: { (function(){ return true })() ? console.log(true) : console.log(false); } expect: { - console.log(true); + true, console.log(true); } expect_stdout: true } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 815dff32..3d11ba42 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -55,7 +55,7 @@ reduce_vars: { console.log(a - 5); eval("console.log(a);"); })(eval); - "yes"; + true, "yes"; console.log(A + 1); } expect_stdout: true @@ -147,7 +147,7 @@ modified: { } function f4() { var b = 2, c = 3; - b = c; + 1, b = c; console.log(1 + b); console.log(b + c); console.log(1 + c); @@ -715,10 +715,12 @@ passes: { passes: 2, reduce_funcs: true, reduce_vars: true, + sequences: true, + side_effects: true, unused: true, } input: { - function f() { + (function() { var a = 1, b = 2, c = 3; if (a) { b = c; @@ -729,17 +731,22 @@ passes: { console.log(b + c); console.log(a + c); console.log(a + b + c); - } + })(); } expect: { - function f() { - 3; - console.log(4); - console.log(6); - console.log(4); + (function() { + console.log(4), + console.log(6), + console.log(4), console.log(7); - } + })(); } + expect_stdout: [ + "4", + "6", + "4", + "7", + ] } iife: { diff --git a/test/compress/transform.js b/test/compress/transform.js index 58874cd6..867b8ade 100644 --- a/test/compress/transform.js +++ b/test/compress/transform.js @@ -59,7 +59,7 @@ if_else_empty: { if ({} ? a : b); else {} } expect: { - !{} ? b : a; + ({}), a; } } -- 2.34.1