enhance `collapse_vars` (#5268)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 6 Jan 2022 21:13:37 +0000 (21:13 +0000)
committerGitHub <noreply@github.com>
Thu, 6 Jan 2022 21:13:37 +0000 (05:13 +0800)
lib/compress.js
test/compress/assignments.js
test/compress/collapse_vars.js
test/compress/sequences.js

index 9320d5a..a7ceaf3 100644 (file)
@@ -1943,16 +1943,46 @@ Compressor.prototype.compress = function(node) {
                     if (stop_after === node) abort = true;
                     return node;
                 }
-                // Stop immediately if these node types are encountered
                 var parent = scanner.parent();
-                if (should_stop(node, parent)) {
-                    abort = true;
-                    return node;
-                }
                 // Stop only if candidate is found within conditional branches
                 if (!stop_if_hit && in_conditional(node, parent)) {
                     stop_if_hit = parent;
                 }
+                // Cascade compound assignments
+                if (compound && scan_lhs && can_replace && !stop_if_hit
+                    && node instanceof AST_Assign && node.operator != "=" && node.left.equivalent_to(lhs)) {
+                    replaced++;
+                    changed = true;
+                    AST_Node.info("Cascading {node} [{file}:{line},{col}]", {
+                        node: node,
+                        file: node.start.file,
+                        line: node.start.line,
+                        col: node.start.col,
+                    });
+                    can_replace = false;
+                    node.right.transform(scanner);
+                    clear_write_only(candidate);
+                    var assign = make_node(AST_Assign, node, {
+                        operator: "=",
+                        left: node.left,
+                        right: make_node(AST_Binary, node, {
+                            operator: node.operator.slice(0, -1),
+                            left: abort ? candidate : make_node(AST_Binary, candidate, {
+                                operator: compound,
+                                left: lhs,
+                                right: rvalue,
+                            }),
+                            right: node.right,
+                        }),
+                    });
+                    abort = true;
+                    return assign;
+                }
+                // Stop immediately if these node types are encountered
+                if (should_stop(node, parent)) {
+                    abort = true;
+                    return node;
+                }
                 // Skip transient nodes caused by single-use variable replacement
                 if (node.single_use && parent instanceof AST_VarDef && parent.value === node) return node;
                 // Replace variable with assignment when found
@@ -2006,13 +2036,8 @@ Compressor.prototype.compress = function(node) {
                             right: rvalue,
                         });
                     }
-                    var assign = candidate;
-                    while (assign.write_only) {
-                        assign.write_only = false;
-                        if (!(assign instanceof AST_Assign)) break;
-                        assign = assign.right;
-                    }
-                    assign = candidate.clone();
+                    clear_write_only(candidate);
+                    var assign = candidate.clone();
                     assign.right = rvalue;
                     return assign;
                 }
