enhance conditionals (#5593)

This commit is contained in:
Alex Lam S.L
2022-08-02 12:01:57 +01:00
committed by GitHub
parent e39f33e41b
commit 672cdfa57a
2 changed files with 491 additions and 84 deletions

View File

@@ -1749,7 +1749,7 @@ Compressor.prototype.compress = function(node) {
}
}
function needs_unbinding(compressor, val) {
function needs_unbinding(val) {
return val instanceof AST_PropAccess
|| is_undeclared_ref(val) && val.name == "eval";
}
@@ -1757,12 +1757,12 @@ Compressor.prototype.compress = function(node) {
// we shouldn't compress (1,func)(something) to
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
function maintain_this_binding(compressor, parent, orig, val) {
function maintain_this_binding(parent, orig, val) {
var wrap = false;
if (parent.TYPE == "Call") {
wrap = parent.expression === orig && needs_unbinding(compressor, val);
wrap = parent.expression === orig && needs_unbinding(val);
} else if (parent instanceof AST_Template) {
wrap = parent.tag === orig && needs_unbinding(compressor, val);
wrap = parent.tag === orig && needs_unbinding(val);
} else if (parent instanceof AST_UnaryPrefix) {
wrap = parent.operator == "delete"
|| parent.operator == "typeof" && is_undeclared_ref(val);
@@ -2154,7 +2154,7 @@ Compressor.prototype.compress = function(node) {
var def = candidate.name.definition();
if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
def.replaced++;
return maintain_this_binding(compressor, parent, node, rvalue);
return maintain_this_binding(parent, node, rvalue);
}
return make_node(AST_Assign, candidate, {
operator: "=",
@@ -3340,7 +3340,7 @@ Compressor.prototype.compress = function(node) {
function patch_sequence(node, tt) {
if (node instanceof AST_Sequence) switch (node.expressions.length) {
case 0: return null;
case 1: return maintain_this_binding(compressor, tt.parent(), node, node.expressions[0]);
case 1: return maintain_this_binding(tt.parent(), node, node.expressions[0]);
}
}
@@ -7110,7 +7110,7 @@ Compressor.prototype.compress = function(node) {
case 0:
return List.skip;
case 1:
return maintain_this_binding(compressor, parent, node, props[0].transform(tt));
return maintain_this_binding(parent, node, props[0].transform(tt));
default:
return make_sequence(node, props.map(function(prop) {
return prop.transform(tt);
@@ -7599,7 +7599,7 @@ Compressor.prototype.compress = function(node) {
}
if (node instanceof AST_Sequence) {
if (node.expressions.length > 1) return;
return maintain_this_binding(compressor, tt.parent(), node, node.expressions[0]);
return maintain_this_binding(tt.parent(), node, node.expressions[0]);
}
});
tt.push(compressor.parent());
@@ -10316,7 +10316,7 @@ Compressor.prototype.compress = function(node) {
seq.expressions.push(call);
return seq.optimize(compressor);
}
} else if (!needs_unbinding(compressor, exp.tail_node())) {
} else if (!needs_unbinding(exp.tail_node())) {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
@@ -10549,7 +10549,7 @@ Compressor.prototype.compress = function(node) {
&& is_undeclared_ref(exp.expression)
&& exp.expression.name == "Object") {
var call = self.clone();
call.expression = maintain_this_binding(compressor, self, exp, exp.args[0]);
call.expression = maintain_this_binding(self, exp, exp.args[0]);
return call.optimize(compressor);
}
}
@@ -10673,7 +10673,7 @@ Compressor.prototype.compress = function(node) {
if (!arg) return make_node(AST_Undefined, self);
args[index] = null;
var parent = this.parent();
return parent ? maintain_this_binding(compressor, parent, node, arg) : arg;
return parent ? maintain_this_binding(parent, node, arg) : arg;
}
}));
var save_inlined = fn.inlined;
@@ -10690,7 +10690,7 @@ Compressor.prototype.compress = function(node) {
exprs.push(retValue);
var node = make_sequence(self, exprs).optimize(compressor);
fn.inlined = save_inlined;
node = maintain_this_binding(compressor, parent, current, node);
node = maintain_this_binding(parent, current, node);
if (replacing || best_of_expression(node, self) === node) {
refs.forEach(function(ref) {
ref.scope = exp === fn ? fn.parent_scope : exp.scope;
@@ -10714,7 +10714,7 @@ Compressor.prototype.compress = function(node) {
fn._squeezed = true;
if (exp !== fn) fn.parent_scope = exp.scope;
var node = make_sequence(self, flatten_fn()).optimize(compressor);
return maintain_this_binding(compressor, parent, current, node);
return maintain_this_binding(parent, current, node);
}
}
if (compressor.option("side_effects")
@@ -11244,7 +11244,7 @@ Compressor.prototype.compress = function(node) {
merge_assignments();
trim_right_for_undefined();
if (end == 0) {
self = maintain_this_binding(compressor, compressor.parent(), compressor.self(), expressions[0]);
self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
return self;
}
@@ -11674,7 +11674,7 @@ Compressor.prototype.compress = function(node) {
var lhs = self.left;
if (lazy_op[self.operator] && !lhs.has_side_effects(compressor)) {
if (lhs.equals(self.right)) {
return maintain_this_binding(compressor, parent, compressor.self(), lhs).optimize(compressor);
return maintain_this_binding(parent, compressor.self(), lhs).optimize(compressor);
}
mark_duplicate_condition(compressor, lhs);
}
@@ -11770,7 +11770,7 @@ Compressor.prototype.compress = function(node) {
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);
return maintain_this_binding(parent, compressor.self(), self.left).optimize(compressor);
} else if (!(ll instanceof AST_Node)) {
AST_Node.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
@@ -11816,7 +11816,7 @@ Compressor.prototype.compress = function(node) {
line: self.start.line,
col: self.start.col,
});
return maintain_this_binding(compressor, parent, compressor.self(), self.left).optimize(compressor);
return maintain_this_binding(parent, compressor.self(), self.left).optimize(compressor);
}
var rr;
if (!nullish && (rr = self.right.evaluate(compressor, true)) && !(rr instanceof AST_Node)) {
@@ -12781,16 +12781,17 @@ Compressor.prototype.compress = function(node) {
operator: self.operator.slice(0, -1),
left: self.left,
right: self.right,
}) : maintain_this_binding(compressor, compressor.parent(), self, self.right)).optimize(compressor);
}) : maintain_this_binding(compressor.parent(), self, self.right)).optimize(compressor);
}
});
OPT(AST_Conditional, function(self, compressor) {
if (compressor.option("sequences") && self.condition instanceof AST_Sequence) {
var expressions = self.condition.expressions.slice();
self.condition = expressions.pop();
expressions.push(self);
return make_sequence(self, expressions);
var node = self.clone();
node.condition = expressions.pop();
expressions.push(node);
return make_sequence(self, expressions).optimize(compressor);
}
if (!compressor.option("conditionals")) return self;
var condition = self.condition;
@@ -12854,16 +12855,27 @@ Compressor.prototype.compress = function(node) {
right: make_node(AST_Conditional, self, {
condition: condition,
consequent: pop_lhs(consequent),
alternative: pop_lhs(alternative)
})
alternative: pop_lhs(alternative),
}),
});
}
}
var alt_tail = alternative.tail_node();
// x ? y : y ---> x, y
if (consequent.equals(alternative)) return make_sequence(self, [
condition,
consequent
]).optimize(compressor);
// x ? (a, c) : (b, c) ---> x ? a : b, c
if (seq_tail.equals(alt_tail)) {
return make_sequence(self, consequent.equals(alternative) ? [
condition,
consequent,
] : [
make_node(AST_Conditional, self, {
condition: condition,
consequent: pop_seq(consequent),
alternative: pop_seq(alternative),
}),
seq_tail,
]).optimize(compressor);
}
// x ? y.p : z.p ---> (x ? y : z).p
// x ? y(a) : z(a) ---> (x ? y : z)(a)
// x ? y.f(a) : z.f(a) ---> (x ? y : z).f(a)
@@ -12893,123 +12905,111 @@ Compressor.prototype.compress = function(node) {
return node;
}
// x ? (y ? a : b) : b ---> x && y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.alternative.equals(alternative)) {
if (seq_tail instanceof AST_Conditional
&& seq_tail.alternative.equals(alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: condition,
operator: "&&",
right: consequent.condition
right: fuse(consequent, seq_tail, "condition"),
}),
consequent: consequent.consequent,
alternative: alternative
consequent: seq_tail.consequent,
alternative: alternative,
});
}
// x ? (y ? a : b) : a ---> !x || y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.consequent.equals(alternative)) {
if (seq_tail instanceof AST_Conditional
&& seq_tail.consequent.equals(alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: negated,
operator: "||",
right: consequent.condition
right: fuse(consequent, seq_tail, "condition"),
}),
consequent: alternative,
alternative: consequent.alternative
alternative: seq_tail.alternative,
});
}
// x ? a : (y ? a : b) ---> x || y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equals(alternative.consequent)) {
if (alt_tail instanceof AST_Conditional
&& consequent.equals(alt_tail.consequent)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: condition,
operator: "||",
right: alternative.condition
right: fuse(alternative, alt_tail, "condition"),
}),
consequent: consequent,
alternative: alternative.alternative
alternative: alt_tail.alternative,
});
}
// x ? b : (y ? a : b) ---> !x && y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equals(alternative.alternative)) {
if (alt_tail instanceof AST_Conditional
&& consequent.equals(alt_tail.alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: negated,
operator: "&&",
right: alternative.condition
right: fuse(alternative, alt_tail, "condition"),
}),
consequent: alternative.consequent,
alternative: consequent
consequent: alt_tail.consequent,
alternative: consequent,
});
}
// x ? (a, c) : (b, c) ---> x ? a : b, c
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
&& consequent.tail_node().equals(alternative.tail_node())) {
return make_sequence(self, [
make_node(AST_Conditional, self, {
condition: condition,
consequent: pop_seq(consequent),
alternative: pop_seq(alternative)
}),
consequent.tail_node()
]).optimize(compressor);
}
// x ? y && a : a ---> (!x || y) && a
if (consequent instanceof AST_Binary
&& consequent.operator == "&&"
&& consequent.right.equals(alternative)) {
if (seq_tail instanceof AST_Binary
&& seq_tail.operator == "&&"
&& seq_tail.right.equals(alternative)) {
return make_node(AST_Binary, self, {
operator: "&&",
left: make_node(AST_Binary, self, {
operator: "||",
left: negated,
right: consequent.left
right: fuse(consequent, seq_tail, "left"),
}),
right: alternative
right: alternative,
}).optimize(compressor);
}
// x ? y || a : a ---> x && y || a
if (consequent instanceof AST_Binary
&& consequent.operator == "||"
&& consequent.right.equals(alternative)) {
if (seq_tail instanceof AST_Binary
&& seq_tail.operator == "||"
&& seq_tail.right.equals(alternative)) {
return make_node(AST_Binary, self, {
operator: "||",
left: make_node(AST_Binary, self, {
operator: "&&",
left: condition,
right: consequent.left
right: fuse(consequent, seq_tail, "left"),
}),
right: alternative
right: alternative,
}).optimize(compressor);
}
// x ? a : y && a ---> (x || y) && a
if (alternative instanceof AST_Binary
&& alternative.operator == "&&"
&& alternative.right.equals(consequent)) {
if (alt_tail instanceof AST_Binary
&& alt_tail.operator == "&&"
&& alt_tail.right.equals(consequent)) {
return make_node(AST_Binary, self, {
operator: "&&",
left: make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative.left
right: fuse(alternative, alt_tail, "left"),
}),
right: consequent
right: consequent,
}).optimize(compressor);
}
// x ? a : y || a ---> !x && y || a
if (alternative instanceof AST_Binary
&& alternative.operator == "||"
&& alternative.right.equals(consequent)) {
if (alt_tail instanceof AST_Binary
&& alt_tail.operator == "||"
&& alt_tail.right.equals(consequent)) {
return make_node(AST_Binary, self, {
operator: "||",
left: make_node(AST_Binary, self, {
operator: "&&",
left: negated,
right: alternative.left
right: fuse(alternative, alt_tail, "left"),
}),
right: consequent
right: consequent,
}).optimize(compressor);
}
var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
@@ -13022,7 +13022,7 @@ Compressor.prototype.compress = function(node) {
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition),
right: alternative
right: alternative,
});
}
if (is_false(consequent)) {
@@ -13034,7 +13034,7 @@ Compressor.prototype.compress = function(node) {
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition.negate(compressor)),
right: alternative
right: alternative,
});
}
if (is_true(alternative)) {
@@ -13042,7 +13042,7 @@ Compressor.prototype.compress = function(node) {
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition.negate(compressor)),
right: consequent
right: consequent,
});
}
if (is_false(alternative)) {
@@ -13050,7 +13050,7 @@ Compressor.prototype.compress = function(node) {
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition),
right: consequent
right: consequent,
});
}
if (compressor.option("typeofs")) mark_locally_defined(condition, consequent, alternative);
@@ -13108,6 +13108,13 @@ Compressor.prototype.compress = function(node) {
return -1;
}
function fuse(node, tail, prop) {
if (node === tail) return tail[prop];
var exprs = node.expressions.slice(0, -1);
exprs.push(tail[prop]);
return make_sequence(node, exprs);
}
function is_tail_equivalent(consequent, alternative) {
if (consequent.TYPE != alternative.TYPE) return;
if (consequent.optional != alternative.optional) return;
@@ -13126,13 +13133,21 @@ Compressor.prototype.compress = function(node) {
}
function combine_tail(consequent, alternative, top) {
if (!is_tail_equivalent(consequent, alternative)) return !top && make_node(AST_Conditional, self, {
var seq_tail = consequent.tail_node();
var alt_tail = alternative.tail_node();
if (!is_tail_equivalent(seq_tail, alt_tail)) return !top && make_node(AST_Conditional, self, {
condition: condition,
consequent: consequent,
alternative: alternative,
});
var node = consequent.clone();
node.expression = combine_tail(consequent.expression, alternative.expression);
var node = seq_tail.clone();
var seq_expr = fuse(consequent, seq_tail, "expression");
var alt_expr = fuse(alternative, alt_tail, "expression");
var combined = combine_tail(seq_expr, alt_expr);
if (seq_tail.expression instanceof AST_Sequence) {
combined = maintain_this_binding(seq_tail, seq_tail.expression, combined);
}
node.expression = combined;
return node;
}

View File

@@ -1433,6 +1433,398 @@ condition_matches_alternative: {
expect_stdout: "null 0 false 5"
}
condition_sequence_1: {
options = {
conditionals: true,
sequences: true,
}
input: {
function f(x, y) {
return (console.log(x), x) ? x : y;
}
console.log(f("foo", "bar"));
console.log(f(null, "baz"));
console.log(f(42));
console.log(f());
}
expect: {
function f(x, y) {
return console.log(x), x || y;
}
console.log(f("foo", "bar")),
console.log(f(null, "baz")),
console.log(f(42)),
console.log(f());
}
expect_stdout: [
"foo",
"foo",
"null",
"baz",
"42",
"42",
"undefined",
"undefined",
]
}
condition_sequence_2: {
options = {
conditionals: true,
sequences: true,
}
input: {
function f(x, y) {
return (console.log(y), y) ? x : y;
}
console.log(f("foo", "bar"));
console.log(f(null, "baz"));
console.log(f(42));
console.log(f());
}
expect: {
function f(x, y) {
return console.log(y), y && x;
}
console.log(f("foo", "bar")),
console.log(f(null, "baz")),
console.log(f(42)),
console.log(f());
}
expect_stdout: [
"bar",
"foo",
"baz",
"null",
"undefined",
"undefined",
"undefined",
"undefined",
]
}
combine_tail_sequence: {
options = {
conditionals: true,
}
input: {
var n = {
f: function() {
console.log("foo");
return this.p;
},
p: "FAIL 1",
};
var o = {
f: function() {
console.log("foz");
return this.p;
},
p: "FAIL 2",
};
var p = "PASS";
function g(a) {
return a
? (console.log("baa"), (console.log("bar"), (console.log("baz"), n).f)())
: (console.log("moo"), (console.log("mor"), (console.log("moz"), o).f)());
}
console.log(g());
console.log(g(42));
}
expect: {
var n = {
f: function() {
console.log("foo");
return this.p;
},
p: "FAIL 1",
};
var o = {
f: function() {
console.log("foz");
return this.p;
},
p: "FAIL 2",
};
var p = "PASS";
function g(a) {
return (0, (a
? (console.log("baa"), console.log("bar"), console.log("baz"), n)
: (console.log("moo"), console.log("mor"), console.log("moz"), o)).f)();
}
console.log(g());
console.log(g(42));
}
expect_stdout: [
"moo",
"mor",
"moz",
"foz",
"PASS",
"baa",
"bar",
"baz",
"foo",
"PASS",
]
}
consequent_sequence_1: {
options = {
conditionals: true,
}
input: {
function f(x, y, a) {
return x ? (console.log("seq"), y && a) : a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect: {
function f(x, y, a) {
return (!x || (console.log("seq"), y)) && a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect_stdout: [
"1",
"2",
"seq",
"false",
"seq",
"4",
]
}
consequent_sequence_2: {
options = {
conditionals: true,
}
input: {
function f(x, y, a) {
return x ? (console.log("seq"), y || a) : a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect: {
function f(x, y, a) {
return x && (console.log("seq"), y) || a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect_stdout: [
"1",
"2",
"seq",
"3",
"seq",
"true",
]
}
consequent_sequence_3: {
options = {
conditionals: true,
}
input: {
function f(x, y, a, b) {
return x ? (console.log("seq"), y ? a : b) : b;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect: {
function f(x, y, a, b) {
return x && (console.log("seq"), y) ? a : b;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect_stdout: [
"-1",
"-2",
"seq",
"-3",
"seq",
"4",
]
}
consequent_sequence_4: {
options = {
conditionals: true,
}
input: {
function f(x, y, a, b) {
return x ? (console.log("seq"), y ? a : b) : a;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect: {
function f(x, y, a, b) {
return !x || (console.log("seq"), y) ? a : b;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect_stdout: [
"1",
"2",
"seq",
"-3",
"seq",
"4",
]
}
alternative_sequence_1: {
options = {
conditionals: true,
}
input: {
function f(x, y, a) {
return x ? a : (console.log("seq"), y && a);
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect: {
function f(x, y, a) {
return (x || (console.log("seq"), y)) && a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect_stdout: [
"seq",
"false",
"seq",
"2",
"3",
"4",
]
}
alternative_sequence_2: {
options = {
conditionals: true,
}
input: {
function f(x, y, a) {
return x ? a : (console.log("seq"), y || a);
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect: {
function f(x, y, a) {
return !x && (console.log("seq"), y) || a;
}
console.log(f(false, false, 1));
console.log(f(false, true, 2));
console.log(f(true, false, 3));
console.log(f(true, true, 4));
}
expect_stdout: [
"seq",
"1",
"seq",
"true",
"3",
"4",
]
}
alternative_sequence_3: {
options = {
conditionals: true,
}
input: {
function f(x, y, a, b) {
return x ? a : (console.log("seq"), y ? a : b);
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect: {
function f(x, y, a, b) {
return x || (console.log("seq"), y) ? a : b;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect_stdout: [
"seq",
"-1",
"seq",
"2",
"3",
"4",
]
}
alternative_sequence_4: {
options = {
conditionals: true,
}
input: {
function f(x, y, a, b) {
return x ? b : (console.log("seq"), y ? a : b);
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect: {
function f(x, y, a, b) {
return !x && (console.log("seq"), y) ? a : b;
}
console.log(f(false, false, 1, -1));
console.log(f(false, true, 2, -2));
console.log(f(true, false, 3, -3));
console.log(f(true, true, 4, -4));
}
expect_stdout: [
"seq",
"-1",
"seq",
"2",
"-3",
"-4",
]
}
delete_conditional_1: {
options = {
booleans: true,