From f9055df44d86c8537afc34f00bd255792f80422f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 22 Mar 2021 20:59:43 +0000 Subject: [PATCH] support logical assignment operators (#4813) --- lib/compress.js | 88 ++++++++++++++++++++-------------- lib/parse.js | 7 ++- test/compress/assignments.js | 74 ++++++++++++++++++++++++++++ test/compress/collapse_vars.js | 19 +++++++- test/ufuzz/index.js | 12 +++-- 5 files changed, 157 insertions(+), 43 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index ce12da2def..95cccb565a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -821,35 +821,19 @@ merge(Compressor.prototype, { } else if (!(left instanceof AST_Destructured || left instanceof AST_SymbolRef)) { mark_assignment_to_arguments(left); return; - } else if (node.operator == "=") { - node.right.walk(tw); - scan_declaration(tw, compressor, left, function() { - return node.right; - }, function(sym, fixed, walk) { - if (!(sym instanceof AST_SymbolRef)) { - mark_assignment_to_arguments(sym); - walk(); - return; - } - var d = sym.definition(); - d.assignments++; - if (fixed - && !is_modified(compressor, tw, node, node.right, 0) - && !sym.in_arg - && safe_to_assign(tw, d)) { - push_ref(d, sym); - mark(tw, d); - if (d.single_use && left instanceof AST_Destructured) d.single_use = false; - tw.loop_ids[d.id] = tw.in_loop; - mark_escaped(tw, d, sym.scope, node, node.right, 0, 1); - sym.fixed = d.fixed = fixed; - sym.fixed.assigns = [ node ]; - } else { - walk(); - d.fixed = false; - } - }); - } else { + } else switch (node.operator) { + case "=": + walk_assign(); + break; + case "&&=": + case "||=": + case "??=": + left.walk(tw); + push(tw); + walk_assign(); + pop(tw); + break; + default: var d = left.definition(); d.assignments++; var fixed = d.fixed; @@ -900,6 +884,36 @@ merge(Compressor.prototype, { lhs.walk(tw); } } + + function walk_assign() { + node.right.walk(tw); + scan_declaration(tw, compressor, left, function() { + return node.right; + }, function(sym, fixed, walk) { + if (!(sym instanceof AST_SymbolRef)) { + mark_assignment_to_arguments(sym); + walk(); + return; + } + var d = sym.definition(); + d.assignments++; + if (fixed + && !is_modified(compressor, tw, node, node.right, 0) + && !sym.in_arg + && safe_to_assign(tw, d)) { + push_ref(d, sym); + mark(tw, d); + if (d.single_use && left instanceof AST_Destructured) d.single_use = false; + tw.loop_ids[d.id] = tw.in_loop; + mark_escaped(tw, d, sym.scope, node, node.right, 0, 1); + sym.fixed = d.fixed = fixed; + sym.fixed.assigns = [ node ]; + } else { + walk(); + d.fixed = false; + } + }); + } }); def(AST_Binary, function(tw) { if (!lazy_op[this.operator]) return; @@ -1979,7 +1993,9 @@ merge(Compressor.prototype, { function should_stop(node, parent) { if (node === rvalue) return true; - if (parent instanceof AST_For) return node !== parent.init; + if (parent instanceof AST_For) { + if (node !== parent.init) return true; + } if (node instanceof AST_Assign) { return node.operator != "=" && lhs.equivalent_to(node.left); } @@ -2014,7 +2030,8 @@ merge(Compressor.prototype, { } function in_conditional(node, parent) { - if (parent instanceof AST_Binary) return lazy_op[parent.operator] && parent.left !== node; + if (parent instanceof AST_Assign) return parent.left !== node && lazy_op[parent.operator.slice(0, -1)]; + if (parent instanceof AST_Binary) return parent.left !== node && lazy_op[parent.operator]; if (parent instanceof AST_Case) return parent.expression !== node; if (parent instanceof AST_Conditional) return parent.condition !== node; return parent instanceof AST_If && parent.condition !== node; @@ -7211,11 +7228,10 @@ merge(Compressor.prototype, { if (compressor.has_directive("use strict") && expr.is_constant()) return this; } if (left.has_side_effects(compressor)) return this; - this.write_only = true; - if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) { - return this.right.drop_side_effect_free(compressor); - } - return this; + var right = this.right; + this.write_only = !(lazy_op[this.operator.slice(0, -1)] && right.has_side_effects(compressor)); + if (!root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) return this; + return right.drop_side_effect_free(compressor); }); def(AST_Await, function(compressor) { if (!compressor.option("awaits")) return this; diff --git a/lib/parse.js b/lib/parse.js index b5abf0d7d1..c3ae96d995 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -104,12 +104,15 @@ var OPERATORS = makePredicate([ ">>=", "<<=", ">>>=", + "&=", "|=", "^=", - "&=", "&&", "||", "??", + "&&=", + "||=", + "??=", ]); var NEWLINE_CHARS = "\n\r\u2028\u2029"; @@ -653,7 +656,7 @@ var UNARY_PREFIX = makePredicate("typeof void delete -- ++ ! ~ - +"); var UNARY_POSTFIX = makePredicate("-- ++"); -var ASSIGNMENT = makePredicate("= += -= /= *= %= **= >>= <<= >>>= |= ^= &="); +var ASSIGNMENT = makePredicate("= += -= /= *= %= **= >>= <<= >>>= &= |= ^= &&= ||= ??="); var PRECEDENCE = function(a, ret) { for (var i = 0; i < a.length;) { diff --git a/test/compress/assignments.js b/test/compress/assignments.js index 5bc971a506..274a0d631f 100644 --- a/test/compress/assignments.js +++ b/test/compress/assignments.js @@ -475,3 +475,77 @@ issue_4521: { } expect_stdout: "42" } + +logical_assignments: { + input: { + var a = 42, b = null, c; + a &&= "foo"; + b ||= "bar"; + c ??= "baz"; + console.log(a, b, c); + } + expect_exact: 'var a=42,b=null,c;a&&="foo";b||="bar";c??="baz";console.log(a,b,c);' + expect_stdout: "foo bar baz" + node_version: ">=15" +} + +logical_collapse_vars: { + options = { + collapse_vars: true, + } + input: { + var a = "FAIL", b = false; + a = "PASS"; + b ??= a; + console.log(a); + } + expect: { + var a = "FAIL", b = false; + a = "PASS"; + b ??= a; + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=15" +} + +logical_reduce_vars: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = "PASS", b = 42; + b ??= a = "FAIL"; + console.log(a); + } + expect: { + var a = "PASS", b = 42; + b ??= a = "FAIL"; + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=15" +} + +logical_side_effects: { + options = { + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = "PASS", b = 42; + b ??= a = "FAIL"; + console.log(a); + } + expect: { + var a = "PASS", b = 42; + b ??= a = "FAIL"; + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=15" +} diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 8530d387bc..eaff0127cb 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2868,7 +2868,7 @@ lvalues_def: { expect_stdout: true } -compound_assignment: { +compound_assignment_1: { options = { collapse_vars: true, } @@ -2887,6 +2887,23 @@ compound_assignment: { expect_stdout: "4" } +compound_assignment_2: { + options = { + collapse_vars: true, + } + input: { + var a; + a = 1; + for (a += a + 2; console.log(a);); + } + expect: { + var a; + a = 1; + for (a += a + 2; console.log(a);); + } + expect_stdout: "4" +} + issue_2187_1: { options = { collapse_vars: true, diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index d91fe040fa..e2bcdf2ca7 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -149,6 +149,7 @@ var SUPPORT = function(matrix) { for_of: "for (var a of []);", generator: "function* f(){}", let: "let a;", + logical_assignment: "[].p ??= 0;", new_target: "function f() { new.target; }", nullish: "0 ?? 0", rest: "var [...a] = [];", @@ -262,10 +263,13 @@ ASSIGNMENTS = ASSIGNMENTS.concat([ ">>=", ">>>=", ]); -if (SUPPORT.exponentiation) { - ASSIGNMENTS = ASSIGNMENTS.concat(ASSIGNMENTS); - ASSIGNMENTS.push("**="); -} +ASSIGNMENTS = ASSIGNMENTS.concat(ASSIGNMENTS); +if (SUPPORT.exponentiation) ASSIGNMENTS.push("**="); +if (SUPPORT.logical_assignment) ASSIGNMENTS = ASSIGNMENTS.concat([ + "&&=", + "||=", + "??=", +]); var UNARY_SAFE = [ "+",