improve truthy compression (#3009)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 15 Mar 2018 22:12:59 +0000 (06:12 +0800)
committerGitHub <noreply@github.com>
Thu, 15 Mar 2018 22:12:59 +0000 (06:12 +0800)
lib/compress.js
test/compress/conditionals.js
test/compress/evaluate.js
test/compress/issue-1261.js
test/compress/negate-iife.js
test/compress/reduce_vars.js
test/compress/transform.js

index cacffd3..39220d6 100644 (file)
@@ -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;
index abb3969..03386d1 100644 (file)
@@ -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);
index 0547e6d..9d35ffa 100644 (file)
@@ -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 = {};
+        }
+    }
+}
index 9f4f466..888fde4 100644 (file)
@@ -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]",
     ]
index 66d2427..17148ca 100644 (file)
@@ -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
 }
index 815dff3..3d11ba4 100644 (file)
@@ -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: {
index 58874cd..867b8ad 100644 (file)
@@ -59,7 +59,7 @@ if_else_empty: {
         if ({} ? a : b); else {}
     }
     expect: {
-        !{} ? b : a;
+        ({}), a;
     }
 }