enhance `inline` & `side_effects` (#4506)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 5 Jan 2021 07:02:49 +0000 (07:02 +0000)
committerGitHub <noreply@github.com>
Tue, 5 Jan 2021 07:02:49 +0000 (15:02 +0800)
lib/ast.js
lib/compress.js
test/compress/default-values.js
test/compress/destructured.js

index 7b4c03d..e8482d7 100644 (file)
@@ -521,6 +521,10 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
     },
     each_argname: function(visit) {
         var tw = new TreeWalker(function(node) {
+            if (node instanceof AST_DefaultValue) {
+                node.name.walk(tw);
+                return true;
+            }
             if (node instanceof AST_DestructuredKeyVal) {
                 node.value.walk(tw);
                 return true;
index 1934ca3..459d54f 100644 (file)
@@ -1210,7 +1210,7 @@ merge(Compressor.prototype, {
     });
 
     AST_Node.DEFMETHOD("convert_symbol", noop);
-    AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
+    function convert_destructured(type, process) {
         return this.transform(new TreeTransformer(function(node, descend) {
             if (node instanceof AST_DefaultValue) {
                 node = node.clone();
@@ -1229,7 +1229,9 @@ merge(Compressor.prototype, {
             }
             return node.convert_symbol(type, process);
         }));
-    });
+    }
+    AST_DefaultValue.DEFMETHOD("convert_symbol", convert_destructured);
+    AST_Destructured.DEFMETHOD("convert_symbol", convert_destructured);
     function convert_symbol(type, process) {
         var node = make_node(type, this, this);
         process(node, this);
@@ -5314,6 +5316,12 @@ merge(Compressor.prototype, {
         }
     });
 
+    function fill_holes(orig, elements) {
+        for (var i = elements.length; --i >= 0;) {
+            if (!elements[i]) elements[i] = make_node(AST_Hole, orig);
+        }
+    }
+
     AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
         if (!compressor.option("unused")) return;
         var self = this;
@@ -6143,9 +6151,7 @@ merge(Compressor.prototype, {
                     });
                     value = save;
                     if (values && elements.length == 0) return null;
-                    for (var i = elements.length; --i >= 0;) {
-                        if (!elements[i]) elements[i] = make_node(AST_Hole, node.elements[i] || node);
-                    }
+                    fill_holes(node, elements);
                     node.elements = elements;
                     return node;
                 }
@@ -8052,24 +8058,32 @@ merge(Compressor.prototype, {
         var is_func = fn instanceof AST_Lambda && (!is_async(fn)
             || compressor.option("awaits") && compressor.parent() instanceof AST_Await);
         var stat = is_func && fn.first_statement();
-        var has_default = false;
+        var has_default = 0, has_destructured = false;
+        var has_spread = !all(self.args, function(arg) {
+            return !(arg instanceof AST_Spread);
+        });
         var can_drop = is_func && all(fn.argnames, function(argname, index) {
-            if (has_default && self.args[index] instanceof AST_Spread) return false;
+            if (has_default == 1 && self.args[index] instanceof AST_Spread) has_default = 2;
             if (argname instanceof AST_DefaultValue) {
-                has_default = true;
-                var arg = self.args[index];
-                if (arg && !is_undefined(arg)) return false;
+                if (!has_default) has_default = 1;
+                var arg = has_default == 1 && self.args[index];
+                if (arg && !is_undefined(arg)) has_default = 2;
+                if (has_arg_refs(argname.value)) return false;
+                argname = argname.name;
+            }
+            if (argname instanceof AST_Destructured) {
+                has_destructured = true;
                 var abort = false;
-                argname.value.walk(new TreeWalker(function(node) {
+                argname.walk(new TreeWalker(function(node) {
                     if (abort) return true;
-                    if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) {
-                        return abort = true;
+                    if (node instanceof AST_DestructuredKeyVal) {
+                        var key = node.key;
+                        if (key instanceof AST_Node && has_arg_refs(key)) return abort = true;
                     }
                 }));
                 if (abort) return false;
-                argname = argname.name;
             }
-            return !(argname instanceof AST_Destructured);
+            return true;
         });
         var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor);
         if (can_inline && stat instanceof AST_Return) {
@@ -8086,9 +8100,7 @@ merge(Compressor.prototype, {
                 && !(fn.name && fn instanceof AST_Function)
                 && (exp === fn || !recursive_ref(compressor, def = exp.definition())
                     && fn.is_constant_expression(find_scope(compressor)))
-                && all(self.args, function(arg) {
-                    return !(arg instanceof AST_Spread);
-                })
+                && !has_spread
                 && (value = can_flatten_body(stat))
                 && !fn.contains_this()) {
                 var replacing = exp === fn || def.single_use && def.references.length - def.replaced == 1;
@@ -8161,13 +8173,79 @@ merge(Compressor.prototype, {
         }
         return try_evaluate(compressor, self);
 
-        function convert_args(value) {
-            var args = self.args.map(function(arg) {
-                return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
-                    elements: [ arg ],
-                }) : arg;
+        function has_arg_refs(node) {
+            var found = false;
+            node.walk(new TreeWalker(function(node) {
+                if (found) return true;
+                if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) {
+                    return found = true;
+                }
+            }));
+            return found;
+        }
+
+        function make_void_lhs(orig) {
+            return make_node(AST_Sub, orig, {
+                expression: make_node(AST_Number, orig, { value: 0 }),
+                property: make_node(AST_Number, orig, { value: 0 }),
             });
-            fn.argnames.forEach(function(argname, index) {
+        }
+
+        function convert_args(value) {
+            var args = self.args.slice();
+            var destructured = has_default > 1 || has_destructured;
+            if (destructured || has_spread) args = [ make_node(AST_Array, self, { elements: args }) ];
+            if (destructured) {
+                var tt = new TreeTransformer(function(node, descend) {
+                    if (node instanceof AST_DefaultValue) return make_node(AST_DefaultValue, node, {
+                        name: node.name.transform(tt) || make_void_lhs(node),
+                        value: node.value,
+                    });
+                    if (node instanceof AST_DestructuredArray) {
+                        var elements = [];
+                        node.elements.forEach(function(node, index) {
+                            node = node.transform(tt);
+                            if (node) elements[index] = node;
+                        });
+                        fill_holes(node, elements);
+                        return make_node(AST_DestructuredArray, node, { elements: elements });
+                    }
+                    if (node instanceof AST_DestructuredObject) {
+                        var properties = [], side_effects = [];
+                        node.properties.forEach(function(prop) {
+                            var key = prop.key;
+                            var value = prop.value.transform(tt);
+                            if (value) {
+                                side_effects.push(key instanceof AST_Node ? key : make_node_from_constant(key, prop));
+                                properties.push(make_node(AST_DestructuredKeyVal, prop, {
+                                    key: make_sequence(node, side_effects),
+                                    value: value,
+                                }));
+                                side_effects = [];
+                            } else if (key instanceof AST_Node) {
+                                side_effects.push(key);
+                            }
+                        });
+                        if (side_effects.length) properties.push(make_node(AST_DestructuredKeyVal, node, {
+                            key: make_sequence(node, side_effects),
+                            value: make_void_lhs(node),
+                        }));
+                        return make_node(AST_DestructuredObject, node, { properties: properties });
+                    }
+                    if (node instanceof AST_SymbolFunarg) return null;
+                });
+                var lhs = [];
+                fn.argnames.forEach(function(argname, index) {
+                    argname = argname.transform(tt);
+                    if (argname) lhs[index] = argname;
+                });
+                fill_holes(fn, lhs);
+                args[0] = make_node(AST_Assign, self, {
+                    operator: "=",
+                    left: make_node(AST_DestructuredArray, fn, { elements: lhs }),
+                    right: args[0],
+                });
+            } else fn.argnames.forEach(function(argname) {
                 if (argname instanceof AST_DefaultValue) args.push(argname.value);
             });
             args.push(value || make_node(AST_Undefined, self));
@@ -8241,8 +8319,7 @@ merge(Compressor.prototype, {
         }
 
         function can_substitute_directly() {
-            if (has_default) return;
-            if (var_assigned) return;
+            if (has_default || has_destructured || var_assigned) return;
             if (compressor.option("inline") < 2 && fn.argnames.length) return;
             if (!fn.variables.all(function(def) {
                 return def.references.length - def.replaced < 2 && def.orig[0] instanceof AST_SymbolFunarg;
@@ -8308,15 +8385,16 @@ merge(Compressor.prototype, {
         }
 
         function can_inject_args(defined, used, safe_to_inject) {
-            for (var i = 0; i < fn.argnames.length; i++) {
-                var arg = fn.argnames[i];
-                if (arg.__unused) continue;
+            var abort = false;
+            fn.each_argname(function(arg) {
+                if (abort) return;
+                if (arg.__unused) return;
                 if (arg instanceof AST_DefaultValue) arg = arg.name;
-                if (!safe_to_inject || var_exists(defined, arg.name)) return false;
+                if (!safe_to_inject || var_exists(defined, arg.name)) return abort = true;
                 used[arg.name] = true;
                 if (in_loop) in_loop.push(arg.definition());
-            }
-            return true;
+            });
+            return !abort;
         }
 
         function can_inject_vars(defined, used, safe_to_inject) {
@@ -8450,6 +8528,22 @@ merge(Compressor.prototype, {
             }
         }
 
+        function flatten_destructured(decls, expressions) {
+            expressions.push(make_node(AST_Assign, self, {
+                operator: "=",
+                left: make_node(AST_DestructuredArray, self, {
+                    elements: fn.argnames.map(function(argname) {
+                        return argname.convert_symbol(AST_SymbolRef, function(ref, name) {
+                            var symbol = make_node(AST_SymbolVar, name, name);
+                            name.definition().orig.push(symbol);
+                            append_var(decls, expressions, symbol);
+                        });
+                    }),
+                }),
+                right: make_node(AST_Array, self, { elements: self.args.slice() }),
+            }));
+        }
+
         function flatten_vars(decls, expressions) {
             var pos = expressions.length;
             for (var i = 0; i < fn.body.length; i++) {
@@ -8484,7 +8578,11 @@ merge(Compressor.prototype, {
         function flatten_fn() {
             var decls = [];
             var expressions = [];
-            flatten_args(decls, expressions);
+            if (has_default > 1 || has_destructured) {
+                flatten_destructured(decls, expressions);
+            } else {
+                flatten_args(decls, expressions);
+            }
             flatten_vars(decls, expressions);
             expressions.push(value);
             var args = fn.body.filter(function(stat) {
index 7308b30..0fa27ea 100644 (file)
@@ -354,6 +354,22 @@ inline_constant: {
     node_version: ">=6"
 }
 
+inline_destructured: {
+    options = {
+        inline: true,
+    }
+    input: {
+        console.log(function([ a ] = []) {
+            return "PASS";
+        }());
+    }
+    expect: {
+        console.log(([ [] = [] ] = [], "PASS"));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
 inline_function: {
     options = {
         default_values: true,
@@ -423,6 +439,45 @@ inline_loop_2: {
     node_version: ">=6"
 }
 
+inline_side_effects_1: {
+    options = {
+        inline: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 42;
+        (function(b = --a) {})(console);
+        console.log(a);
+    }
+    expect: {
+        var a = 42;
+        [ b = --a ] = [ console ],
+        void 0;
+        var b;
+        console.log(a);
+    }
+    expect_stdout: "42"
+    node_version: ">=6"
+}
+
+inline_side_effects_2: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        var a = 42;
+        (function(b = --a) {})(console);
+        console.log(a);
+    }
+    expect: {
+        var a = 42;
+        [ 0[0] = --a ] = [ console ];
+        console.log(a);
+    }
+    expect_stdout: "42"
+    node_version: ">=6"
+}
+
 drop_empty_iife: {
     options = {
         side_effects: true,
@@ -1419,7 +1474,7 @@ issue_4502_4: {
         (function(a, b = console.log("FAIL")) {})(..."" + console.log(42));
     }
     expect: {
-        (function(a, b = console.log("FAIL")) {})(..."" + console.log(42));
+        [ , 0[0] = console.log("FAIL") ] = [ ..."" + console.log(42) ];
     }
     expect_stdout: "42"
     node_version: ">=6"
index 9812b14..f9d32ce 100644 (file)
@@ -188,7 +188,7 @@ funarg_side_effects_1: {
     }
     expect: {
         try {
-            (function({}) {})();
+            [ {} ] = [];
         } catch (e) {
             console.log("PASS");
         }
@@ -682,7 +682,7 @@ funarg_inline: {
     }
     expect: {
         try {
-            (function({}) {})();
+            [ {} ] = [];
         } catch (e) {
             console.log("PASS");
         }
@@ -1718,10 +1718,14 @@ issue_4312: {
     expect: {
         var a;
         b = "PASS",
-        (function({
-            [a = b]: d,
-        }){})((c = "FAIL") && c);
-        var b, c;
+        c = "FAIL",
+        [
+            {
+                [a = b]: d,
+            },
+        ] = [ c && c ],
+        void 0;
+        var b, c, d;
         console.log(a);
     }
     expect_stdout: "PASS"
@@ -1783,9 +1787,7 @@ issue_4319: {
         function f(a) {
             while (!a);
         }
-        console.log(function({}) {
-            return f(console);
-        }(0));
+        console.log(([ {} ] = [ 0 ], f(console)));
     }
     expect_stdout: "undefined"
     node_version: ">=6"
@@ -1809,11 +1811,9 @@ issue_4321: {
     }
     expect: {
         try {
-            console.log(function({}) {
-                return function() {
-                    while (!console);
-                }();
-            }());
+            console.log(([ {} ] = [], function() {
+                while (!console);
+            }()));
         } catch (e) {
             console.log("PASS");
         }
@@ -1844,11 +1844,15 @@ issue_4323: {
     }
     expect: {
         var a = 0;
-        (function({
-            [function a() {
-                console.log(typeof a);
-            }()]: d,
-        }) {})(0);
+        [
+            {
+                [function a() {
+                    console.log(typeof a);
+                }()]: d,
+            },
+        ] = [ 0 ],
+        void 0;
+        var d;
         e = 1,
         console.log,
         void e.p;