enhance `inline` (#3767)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 10 Apr 2020 02:48:24 +0000 (03:48 +0100)
committerGitHub <noreply@github.com>
Fri, 10 Apr 2020 02:48:24 +0000 (10:48 +0800)
lib/compress.js
test/compress/functions.js
test/compress/issue-2719.js
test/compress/reduce_vars.js
test/input/rename/input.js
test/mocha/cli.js

index f03e0ee..7961486 100644 (file)
@@ -5819,38 +5819,45 @@ merge(Compressor.prototype, {
                 var args = self.args.concat(value || make_node(AST_Undefined, self));
                 return make_sequence(self, args).optimize(compressor);
             }
-            var funarg, pos;
-            if (value instanceof AST_SymbolRef
-                && (funarg = resolve_funarg(value.definition().orig))
-                && (pos = fn.argnames.indexOf(funarg)) >= 0
-                && (pos >= self.args.length - 1 || all(self.args.slice(pos), function(funarg) {
-                    return !funarg.has_side_effects(compressor);
-                }))) {
-                var args = self.args.slice();
-                args.push(args.splice(pos, 1)[0] || make_node(AST_Undefined, self));
-                var node = make_sequence(self, args).optimize(compressor);
-                return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
-            }
         }
         if (is_func) {
-            var def, value, scope, in_loop, level = -1;
+            var def, value;
             if (can_inline
                 && !fn.uses_arguments
                 && !fn.pinned()
                 && !(fn.name && fn instanceof AST_Function)
                 && (value = can_flatten_body(stat))
                 && (exp === fn
-                    || compressor.option("unused")
-                        && (def = exp.definition()).references.length == 1
-                        && !recursive_ref(compressor, def)
-                        && fn.is_constant_expression(exp.scope))
-                && !self.pure
-                && !fn.contains_this()
-                && can_inject_symbols()) {
-                fn._squeezed = true;
-                if (exp !== fn) fn.parent_scope = exp.scope;
-                var node = make_sequence(self, flatten_fn()).optimize(compressor);
-                return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
+                    || !recursive_ref(compressor, def = exp.definition) && fn.is_constant_expression(exp.scope))
+                && !fn.contains_this()) {
+                if (can_substitute_directly()) {
+                    var args = self.args.slice();
+                    args.push(value.clone(true).transform(new TreeTransformer(function(node) {
+                        if (node instanceof AST_SymbolRef) {
+                            var def = node.definition();
+                            if (fn.variables.get(node.name) !== def) return node;
+                            var index = resolve_index(def);
+                            var arg = args[index];
+                            if (!arg) return make_node(AST_Undefined, self);
+                            args[index] = null;
+                            var parent = this.parent();
+                            return parent ? maintain_this_binding(compressor, parent, node, arg) : arg;
+                        }
+                    })));
+                    var node = make_sequence(self, args.filter(function(arg) {
+                        return arg;
+                    })).optimize(compressor);
+                    node = maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
+                    if (best_of(compressor, self, node) === node) return node;
+                }
+                var scope, in_loop, level = -1;
+                if ((exp === fn || compressor.option("unused") && exp.definition().references.length == 1)
+                    && can_inject_symbols()) {
+                    fn._squeezed = true;
+                    if (exp !== fn) fn.parent_scope = exp.scope;
+                    var node = make_sequence(self, flatten_fn()).optimize(compressor);
+                    return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
+                }
             }
             if (compressor.option("side_effects")
                 && all(fn.body, is_empty)
@@ -5877,14 +5884,6 @@ merge(Compressor.prototype, {
         }
         return try_evaluate(compressor, self);
 
-        function resolve_funarg(orig) {
-            var funarg;
-            for (var i = 0; orig[i] instanceof AST_SymbolFunarg; i++) {
-                funarg = orig[i];
-            }
-            return funarg;
-        }
-
         function return_value(stat) {
             if (!stat) return make_node(AST_Undefined, self);
             if (stat instanceof AST_Return) {
@@ -5924,6 +5923,61 @@ merge(Compressor.prototype, {
             return return_value(stat);
         }
 
+        function resolve_index(def) {
+            for (var i = fn.argnames.length; --i >= 0;) {
+                if (fn.argnames[i].definition() === def) return i;
+            }
+        }
+
+        function can_substitute_directly() {
+            if (compressor.option("inline") <= 1 && fn.argnames.length) return;
+            if (fn.variables.size() > fn.argnames.length + 1) return;
+            if (!all(fn.argnames, function(argname) {
+                return argname.definition().references.length < 2;
+            })) return;
+            var abort = false;
+            var begin;
+            var in_order = [];
+            var side_effects = false;
+            value.walk(new TreeWalker(function(node) {
+                if (abort) return true;
+                if (node instanceof AST_Binary && lazy_op[node.operator]
+                    || node instanceof AST_Conditional) {
+                    in_order = null;
+                    return;
+                }
+                if (node instanceof AST_Scope) return abort = true;
+                var def;
+                if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === (def = node.definition())) {
+                    if (def.init instanceof AST_Defun) return abort = true;
+                    if (is_lhs(node, this.parent())) return abort = true;
+                    var index = resolve_index(def);
+                    if (!(begin < index)) begin = index;
+                    if (!in_order) return;
+                    if (side_effects) {
+                        in_order = null;
+                    } else {
+                        in_order.push(fn.argnames[index]);
+                    }
+                    return;
+                }
+                if (node.has_side_effects(compressor)) side_effects = true;
+            }));
+            if (abort) return;
+            var end = self.args.length;
+            if (in_order && fn.argnames.length >= end) {
+                end = fn.argnames.length;
+                while (end-- > begin && fn.argnames[end] === in_order.pop());
+                end++;
+            }
+            var scope = side_effects && compressor.find_parent(AST_Scope);
+            return end <= begin || all(self.args.slice(begin, end), side_effects ? function(funarg) {
+                return funarg.is_constant_expression(scope);
+            } : function(funarg) {
+                return !funarg.has_side_effects(compressor);
+            });
+        }
+
         function var_exists(defined, name) {
             return defined[name] || identifier_atom[name] || scope.var_names()[name];
         }
index b1a44ff..4a192d5 100644 (file)
@@ -1020,9 +1020,7 @@ issue_2616: {
     }
     expect: {
         var c = "FAIL";
-        !function(NaN) {
-            (true << NaN) - 0/0 || (c = "PASS");
-        }([]);
+        (true << []) - NaN || (c = "PASS");
         console.log(c);
     }
     expect_stdout: "PASS"
@@ -1607,7 +1605,31 @@ duplicate_argnames_2: {
     }
     expect: {
         var a = "PASS";
-        console, b && (a = "FAIL");
+        console, void 0 && (a = "FAIL");
+        console.log(a);
+    }
+    expect_stdout: "PASS"
+}
+
+duplicate_argnames_3: {
+    options = {
+        inline: true,
+        reduce_vars: true,
+        side_effects: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a = "FAIL";
+        function f(b, b, b) {
+            b && (a = "PASS");
+        }
+        f(null, 0, console, "42".toString());
+        console.log(a);
+    }
+    expect: {
+        var a = "FAIL";
+        b = console, "42".toString(), b && (a = "PASS");
         var b;
         console.log(a);
     }
@@ -1766,8 +1788,7 @@ inline_2: {
     }
     expect: {
         console.log(1);
-        a = 2, console.log(a);
-        var a;
+        console.log(2);
         (function(b) {
             var c = b;
             console.log(c);
@@ -1800,8 +1821,7 @@ inline_3: {
     }
     expect: {
         console.log(1);
-        a = 2, console.log(a);
-        var a;
+        console.log(2);
         b = 3, c = b, console.log(c);
         var b, c;
     }
@@ -1832,8 +1852,7 @@ inline_true: {
     }
     expect: {
         console.log(1);
-        a = 2, console.log(a);
-        var a;
+        console.log(2);
         b = 3, c = b, console.log(c);
         var b, c;
     }
@@ -1897,7 +1916,24 @@ duplicate_arg_var_2: {
         }("PA"));
     }
     expect: {
-        console.log((b = "PA", b + "SS"));
+        console.log("PA" + "SS");
+    }
+    expect_stdout: "PASS"
+}
+
+duplicate_arg_var_3: {
+    options = {
+        inline: true,
+        toplevel: true,
+    }
+    input: {
+        console.log(function(b) {
+            return b + "SS";
+            var b;
+        }("PA", "42".toString()));
+    }
+    expect: {
+        console.log((b = "PA", "42".toString(), b + "SS"));
         var b;
     }
     expect_stdout: "PASS"
@@ -2045,10 +2081,8 @@ issue_3016_1: {
     expect: {
         var b = 1;
         do {
-            a = 3,
-            a[b];
+            3[b];
         } while(0);
-        var a;
         console.log(b);
     }
     expect_stdout: "1"
@@ -2556,10 +2590,9 @@ cross_references_2: {
     options = {
         collapse_vars: true,
         evaluate: true,
-        hoist_props: true,
         inline: true,
-        passes: 4,
-        pure_getters: true,
+        passes: 6,
+        properties: true,
         reduce_vars: true,
         sequences: true,
         side_effects: true,
@@ -3685,9 +3718,7 @@ pr_3595_3: {
         var g = [ "PASS" ];
         console.log(function(problem) {
             return g[problem];
-        }(function(arg) {
-            return g.indexOf(arg);
-        }("PASS")));
+        }(g.indexOf("PASS")));
     }
     expect_stdout: "PASS"
 }
index dc37f40..f11d007 100644 (file)
@@ -26,7 +26,7 @@ warn: {
         }().length);
     }
     expect_warnings: [
-        "WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]",
         "WARN: Function.prototype.arguments not supported [test/compress/issue-2719.js:5,19]",
+        "WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]",
     ]
 }
index 6db0a47..85ca6bd 100644 (file)
@@ -2289,11 +2289,10 @@ redefine_farg_2: {
         console.log(f([]), g([]), h([]));
     }
     expect: {
-        console.log((a = [], typeof a), "number",function(a, b) {
+        console.log(typeof [], "number",function(a, b) {
             a = b;
             return typeof a;
         }([]));
-        var a;
     }
     expect_stdout: "object number undefined"
 }
index 3008433..7aff03a 100644 (file)
@@ -1,6 +1,6 @@
 function f(x) {
     return g(x);
     function g(x) {
-        return x + 1;
+        return x + x;
     }
 }
index 2e68fbf..82f76c0 100644 (file)
@@ -674,7 +674,7 @@ describe("bin/uglifyjs", function() {
         var command = uglifyjscmd + " test/input/rename/input.js --rename";
         exec(command, function(err, stdout, stderr) {
             if (err) throw err;
-            assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c+1}}\n");
+            assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c+c}}\n");
             done();
         });
     });
@@ -682,7 +682,7 @@ describe("bin/uglifyjs", function() {
         var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename";
         exec(command, function(err, stdout, stderr) {
             if (err) throw err;
-            assert.strictEqual(stdout, "function f(n){return function(n){return n+1}(n)}\n");
+            assert.strictEqual(stdout, "function f(n){return function(n){return n+n}(n)}\n");
             done();
         });
     });
@@ -690,7 +690,7 @@ describe("bin/uglifyjs", function() {
         var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2";
         exec(command, function(err, stdout, stderr) {
             if (err) throw err;
-            assert.strictEqual(stdout, "function f(n){return n+1}\n");
+            assert.strictEqual(stdout, "function f(n){return n+n}\n");
             done();
         });
     });
@@ -698,7 +698,7 @@ describe("bin/uglifyjs", function() {
         var command = uglifyjscmd + " test/input/rename/input.js -c passes=2";
         exec(command, function(err, stdout, stderr) {
             if (err) throw err;
-            assert.strictEqual(stdout, "function f(x){return function(x){return x+1}(x)}\n");
+            assert.strictEqual(stdout, "function f(x){return function(x){return x+x}(x)}\n");
             done();
         });
     });