diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2021-06-22 17:35:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-23 00:35:50 +0800 |
commit | 7c5b6f349e5818a13ccb93c88b79a607d236bd0b (patch) | |
tree | 007faa7ceb09f56de573d644e7ad47143d058b13 | |
parent | e9c902b0449a291469ddaee04dc745cf4706560d (diff) | |
download | tracifyjs-7c5b6f349e5818a13ccb93c88b79a607d236bd0b.tar.gz tracifyjs-7c5b6f349e5818a13ccb93c88b79a607d236bd0b.zip |
enhance `booleans` (#5022)
closes #5021
-rw-r--r-- | lib/compress.js | 107 | ||||
-rw-r--r-- | test/benchmark.js | 2 | ||||
-rw-r--r-- | test/compress/booleans.js | 377 | ||||
-rw-r--r-- | test/compress/collapse_vars.js | 2 | ||||
-rw-r--r-- | test/compress/conditionals.js | 27 | ||||
-rw-r--r-- | test/compress/join_vars.js | 2 | ||||
-rw-r--r-- | test/compress/nullish.js | 152 |
7 files changed, 639 insertions, 30 deletions
diff --git a/lib/compress.js b/lib/compress.js index 52979c92..0d98e456 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -8223,35 +8223,86 @@ merge(Compressor.prototype, { } } + function fuzzy_eval(compressor, node, nullish) { + if (node.truthy) return true; + if (node.falsy && !nullish) return false; + if (node.is_truthy()) return true; + return node.evaluate(compressor, true); + } + + function mark_duplicate_condition(compressor, node) { + for (var level = 0, child = compressor.self(), parent; ; child = parent) { + parent = compressor.parent(level++); + if (parent instanceof AST_Binary) { + var op = parent.operator; + if (!lazy_op[op]) return; + var left = parent.left; + if (left === child) continue; + if (node.equivalent_to(left)) node[op == "&&" ? "truthy" : "falsy"] = true; + } else if (parent instanceof AST_Conditional) { + var cond = parent.condition; + if (cond === child) continue; + if (node.equivalent_to(cond)) switch (child) { + case parent.consequent: + node.truthy = true; + break; + case parent.alternative: + node.falsy = true; + break; + } + } else if (parent instanceof AST_Exit) { + break; + } else if (parent instanceof AST_If) { + break; + } else if (parent instanceof AST_Sequence) { + if (parent.expressions[0] === child) continue; + } else if (parent instanceof AST_SimpleStatement) { + break; + } + return; + } + while (true) { + child = parent; + parent = compressor.parent(level++); + if (parent instanceof AST_BlockStatement) { + if (parent.body[0] === child) continue; + } else if (parent instanceof AST_If) { + var cond = parent.condition; + if (cond === child) continue; + if (node.equivalent_to(cond)) switch (child) { + case parent.body: + node.truthy = true; + break; + case parent.alternative: + node.falsy = true; + break; + } + } + return; + } + } + OPT(AST_If, function(self, compressor) { if (is_empty(self.alternative)) self.alternative = null; if (!compressor.option("conditionals")) return self; + if (compressor.option("booleans")) mark_duplicate_condition(compressor, self.condition); // if condition can be statically determined, warn and drop // one of the blocks. note, statically determined implies // “has no side effects”; also it doesn't work for cases like // `x && true`, though it probably should. if (compressor.option("dead_code")) { - var cond = self.condition.evaluate(compressor); - if (cond instanceof AST_Node) { - cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true); - } + var cond = fuzzy_eval(compressor, self.condition); if (!cond) { AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start); - var body = [ - make_node(AST_SimpleStatement, self.condition, { - body: self.condition - }), - ]; + var body = [ make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ]; extract_declarations_from_unreachable_code(compressor, self.body, body); if (self.alternative) body.push(self.alternative); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } else if (!(cond instanceof AST_Node)) { AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start); var body = [ - make_node(AST_SimpleStatement, self.condition, { - body: self.condition - }), + make_node(AST_SimpleStatement, self.condition, { body: self.condition }), self.body, ]; if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body); @@ -8275,9 +8326,7 @@ merge(Compressor.prototype, { var body_exprs = sequencesize(self.body, body, var_defs, refs); var alt_exprs = sequencesize(self.alternative, body, var_defs, refs); if (body_exprs && alt_exprs) { - if (var_defs.length > 0) body.push(make_node(AST_Var, self, { - definitions: var_defs - })); + if (var_defs.length > 0) body.push(make_node(AST_Var, self, { definitions: var_defs })); if (body_exprs.length == 0) { body.push(make_node(AST_SimpleStatement, self.condition, { body: alt_exprs.length > 0 ? make_node(AST_Binary, self, { @@ -10221,7 +10270,18 @@ merge(Compressor.prototype, { } break; } - var in_bool = compressor.option("booleans") && compressor.in_boolean_context(); + var in_bool = false; + var parent = compressor.parent(); + if (compressor.option("booleans")) { + var lhs = self.left; + if (lazy_op[self.operator] && !lhs.has_side_effects(compressor)) { + if (lhs.equivalent_to(self.right)) { + return maintain_this_binding(compressor, parent, compressor.self(), lhs).optimize(compressor); + } + mark_duplicate_condition(compressor, lhs); + } + in_bool = compressor.in_boolean_context(); + } if (in_bool) switch (self.operator) { case "+": var ll = self.left.evaluate(compressor); @@ -10255,7 +10315,6 @@ merge(Compressor.prototype, { } break; } - var parent = compressor.parent(); if (compressor.option("comparisons") && self.is_boolean(compressor)) { if (!(parent instanceof AST_Binary) || parent instanceof AST_Assign) { var negated = best_of(compressor, self, make_node(AST_UnaryPrefix, self, { @@ -10313,7 +10372,7 @@ merge(Compressor.prototype, { var associative = true; switch (self.operator) { case "&&": - var ll = fuzzy_eval(self.left); + var ll = fuzzy_eval(compressor, self.left); if (!ll) { AST_Node.warn("Condition left of && always false [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor, parent, compressor.self(), self.left).optimize(compressor); @@ -10349,7 +10408,7 @@ merge(Compressor.prototype, { case "??": var nullish = true; case "||": - var ll = fuzzy_eval(self.left, nullish); + var ll = fuzzy_eval(compressor, self.left, nullish); if (nullish ? ll == null : !ll) { AST_Node.warn("Condition left of {operator} always {value} [{file}:{line},{col}]", { operator: self.operator, @@ -10732,13 +10791,6 @@ merge(Compressor.prototype, { }); } - function fuzzy_eval(node, nullish) { - if (node.truthy) return true; - if (node.falsy && !nullish) return false; - if (node.is_truthy()) return true; - return node.evaluate(compressor, true); - } - function is_indexFn(node) { return node.TYPE == "Call" && node.expression instanceof AST_Dot @@ -11286,7 +11338,8 @@ merge(Compressor.prototype, { return make_sequence(self, expressions); } if (!compressor.option("conditionals")) return self; - var condition = self.condition.is_truthy() || self.condition.evaluate(compressor, true); + if (compressor.option("booleans")) mark_duplicate_condition(compressor, self.condition); + var condition = fuzzy_eval(compressor, self.condition); if (!condition) { AST_Node.warn("Condition always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.condition, self.alternative ]).optimize(compressor); diff --git a/test/benchmark.js b/test/benchmark.js index 74ac84be..5bc7a5ce 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -16,7 +16,7 @@ var urls = [ "https://code.angularjs.org/1.7.8/angular.js", "https://unpkg.com/mathjs@6.2.3/dist/math.js", "https://unpkg.com/react@15.3.2/dist/react.js", - "https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.js", + "https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.js", "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js", "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js", "https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.12.2/ember.prod.js", diff --git a/test/compress/booleans.js b/test/compress/booleans.js index 779f8d8c..cd860f96 100644 --- a/test/compress/booleans.js +++ b/test/compress/booleans.js @@ -29,6 +29,383 @@ iife_boolean_context: { ] } +de_morgan_1a: { + options = { + booleans: true, + } + input: { + function f(a) { + return a || a; + } + console.log(f(null), f(42)); + } + expect: { + function f(a) { + return a; + } + console.log(f(null), f(42)); + } + expect_stdout: "null 42" +} + +de_morgan_1b: { + options = { + booleans: true, + } + input: { + function f(a) { + return a && a; + } + console.log(f(null), f(42)); + } + expect: { + function f(a) { + return a; + } + console.log(f(null), f(42)); + } + expect_stdout: "null 42" +} + +de_morgan_1c: { + options = { + booleans: true, + } + input: { + console.log(delete (NaN && NaN)); + } + expect: { + console.log(delete (0, NaN)); + } + expect_stdout: "true" +} + +de_morgan_2a: { + options = { + booleans: true, + conditionals: true, + } + input: { + function f(a, b) { + return a || (a || b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a || b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "undefined {}", + "42 42", + ] +} + +de_morgan_2b: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b) { + return a || (a && b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "42 42", + ] +} + +de_morgan_2c: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b) { + return a && (a || b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "42 42", + ] +} + +de_morgan_2d: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b) { + return a && (a && b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a && b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "undefined {}", + ] +} + +de_morgan_3a: { + options = { + booleans: true, + conditionals: true, + } + input: { + function f(a, b, c) { + return a || ((a || b) || c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a || b || c; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "undefined {} true true", + "42 42 42 42", + ] +} + +de_morgan_3b: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b, c) { + return a || ((a || b) && c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a || b && c; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "false false undefined {}", + "42 42 42 42", + ] +} + +de_morgan_3c: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b, c) { + return a || ((a && b) || c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a || c; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "undefined {} undefined {}", + "42 42 42 42", + ] +} + +de_morgan_3d: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b, c) { + return a || ((a && b) && c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "null null null null", + "42 42 42 42", + ] +} + +de_morgan_3e: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b, c) { + return a && ((a || b) || c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "null null null null", + "42 42 42 42", + ] +} + +de_morgan_3f: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b, c) { + return a && ((a || b) && c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a && c; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "null null null null", + "undefined {} undefined {}", + ] +} + +de_morgan_3g: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b, c) { + return a && ((a && b) || c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a && (b || c); + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "null null null null", + "undefined {} true true", + ] +} + +de_morgan_3h: { + options = { + booleans: true, + conditionals: true, + } + input: { + function f(a, b, c) { + return a && ((a && b) && c); + } + console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {})); + console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {})); + } + expect: { + function f(a, b, c) { + return a && b && c; + } + console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {})); + console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {})); + } + expect_stdout: [ + "null null null null", + "false false undefined {}", + ] +} + +conditional_chain: { + options = { + booleans: true, + conditionals: true, + evaluate: true, + } + input: { + function f(a, b) { + return a ? a : b ? b : 42; + } + console.log(f("PASS", "FAIL")); + } + expect: { + function f(a, b) { + return a || b || 42; + } + console.log(f("PASS", "FAIL")); + } + expect_stdout: "PASS" +} + issue_3465_1: { options = { booleans: true, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 30dc0248..50db51b0 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -8864,7 +8864,7 @@ issue_4732_1: { expect: { var a = 0; (function(b) { - (b = a++) && (b && console.log("PASS")); + (b = a++) && console.log("PASS"); })(a++); } expect_stdout: "PASS" diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 4187c152..84559c32 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -822,6 +822,33 @@ cond_13: { } } +cond_14: { + options = { + booleans: true, + conditionals: true, + side_effects: true, + } + input: { + function f(a) { + if (a) + if (a) + console.log("PASS"); + else + console.log("FAIL"); + } + f(null); + f(42); + } + expect: { + function f(a) { + a && console.log("PASS"); + } + f(null); + f(42); + } + expect_stdout: "PASS" +} + ternary_boolean_consequent: { options = { booleans: true, diff --git a/test/compress/join_vars.js b/test/compress/join_vars.js index 89251266..65e18f88 100644 --- a/test/compress/join_vars.js +++ b/test/compress/join_vars.js @@ -1015,7 +1015,7 @@ issue_3856: { console.log(function() { (function() { var a, b; - if (a) return !!a; + if (a) return a, 1; for (a = 0; !console;); return 0; })(); diff --git a/test/compress/nullish.js b/test/compress/nullish.js index a8526cac..27f6aa5a 100644 --- a/test/compress/nullish.js +++ b/test/compress/nullish.js @@ -110,6 +110,158 @@ conditional_assignment_4: { node_version: ">=14" } +de_morgan_1: { + options = { + booleans: true, + } + input: { + function f(a) { + return a ?? a; + } + console.log(f(null), f(42)); + } + expect: { + function f(a) { + return a; + } + console.log(f(null), f(42)); + } + expect_stdout: "null 42" + node_version: ">=14" +} + +de_morgan_2a: { + options = { + booleans: true, + conditionals: true, + evaluate: true, + } + input: { + function f(a, b) { + return a || (a ?? b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a || (a ?? b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "undefined {}", + "42 42", + ] + node_version: ">=14" +} + +de_morgan_2b: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b) { + return a && (a ?? b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "42 42", + ] + node_version: ">=14" +} + +de_morgan_2c: { + options = { + booleans: true, + evaluate: true, + side_effects: true, + } + input: { + function f(a, b) { + return a ?? (a || b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a ?? b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "undefined {}", + "42 42", + ] + node_version: ">=14" +} + +de_morgan_2d: { + options = { + booleans: true, + evaluate: true, + } + input: { + function f(a, b) { + return a ?? (a && b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "42 42", + ] + node_version: ">=14" +} + +de_morgan_2e: { + options = { + booleans: true, + conditionals: true, + } + input: { + function f(a, b) { + return a ?? (a ?? b); + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a ?? b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "undefined {}", + "42 42", + ] + node_version: ">=14" +} + issue_4679: { options = { comparisons: true, |