enhance `pure_getters`, `reduce_vars` & `unused` (#4863)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 23 Apr 2021 23:17:30 +0000 (00:17 +0100)
committerGitHub <noreply@github.com>
Fri, 23 Apr 2021 23:17:30 +0000 (07:17 +0800)
lib/compress.js
test/compress/classes.js
test/compress/drop-unused.js
test/compress/functions.js
test/compress/pure_getters.js
test/ufuzz/index.js

index fea4459..f0e7ab8 100644 (file)
@@ -814,11 +814,12 @@ merge(Compressor.prototype, {
         def(AST_Assign, function(tw, descend, compressor) {
             var node = this;
             var left = node.left;
+            var right = node.right;
             var scan = left instanceof AST_Destructured || left instanceof AST_SymbolRef;
             switch (node.operator) {
               case "=":
-                if (left.equivalent_to(node.right) && !left.has_side_effects(compressor)) {
-                    node.right.walk(tw);
+                if (left.equivalent_to(right) && !left.has_side_effects(compressor)) {
+                    right.walk(tw);
                     walk_prop(left);
                     node.__drop = true;
                     return true;
@@ -838,7 +839,7 @@ merge(Compressor.prototype, {
                     walk_assign();
                 } else {
                     mark_assignment_to_arguments(left);
-                    node.right.walk(tw);
+                    right.walk(tw);
                 }
                 pop(tw);
                 return true;
@@ -855,7 +856,7 @@ merge(Compressor.prototype, {
                     return;
                 }
                 var safe = safe_to_read(tw, d);
-                node.right.walk(tw);
+                right.walk(tw);
                 if (safe && !left.in_arg && safe_to_assign(tw, d)) {
                     push_ref(d, left);
                     mark(tw, d);
@@ -864,7 +865,7 @@ merge(Compressor.prototype, {
                         return make_node(AST_Binary, node, {
                             operator: node.operator.slice(0, -1),
                             left: make_ref(left, fixed),
-                            right: node.right
+                            right: node.right,
                         });
                     };
                     left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
@@ -899,7 +900,7 @@ merge(Compressor.prototype, {
             }
 
             function walk_assign() {
-                node.right.walk(tw);
+                right.walk(tw);
                 scan_declaration(tw, compressor, left, function() {
                     return node.right;
                 }, function(sym, fixed, walk) {
@@ -911,14 +912,14 @@ merge(Compressor.prototype, {
                     var d = sym.definition();
                     d.assignments++;
                     if (fixed
-                        && !is_modified(compressor, tw, node, node.right, 0)
+                        && !is_modified(compressor, tw, node, right, 0, is_immutable(right), recursive_ref(tw, d))
                         && !sym.in_arg
                         && safe_to_assign(tw, d)) {
                         push_ref(d, sym);
                         mark(tw, d);
                         if (d.single_use && left instanceof AST_Destructured) d.single_use = false;
                         tw.loop_ids[d.id] = tw.in_loop;
-                        mark_escaped(tw, d, sym.scope, node, node.right, 0, 1);
+                        mark_escaped(tw, d, sym.scope, node, right, 0, 1);
                         sym.fixed = d.fixed = fixed;
                         sym.fixed.assigns = [ node ];
                     } else {
@@ -3656,11 +3657,11 @@ merge(Compressor.prototype, {
     // may_throw_on_access()
     // returns true if this node may be null, undefined or contain `AST_Accessor`
     (function(def) {
-        AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
-            return !compressor.option("pure_getters") || this._dot_throw(compressor);
+        AST_Node.DEFMETHOD("may_throw_on_access", function(compressor, force) {
+            return !compressor.option("pure_getters") || this._dot_throw(compressor, force);
         });
-        function is_strict(compressor) {
-            return /strict/.test(compressor.option("pure_getters"));
+        function is_strict(compressor, force) {
+            return force || /strict/.test(compressor.option("pure_getters"));
         }
         def(AST_Node, is_strict);
         def(AST_Array, return_false);
@@ -3683,28 +3684,28 @@ merge(Compressor.prototype, {
             return this.consequent._dot_throw(compressor) || this.alternative._dot_throw(compressor);
         });
         def(AST_Constant, return_false);
-        def(AST_Dot, function(compressor) {
-            if (!is_strict(compressor)) return false;
+        def(AST_Dot, function(compressor, force) {
+            if (!is_strict(compressor, force)) return false;
             var exp = this.expression;
             if (exp instanceof AST_SymbolRef) exp = exp.fixed_value();
             return !(this.property == "prototype" && is_lambda(exp));
         });
         def(AST_Lambda, return_false);
         def(AST_Null, return_true);
-        def(AST_Object, function(compressor) {
-            return is_strict(compressor) && !all(this.properties, function(prop) {
+        def(AST_Object, function(compressor, force) {
+            return is_strict(compressor, force) && !all(this.properties, function(prop) {
                 return prop instanceof AST_ObjectKeyVal;
             });
         });
-        def(AST_ObjectIdentity, function(compressor) {
-            return is_strict(compressor) && !this.scope.new;
+        def(AST_ObjectIdentity, function(compressor, force) {
+            return is_strict(compressor, force) && !this.scope.new;
         });
         def(AST_Sequence, function(compressor) {
             return this.tail_node()._dot_throw(compressor);
         });
-        def(AST_SymbolRef, function(compressor) {
+        def(AST_SymbolRef, function(compressor, force) {
             if (this.is_undefined) return true;
-            if (!is_strict(compressor)) return false;
+            if (!is_strict(compressor, force)) return false;
             if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
             if (this.is_immutable()) return false;
             var def = this.definition();
@@ -5817,11 +5818,10 @@ merge(Compressor.prototype, {
         var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
             var sym, nested = false;
             if (node instanceof AST_Assign) {
-                if (node.write_only || node.operator == "=") sym = node.left;
+                if (node.write_only || node.operator == "=") sym = extract_reference(node.left, props);
             } else if (node instanceof AST_Unary) {
-                if (node.write_only) sym = node.expression;
+                if (node.write_only) sym = extract_reference(node.expression, props);
             }
-            if (/strict/.test(compressor.option("pure_getters"))) sym = extract_reference(sym, props);
             if (!(sym instanceof AST_SymbolRef)) return;
             var def = sym.definition();
             if (export_defaults[def.id]) return;
@@ -5832,15 +5832,17 @@ merge(Compressor.prototype, {
             function extract_reference(node, props) {
                 if (node instanceof AST_PropAccess) {
                     var expr = node.expression;
-                    if (!expr.may_throw_on_access(compressor)) {
+                    if (!expr.may_throw_on_access(compressor, true)) {
                         nested = true;
                         if (props && node instanceof AST_Sub) props.unshift(node.property);
                         return extract_reference(expr, props);
                     }
                 } else if (node instanceof AST_Assign && node.operator == "=") {
+                    node.write_only = "p";
                     var ref = extract_reference(node.right);
-                    if (props) props.assign = node;
-                    return ref;
+                    if (!props) return ref;
+                    props.assign = node;
+                    return ref instanceof AST_SymbolRef ? ref : node.left;
                 }
                 return node;
             }
@@ -6125,6 +6127,10 @@ merge(Compressor.prototype, {
                         value = make_node(AST_Number, node, { value: 0 });
                     }
                     if (value) {
+                        if (props.assign) {
+                            var assign = props.assign.drop_side_effect_free(compressor);
+                            if (assign) props.unshift(assign);
+                        }
                         if (parent instanceof AST_Sequence && parent.tail_node() !== node) {
                             value = value.drop_side_effect_free(compressor);
                         }
@@ -6662,16 +6668,17 @@ merge(Compressor.prototype, {
                         }
                     }, true);
                 }))) {
-                if (props.assign) {
-                    props.assign.write_only = true;
-                    props.assign.walk(tw);
-                    delete props.assign.write_only;
+                if (node.write_only === "p" && node.right.may_throw_on_access(compressor, true)) return;
+                var assign = props.assign;
+                if (assign) {
+                    assign.write_only = true;
+                    assign.walk(tw);
+                    assign.write_only = "p";
                 }
                 props.forEach(function(prop) {
                     prop.walk(tw);
                 });
                 if (node instanceof AST_Assign) {
-                    if (node.write_only === "p" && node.right.may_throw_on_access(compressor)) return;
                     var right = get_rhs(node);
                     if (init && node.write_only === true && node_def.scope === self && !right.has_side_effects(compressor)) {
                         initializations.add(node_def.id, right);
@@ -7312,11 +7319,8 @@ merge(Compressor.prototype, {
         def(AST_Assign, function(compressor) {
             var left = this.left;
             if (left instanceof AST_PropAccess) {
-                var expr = left.expression;
-                if (expr instanceof AST_Assign && expr.operator == "=" && !expr.may_throw_on_access(compressor)) {
-                    expr.write_only = "p";
-                }
-                if (compressor.has_directive("use strict") && expr.is_constant()) return this;
+                if (left.expression.may_throw_on_access(compressor, true)) return this;
+                if (compressor.has_directive("use strict") && left.expression.is_constant()) return this;
             }
             if (left.has_side_effects(compressor)) return this;
             var right = this.right;
index e8bff75..e201bd3 100644 (file)
@@ -1546,6 +1546,8 @@ drop_unused_self_reference: {
     options = {
         pure_getters: "strict",
         reduce_vars: true,
+        sequences: true,
+        side_effects: true,
         toplevel: true,
         unused: true,
     }
index 4ade086..6c4ca29 100644 (file)
@@ -2371,6 +2371,7 @@ function_parameter_ie8: {
 issue_3664: {
     options = {
         pure_getters: "strict",
+        side_effects: true,
         unused: true,
     }
     input: {
@@ -2381,7 +2382,7 @@ issue_3664: {
     }
     expect: {
         console.log(function() {
-            var b = ([ b && console.log("FAIL") ].p = 0, 0);
+            var b = (b && console.log("FAIL"), 0, 0);
             return "PASS";
         }());
     }
@@ -2391,6 +2392,7 @@ issue_3664: {
 issue_3673: {
     options = {
         pure_getters: "strict",
+        sequences: true,
         side_effects: true,
         toplevel: true,
         unused: true,
@@ -2401,8 +2403,6 @@ issue_3673: {
         console.log("PASS");
     }
     expect: {
-        var a;
-        (a = [ a ]).p = 42;
         console.log("PASS");
     }
     expect_stdout: "PASS"
index 3d19f49..a9a3b8f 100644 (file)
@@ -6026,6 +6026,8 @@ drop_unused_self_reference: {
     options = {
         pure_getters: "strict",
         reduce_vars: true,
+        sequences: true,
+        side_effects: true,
         toplevel: true,
         unused: true,
     }
index d1dd07e..2a20c33 100644 (file)
@@ -1564,3 +1564,77 @@ issue_4803: {
     }
     expect_stdout: "PASS"
 }
+
+nested_property_assignments_1: {
+    options = {
+        pure_getters: "strict",
+        reduce_vars: true,
+        sequences: true,
+        side_effects: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var f;
+        ((f = function() {
+            console.log("FAIL");
+        }).p = f).q = console.log("PASS");
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}
+
+nested_property_assignments_2: {
+    options = {
+        pure_getters: "strict",
+        unused: true,
+    }
+    input: {
+        var o = {};
+        (function() {
+            var a;
+            (o.p = a = {}).q = "PASS";
+        })();
+        console.log(o.p.q);
+    }
+    expect: {
+        var o = {};
+        (function() {
+            (o.p = {}).q = "PASS";
+        })();
+        console.log(o.p.q);
+    }
+    expect_stdout: "PASS"
+}
+
+nested_property_assignments_3: {
+    options = {
+        collapse_vars: true,
+        pure_getters: true,
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        var o = { p: {} };
+        (function(a) {
+            console && a;
+            if (console) {
+                a = a.p;
+                a.q = a;
+            }
+        })(o);
+        console.log(o.p.q === o.p ? "PASS" : "FAIL");
+    }
+    expect: {
+        var o = { p: {} };
+        (function(a) {
+            console;
+            if (console)
+                (a = a.p).q = a;
+        })(o);
+        console.log(o.p.q === o.p ? "PASS" : "FAIL");
+    }
+    expect_stdout: "PASS"
+}
index 7210c6b..b3650fa 100644 (file)
@@ -445,9 +445,9 @@ function addTrailingComma(list) {
 
 function createParams(was_async, was_generator, noDuplicate) {
     var save_async = async;
-    if (was_async) async = true;
+    if (!async) async = was_async;
     var save_generator = generator;
-    if (was_generator) generator = true;
+    if (!generator) generator = was_generator;
     var len = unique_vars.length;
     var params = [];
     for (var n = rng(4); --n >= 0;) {
@@ -569,9 +569,9 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
     function createName() {
         unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
         var save_async = async;
-        if (was_async) async = true;
+        if (!async) async = was_async;
         var save_generator = generator;
-        if (was_generator) generator = true;
+        if (!generator) generator = was_generator;
         var name = createVarName(MANDATORY);
         generator = save_generator;
         async = save_async;