@@ -2095,6 +2120,12 @@ Compressor.prototype.compress = function(node) {
                 if (node instanceof AST_SymbolRef && node.definition() === def) {
                     if (is_lhs(node, multi_replacer.parent())) return node;
                     if (!--replaced) abort = true;
+                    AST_Node.info("Replacing {node} [{file}:{line},{col}]", {
+                        node: node,
+                        file: node.start.file,
+                        line: node.start.line,
+                        col: node.start.col,
+                    });
                     var ref = rvalue.clone();
                     ref.scope = node.scope;
                     ref.reference();
@@ -2138,6 +2169,7 @@ Compressor.prototype.compress = function(node) {
                     var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
                     var scan_rhs = foldable(candidate);
                     if (!scan_lhs && !scan_rhs) continue;
+                    var compound = candidate instanceof AST_Assign && candidate.operator.slice(0, -1);
                     var funarg = candidate.name instanceof AST_SymbolFunarg;
                     var may_throw = return_false;
                     if (candidate.may_throw(compressor)) {
@@ -2154,13 +2186,7 @@ Compressor.prototype.compress = function(node) {
                     var lvalues = get_lvalues(candidate);
                     var lhs_local = is_lhs_local(lhs);
                     var rhs_value = get_rvalue(candidate);
-                    var rvalue;
-                    if (rhs_value instanceof AST_Sequence
-                        && !(candidate instanceof AST_Assign && candidate.operator != "=")) {
-                        rvalue = rhs_value.tail_node();
-                    } else {
-                        rvalue = rhs_value;
-                    }
+                    var rvalue = !compound && rhs_value instanceof AST_Sequence ? rhs_value.tail_node() : rhs_value;
                     if (!side_effects) side_effects = value_has_side_effects();
                     var check_destructured = in_try || !lhs_local ? function(node) {
                         return node instanceof AST_Destructured;
@@ -2834,7 +2860,6 @@ Compressor.prototype.compress = function(node) {
             function get_lhs(expr) {
                 if (expr instanceof AST_Assign) {
                     var lhs = expr.left;
-                    if (expr.operator != "=") return lhs;
                     if (!(lhs instanceof AST_SymbolRef)) return lhs;
                     var def = lhs.definition();
                     if (scope.uses_arguments && is_funarg(def)) return lhs;
@@ -2849,7 +2874,7 @@ Compressor.prototype.compress = function(node) {
                             assign_pos = 0;
                         }
                     }
-                    mangleable_var(expr.right);
+                    if (expr.operator == "=") mangleable_var(expr.right);
                     return lhs;
                 }
                 if (expr instanceof AST_Binary) return expr.right.left;
@@ -2952,6 +2977,14 @@ Compressor.prototype.compress = function(node) {
                 };
             }
 
+            function clear_write_only(assign) {
+                while (assign.write_only) {
+                    assign.write_only = false;
+                    if (!(assign instanceof AST_Assign)) break;
+                    assign = assign.right;
+                }
+            }
+
             function may_be_global(node) {
                 if (node instanceof AST_SymbolRef) {
                     node = node.fixed_value();
@@ -3118,12 +3151,13 @@ Compressor.prototype.compress = function(node) {
 
             function is_lhs_local(lhs) {
                 var sym = root_expr(lhs);
-                return sym instanceof AST_SymbolRef
-                    && sym.definition().scope.resolve() === scope
-                    && !(in_loop
-                        && (lvalues.has(sym.name) && lvalues.get(sym.name)[0] !== lhs
-                            || candidate instanceof AST_Unary
-                            || candidate instanceof AST_Assign && candidate.operator != "="));
+                if (!(sym instanceof AST_SymbolRef)) return false;
+                if (sym.definition().scope.resolve() !== scope) return false;
+                if (!in_loop) return true;
+                if (compound) return false;
+                if (candidate instanceof AST_Unary) return false;
+                var lvalue = lvalues.get(sym.name);
+                return !lvalue || lvalue[0] === lhs;
             }
 
             function value_has_side_effects() {
@@ -7099,7 +7133,7 @@ Compressor.prototype.compress = function(node) {
                 log(node.name, text);
             } else {
                 AST_Node.info(text + " [{file}:{line},{col}]", {
-                    name: node.print_to_string(),
+                    name: node,
                     file: node.start.file,
                     line: node.start.line,
                     col : node.start.col,
index 9e83172..8490410 100644 (file)
@@ -530,6 +530,25 @@ logical_collapse_vars_2: {
     node_version: ">=15"
 }
 
+logical_collapse_vars_3: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 6;
+        a *= 7;
+        a ??= "FAIL";
+        console.log(a);
+    }
+    expect: {
+        var a = 6;
+        a = a * 7 ?? "FAIL";
+        console.log(a);
+    }
+    expect_stdout: "42"
+    node_version: ">=15"
+}
+
 logical_reduce_vars: {
     options = {
         evaluate: true,
index 03a375b..e05b3d4 100644 (file)
@@ -2995,6 +2995,43 @@ compound_assignment_4: {
     expect_stdout: "PASS"
 }
 
+compound_assignment_5: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 0, b;
+        a += 42;
+        b && (a *= null);
+        console.log(a);
+    }
+    expect: {
+        var a = 0, b;
+        a += 42;
+        b && (a *= null);
+        console.log(a);
+    }
+    expect_stdout: "42"
+}
+
+compound_assignment_6: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        a ^= 6;
+        a *= a + 1;
+        console.log(a);
+    }
+    expect: {
+        var a;
+        a = (a ^= 6) * (a + 1);
+        console.log(a);
+    }
+    expect_stdout: "42"
+}
+
 issue_2187_1: {
     options = {
         collapse_vars: true,
index efd8150..59e0d56 100644 (file)
@@ -774,7 +774,7 @@ side_effects_cascade_3: {
     }
     expect: {
         function f(a, b) {
-            (b += a) || (b = a) || (b -= a, b ^= a),
+            (b += a) || (b = a) || (b = b - a ^ a),
             a--;
         }
     }