From 0417a69c3ef4922470b6891ad6039221059f5750 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Jan 2021 13:52:14 +0000 Subject: [PATCH] enhance `collapse_vars` & `dead_code` (#4491) --- lib/ast.js | 2 +- lib/compress.js | 100 ++++++++++++++++++++++----------- lib/scope.js | 4 +- test/compress/collapse_vars.js | 53 +++++++++++++++-- test/compress/dead-code.js | 2 +- test/compress/functions.js | 4 +- 6 files changed, 120 insertions(+), 45 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 60e57385..a7885a79 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -1342,7 +1342,7 @@ var AST_Label = DEFNODE("Label", "references", { } }, AST_Symbol); -var AST_SymbolRef = DEFNODE("SymbolRef", "fixed in_arg", { +var AST_SymbolRef = DEFNODE("SymbolRef", "fixed in_arg redef", { $documentation: "Reference to some symbol (not definition/declaration)", }, AST_Symbol); diff --git a/lib/compress.js b/lib/compress.js index bd98c306..0a560dda 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1204,10 +1204,8 @@ merge(Compressor.prototype, { }); AST_SymbolRef.DEFMETHOD("is_immutable", function() { - var def = this.definition(); - if (def.orig.length != 1) return false; - var sym = def.orig[0]; - return sym instanceof AST_SymbolLambda && def.scope.name === sym; + var def = this.redef || this.definition(); + return def.orig.length == 1 && def.orig[0] instanceof AST_SymbolLambda; }); AST_Node.DEFMETHOD("convert_symbol", noop); @@ -1296,8 +1294,9 @@ merge(Compressor.prototype, { function is_lhs_read_only(lhs, compressor) { if (lhs instanceof AST_This) return true; if (lhs instanceof AST_SymbolRef) { + if (lhs.is_immutable()) return true; var def = lhs.definition(); - return def.lambda || compressor.exposed(def) && identifier_atom[def.name]; + return compressor.exposed(def) && identifier_atom[def.name]; } if (lhs instanceof AST_PropAccess) { lhs = lhs.expression; @@ -1440,10 +1439,6 @@ merge(Compressor.prototype, { return node instanceof AST_SymbolRef && node.definition().undeclared; } - function get_rvalue(expr) { - return expr[expr instanceof AST_Assign ? "right" : "value"]; - } - var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Math Number parseFloat parseInt RangeError ReferenceError RegExp Object setInterval setTimeout String SyntaxError TypeError unescape URIError"); AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { return this.defined @@ -1580,6 +1575,15 @@ merge(Compressor.prototype, { line: node.start.line, col: node.start.col, }); + if (candidate.TYPE == "Binary") return make_node(AST_Assign, candidate, { + operator: "=", + left: candidate.right.left, + right: make_node(AST_Conditional, candidate, { + condition: candidate.operator == "&&" ? candidate.left : candidate.left.negate(compressor), + consequent: candidate.right.right, + alternative: node, + }), + }); if (candidate instanceof AST_UnaryPostfix) { if (lhs instanceof AST_SymbolRef) lhs.definition().fixed = false; return make_node(AST_UnaryPrefix, candidate, candidate); @@ -2004,10 +2008,12 @@ merge(Compressor.prototype, { } } - function extract_candidates(expr) { + function extract_candidates(expr, unused) { hit_stack.push(expr); if (expr instanceof AST_Array) { - expr.elements.forEach(extract_candidates); + expr.elements.forEach(function(node) { + extract_candidates(node, unused); + }); } else if (expr instanceof AST_Assign) { if (!(expr.left instanceof AST_Destructured)) candidates.push(hit_stack.slice()); extract_candidates(expr.left); @@ -2016,10 +2022,18 @@ merge(Compressor.prototype, { assignments[expr.left.name] = (assignments[expr.left.name] || 0) + 1; } } else if (expr instanceof AST_Await) { - extract_candidates(expr.expression); + extract_candidates(expr.expression, unused); } else if (expr instanceof AST_Binary) { - extract_candidates(expr.left); - extract_candidates(expr.right); + var lazy = lazy_op[expr.operator]; + if (unused + && lazy + && expr.right instanceof AST_Assign + && expr.right.operator == "=" + && !(expr.right.left instanceof AST_Destructured)) { + candidates.push(hit_stack.slice()); + } + extract_candidates(expr.left, !lazy && unused); + extract_candidates(expr.right, unused); } else if (expr instanceof AST_Call) { extract_candidates(expr.expression); expr.args.forEach(extract_candidates); @@ -2027,8 +2041,8 @@ merge(Compressor.prototype, { extract_candidates(expr.expression); } else if (expr instanceof AST_Conditional) { extract_candidates(expr.condition); - extract_candidates(expr.consequent); - extract_candidates(expr.alternative); + extract_candidates(expr.consequent, unused); + extract_candidates(expr.alternative, unused); } else if (expr instanceof AST_Definitions) { expr.definitions.forEach(extract_candidates); } else if (expr instanceof AST_Dot) { @@ -2041,9 +2055,9 @@ merge(Compressor.prototype, { } else if (expr instanceof AST_Exit) { if (expr.value) extract_candidates(expr.value); } else if (expr instanceof AST_For) { - if (expr.init) extract_candidates(expr.init); + if (expr.init) extract_candidates(expr.init, true); if (expr.condition) extract_candidates(expr.condition); - if (expr.step) extract_candidates(expr.step); + if (expr.step) extract_candidates(expr.step, true); if (!(expr.body instanceof AST_Block)) { extract_candidates(expr.body); } @@ -2064,13 +2078,16 @@ merge(Compressor.prototype, { expr.properties.forEach(function(prop) { hit_stack.push(prop); if (prop.key instanceof AST_Node) extract_candidates(prop.key); - if (prop instanceof AST_ObjectKeyVal) extract_candidates(prop.value); + if (prop instanceof AST_ObjectKeyVal) extract_candidates(prop.value, unused); hit_stack.pop(); }); } else if (expr instanceof AST_Sequence) { - expr.expressions.forEach(extract_candidates); + var end = expr.expressions.length - (unused ? 0 : 1); + expr.expressions.forEach(function(node, index) { + extract_candidates(node, index < end); + }); } else if (expr instanceof AST_SimpleStatement) { - extract_candidates(expr.body); + extract_candidates(expr.body, true); } else if (expr instanceof AST_Spread) { extract_candidates(expr.expression); } else if (expr instanceof AST_Sub) { @@ -2269,6 +2286,19 @@ merge(Compressor.prototype, { } function get_lhs(expr) { + if (expr instanceof AST_Assign) { + var def, lhs = expr.left; + if (expr.operator == "=" + && lhs instanceof AST_SymbolRef + && (def = lhs.definition()).references[0] === lhs + && !compressor.exposed(def)) { + var referenced = def.references.length - def.replaced; + if (referenced > 1) mangleable_var(expr.right); + } + return lhs; + } + if (expr instanceof AST_Binary) return expr.right.left; + if (expr instanceof AST_Unary) return expr.expression; if (expr instanceof AST_VarDef) { var def = expr.name.definition(); if (def.const_redefs) return; @@ -2282,19 +2312,18 @@ merge(Compressor.prototype, { if (mangleable_var(expr.value) || referenced == 1 && !compressor.exposed(def)) { return make_node(AST_SymbolRef, expr.name, expr.name); } - } else if (expr instanceof AST_Assign) { - var def, lhs = expr.left; - if (expr.operator == "=" - && lhs instanceof AST_SymbolRef - && (def = lhs.definition()).references[0] === lhs - && !compressor.exposed(def)) { - var referenced = def.references.length - def.replaced; - if (referenced > 1) mangleable_var(expr.right); - } - return lhs; - } else { - return expr.expression; + return; + } + } + + function get_rvalue(expr) { + if (expr instanceof AST_Assign) return expr.right; + if (expr instanceof AST_Binary) { + var node = expr.clone(); + node.right = expr.right.right; + return node; } + if (expr instanceof AST_VarDef) return expr.value; } function invariant(expr) { @@ -5890,6 +5919,10 @@ merge(Compressor.prototype, { }; } + function get_rvalue(expr) { + return expr[expr instanceof AST_Assign ? "right" : "value"]; + } + function insert_statements(body, orig, in_list) { switch (body.length) { case 0: @@ -9651,6 +9684,7 @@ merge(Compressor.prototype, { return strip_assignment(); } } while (parent instanceof AST_Binary && parent.right === node + || parent instanceof AST_Conditional && parent.condition !== node || parent instanceof AST_Sequence && parent.tail_node() === node || parent instanceof AST_UnaryPrefix); } diff --git a/lib/scope.js b/lib/scope.js index 4660c787..036df118 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -48,7 +48,6 @@ function SymbolDef(id, scope, orig, init) { this.global = false; this.id = id; this.init = init; - this.lambda = orig instanceof AST_SymbolLambda; this.mangled_name = null; this.name = orig.name; this.orig = [ orig ]; @@ -352,11 +351,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { } old_def.defun = new_def.scope; old_def.forEach(function(node) { - node.redef = true; + node.redef = old_def; node.thedef = new_def; node.reference(options); }); - if (old_def.lambda) new_def.lambda = true; if (new_def.undeclared) self.variables.set(name, new_def); } }); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index f630883a..258df6f7 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3145,8 +3145,8 @@ issue_2313_2: { var c = 0; !function a() { a && c++; - var a = 0; - a && c++; + var a; + (a = 0) && c++; }(); console.log(c); } @@ -5502,8 +5502,7 @@ collapse_rhs_lhs_2: { expect: { var b = 1; (function f(f) { - f = b; - f[b] = 0; + b[b] = 0; })(); console.log("PASS"); } @@ -5996,7 +5995,7 @@ issue_3215_1: { }()); } expect: { - console.log(typeof 42); + console.log("number"); } expect_stdout: "number" } @@ -8662,3 +8661,47 @@ issue_4430_2: { } expect_stdout: "PASS" } + +collapse_and_assign: { + options = { + collapse_vars: true, + } + input: { + var log = console.log; + var a = { + p: "PASS", + }; + console && (a = a.p); + log(a); + } + expect: { + var log = console.log; + var a = { + p: "PASS", + }; + log(a = console ? a.p : a); + } + expect_stdout: "PASS" +} + +collapse_or_assign: { + options = { + collapse_vars: true, + } + input: { + var log = console.log; + var a = { + p: "PASS", + }; + a.q || (a = a.p); + log(a); + } + expect: { + var log = console.log; + var a = { + p: "PASS", + }; + log(a = !a.q ? a.p : a); + } + expect_stdout: "PASS" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 84e7613a..cfbc6fae 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -725,7 +725,7 @@ issue_2749: { expect: { var a = 2, c = "PASS"; while (a--) - b = void 0, b ? c = "FAIL" : b = 1; + b = void 0, b ? c = "FAIL" : 1; var b; console.log(c); } diff --git a/test/compress/functions.js b/test/compress/functions.js index af99467a..e0a4ee72 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -2175,7 +2175,7 @@ issue_3016_3: { expect: { var b = 1; do { - console.log((a = void 0, a ? "FAIL" : a = "PASS")); + console.log((a = void 0, a ? "FAIL" : "PASS")); } while (b--); var a; } @@ -2208,7 +2208,7 @@ issue_3016_3_ie8: { expect: { var b = 1; do { - console.log((a = void 0, a ? "FAIL" : a = "PASS")); + console.log((a = void 0, a ? "FAIL" : "PASS")); } while (b--); var a; } -- 2.34.1