enhance `loops` & `unused` (#4074)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 26 Aug 2020 01:32:55 +0000 (02:32 +0100)
committerGitHub <noreply@github.com>
Wed, 26 Aug 2020 01:32:55 +0000 (09:32 +0800)
- extend `ufuzz` generation of for-in loops

lib/compress.js
test/compress/loops.js
test/ufuzz/index.js

index 4744566..213cead 100644 (file)
@@ -4456,7 +4456,7 @@ merge(Compressor.prototype, {
                 if (drop_funcs && node !== self && node instanceof AST_Defun) {
                     var def = node.name.definition();
                     if (!(def.id in in_use_ids)) {
-                        log(node.name, "Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
+                        log(node.name, "Dropping unused function {name}");
                         def.eliminated++;
                         return in_list ? List.skip : make_node(AST_EmptyStatement, node);
                     }
@@ -4471,7 +4471,7 @@ merge(Compressor.prototype, {
                         if (!(sym.definition().id in in_use_ids)) {
                             sym.__unused = true;
                             if (trim) {
-                                log(sym, "Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
+                                log(sym, "Dropping unused function argument {name}");
                                 a.pop();
                             }
                         } else {
@@ -4574,7 +4574,7 @@ merge(Compressor.prototype, {
                             AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
                             side_effects.push(value);
                         } else {
-                            log(def.name, "Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
+                            log(def.name, "Dropping unused variable {name}");
                         }
                         sym.eliminated++;
                     }
@@ -4669,19 +4669,22 @@ merge(Compressor.prototype, {
                 return !block ? node : in_list ? List.splice(block.body) : block;
             } else if (node instanceof AST_ForIn) {
                 if (!drop_vars || !compressor.option("loops")) return;
-                if (!(node.init instanceof AST_Definitions)) return;
-                var sym = node.init.definitions[0].name;
-                if (sym.definition().id in in_use_ids) return;
                 if (!is_empty(node.body)) return;
-                log(sym, "Dropping unused loop variable {name} [{file}:{line},{col}]", template(sym));
-                var value = node.object.drop_side_effect_free(compressor);
-                if (value) {
-                    AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", template(sym));
-                    return make_node(AST_SimpleStatement, node, {
-                        body: value
-                    });
+                var sym = node.init;
+                if (sym instanceof AST_Definitions) {
+                    sym = sym.definitions[0].name;
+                } else while (sym instanceof AST_PropAccess) {
+                    sym = sym.expression.tail_node();
                 }
-                return in_list ? List.skip : make_node(AST_EmptyStatement, node);
+                var def = sym.definition();
+                if (!def || def.id in in_use_ids) return;
+                log(sym, "Dropping unused loop variable {name}");
+                var value = node.object.drop_side_effect_free(compressor);
+                if (!value) return in_list ? List.skip : make_node(AST_EmptyStatement, node);
+                AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", value.start);
+                return make_node(AST_SimpleStatement, node, {
+                    body: value
+                });
             } else if (node instanceof AST_Sequence) {
                 if (node.expressions.length == 1) return node.expressions[0];
             }
@@ -4701,8 +4704,8 @@ merge(Compressor.prototype, {
             drop_unused_call_args(call, compressor, fns_with_marked_args);
         });
 
-        function log(sym, text, props) {
-            AST_Node[sym.definition().references.length > 0 ? "info" : "warn"](text, props);
+        function log(sym, text) {
+            AST_Node[sym.definition().references.length > 0 ? "info" : "warn"](text + " [{file}:{line},{col}]", template(sym));
         }
 
         function template(sym) {
@@ -4790,6 +4793,13 @@ merge(Compressor.prototype, {
                 if (track_assigns(node_def, sym) && is_lhs(sym, node) !== sym) add_assigns(node_def, sym);
                 return true;
             }
+            if (node instanceof AST_ForIn) {
+                if (!compressor.option("loops")) return;
+                if (!is_empty(node.body)) return;
+                if (node.init.has_side_effects(compressor)) return;
+                node.object.walk(tw);
+                return true;
+            }
             if (node instanceof AST_SymbolRef) {
                 node_def = node.definition();
                 if (!(node_def.id in in_use_ids)) {
index aef1b90..4a276b9 100644 (file)
@@ -756,7 +756,37 @@ empty_for_in_side_effects: {
     expect_warnings: [
         "WARN: Dropping unused variable b [test/compress/loops.js:4,16]",
         "INFO: Dropping unused loop variable a [test/compress/loops.js:1,17]",
-        "WARN: Side effects in object of for-in loop [test/compress/loops.js:1,17]",
+        "WARN: Side effects in object of for-in loop [test/compress/loops.js:2,17]",
+    ]
+}
+
+empty_for_in_prop_init: {
+    options = {
+        loops: true,
+        pure_getters: "strict",
+        unused: true,
+    }
+    input: {
+        console.log(function f() {
+            var a = "bar";
+            for ((a, f)[a] in console.log("foo"));
+            return a;
+        }());
+    }
+    expect: {
+        console.log(function() {
+            var a = "bar";
+            console.log("foo");
+            return a;
+        }());
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+    ]
+    expect_warnings: [
+        "INFO: Dropping unused loop variable f [test/compress/loops.js:3,21]",
+        "WARN: Side effects in object of for-in loop [test/compress/loops.js:3,30]",
     ]
 }
 
index 9e8ca8d..60f8666 100644 (file)
@@ -496,11 +496,16 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
         var label = createLabel(canBreak, canContinue);
         canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
         canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
-        var optElementVar = "";
-        if (rng(5) > 1) {
-            optElementVar = "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[key" + loop + "]; ";
-        }
-        return "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; " + label.target + " for (var key" + loop + " in expr" + loop + ") {" + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}}";
+        var key = rng(10) ? "key" + loop : getVarName();
+        return [
+            "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
+            label.target + " for (",
+            /^key/.test(key) ? "var " : "",
+            key + " in expr" + loop + ") {",
+            rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
+            createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
+            "}}",
+        ].join("");
       case STMT_SEMI:
         return use_strict && rng(20) === 0 ? '"use strict";' : ";";
       case STMT_EXPR: