From 6eceac096694cfa3390e7112c4764a235592751d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 5 Jan 2021 07:02:49 +0000 Subject: [PATCH] enhance `inline` & `side_effects` (#4506) --- lib/ast.js | 4 + lib/compress.js | 164 +++++++++++++++++++++++++------- test/compress/default-values.js | 57 ++++++++++- test/compress/destructured.js | 42 ++++---- 4 files changed, 214 insertions(+), 53 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 7b4c03d7..e8482d70 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -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; diff --git a/lib/compress.js b/lib/compress.js index 1934ca3d..459d54f0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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) { diff --git a/test/compress/default-values.js b/test/compress/default-values.js index 7308b303..0fa27ea5 100644 --- a/test/compress/default-values.js +++ b/test/compress/default-values.js @@ -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" diff --git a/test/compress/destructured.js b/test/compress/destructured.js index 9812b146..f9d32ce0 100644 --- a/test/compress/destructured.js +++ b/test/compress/destructured.js @@ -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; -- 2.34.1