enhance booleans (#5365)

This commit is contained in:
Alex Lam S.L
2022-02-21 03:02:19 +00:00
committed by GitHub
parent d5afe16bc8
commit dd3b81dec6
6 changed files with 289 additions and 169 deletions

View File

@@ -2085,33 +2085,40 @@ TreeWalker.prototype = {
}
},
in_boolean_context: function() {
var self = this.self();
for (var i = 0, p; p = this.parent(i); i++) {
if (p instanceof AST_Conditional && p.condition === self
|| p instanceof AST_DWLoop && p.condition === self
|| p instanceof AST_For && p.condition === self
|| p instanceof AST_If && p.condition === self
|| p instanceof AST_Return && p.in_bool
|| p instanceof AST_Sequence && p.tail_node() !== self
|| p instanceof AST_SimpleStatement
|| p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
return true;
}
if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")
|| p instanceof AST_Conditional
|| p.tail_node() === self) {
self = p;
} else if (p instanceof AST_Return) {
for (var call, fn = p; call = this.parent(++i); fn = call) {
if (call.TYPE == "Call") {
if (!(fn instanceof AST_Lambda) || fn.name) return false;
} else if (fn instanceof AST_Lambda) {
for (var drop = true, level = 0, parent, self = this.self(); parent = this.parent(level++); self = parent) {
if (parent instanceof AST_Binary) switch (parent.operator) {
case "&&":
case "||":
if (parent.left === self) drop = false;
continue;
default:
return false;
}
if (parent instanceof AST_Conditional) {
if (parent.condition === self) return true;
continue;
}
} else {
if (parent instanceof AST_DWLoop) return parent.condition === self;
if (parent instanceof AST_For) return parent.condition === self;
if (parent instanceof AST_If) return parent.condition === self;
if (parent instanceof AST_Return) {
if (parent.in_bool) return true;
while (parent = this.parent(level++)) {
if (parent instanceof AST_Lambda) {
if (parent.name) return false;
parent = this.parent(level++);
if (parent.TYPE != "Call") return false;
break;
}
}
}
if (parent instanceof AST_Sequence) {
if (parent.tail_node() === self) continue;
return drop ? "d" : true;
}
if (parent instanceof AST_SimpleStatement) return drop ? "d" : true;
if (parent instanceof AST_UnaryPrefix) return parent.operator == "!";
return false;
}
}
}
};

View File

@@ -189,7 +189,7 @@ Compressor.prototype = new TreeTransformer(function(node, descend, in_list) {
if (is_scope) {
node.hoist_properties(this);
node.hoist_declarations(this);
node.process_boolean_returns(this);
node.process_returns(this);
}
// Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
// would call AST_Node.transform() if a different instance of AST_Node is
@@ -475,7 +475,8 @@ Compressor.prototype.compress = function(node) {
function reset_def(tw, compressor, def) {
def.assignments = 0;
def.bool_fn = 0;
def.bool_return = 0;
def.drop_return = 0;
def.cross_loop = false;
def.direct_access = false;
def.escaped = [];
@@ -1057,12 +1058,11 @@ Compressor.prototype.compress = function(node) {
if (iife) delete exp.reduce_vars;
return true;
}
if (node.TYPE == "Call" && tw.in_boolean_context()) {
if (exp instanceof AST_SymbolRef) {
exp.definition().bool_fn++;
} else if (exp instanceof AST_Assign && exp.operator == "=" && exp.left instanceof AST_SymbolRef) {
exp.left.definition().bool_fn++;
}
if (node.TYPE == "Call") switch (tw.in_boolean_context()) {
case "d":
var drop = true;
case true:
mark_refs(exp, drop);
}
exp.walk(tw);
var optional = node.optional;
@@ -1078,6 +1078,25 @@ Compressor.prototype.compress = function(node) {
tw.find_parent(AST_Scope).may_call_this();
}
return true;
function mark_refs(node, drop) {
if (node instanceof AST_Assign) {
if (node.operator != "=") return;
mark_refs(node.left, drop);
mark_refs(node.right, drop);
} else if (node instanceof AST_Binary) {
if (!lazy_op[node.operator]) return;
mark_refs(node.left, drop);
mark_refs(node.right, drop);
} else if (node instanceof AST_Conditional) {
mark_refs(node.consequent, drop);
mark_refs(node.alternative, drop);
} else if (node instanceof AST_SymbolRef) {
var def = node.definition();
def.bool_return++;
if (drop) def.drop_return++;
}
}
});
def(AST_Class, function(tw, descend, compressor) {
var node = this;
@@ -7923,7 +7942,7 @@ Compressor.prototype.compress = function(node) {
}));
}
function map_bool_returns(fn) {
function map_self_returns(fn) {
var map = Object.create(null);
scan_local_returns(fn, function(node) {
var value = node.value;
@@ -7936,9 +7955,14 @@ Compressor.prototype.compress = function(node) {
return map;
}
function all_bool(def, bool_returns, compressor) {
return def.bool_fn + (bool_returns[def.id] || 0) === def.references.length - def.replaced
&& !compressor.exposed(def);
function can_trim_returns(def, self_returns, compressor) {
if (compressor.exposed(def)) return false;
switch (def.references.length - def.replaced - (self_returns[def.id] || 0)) {
case def.drop_return:
return "d";
case def.bool_return:
return true;
}
}
function process_boolean_returns(fn, compressor) {
@@ -7964,39 +7988,74 @@ Compressor.prototype.compress = function(node) {
});
}
AST_Scope.DEFMETHOD("process_boolean_returns", noop);
AST_Defun.DEFMETHOD("process_boolean_returns", function(compressor) {
AST_Scope.DEFMETHOD("process_returns", noop);
AST_Defun.DEFMETHOD("process_returns", function(compressor) {
if (!compressor.option("booleans")) return;
var bool_returns = map_bool_returns(this);
if (!all_bool(this.name.definition(), bool_returns, compressor)) return;
if (compressor.parent() instanceof AST_ExportDefault) return;
switch (can_trim_returns(this.name.definition(), map_self_returns(this), compressor)) {
case "d":
drop_returns(compressor, this, true);
break;
case true:
process_boolean_returns(this, compressor);
break;
}
});
AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) {
AST_Function.DEFMETHOD("process_returns", function(compressor) {
if (!compressor.option("booleans")) return;
var bool_returns = map_bool_returns(this);
if (this.name && !all_bool(this.name.definition(), bool_returns, compressor)) return;
var drop = true;
var self_returns = map_self_returns(this);
if (this.name && !can_trim(this.name.definition())) return;
var parent = compressor.parent();
if (parent instanceof AST_Assign) {
if (parent.operator != "=") return;
var sym = parent.left;
if (!(sym instanceof AST_SymbolRef)) return;
if (!all_bool(sym.definition(), bool_returns, compressor)) return;
if (!can_trim(sym.definition())) return;
} else if (parent instanceof AST_Call && parent.expression !== this) {
var exp = parent.expression;
if (exp instanceof AST_SymbolRef) exp = exp.fixed_value();
if (!(exp instanceof AST_Lambda)) return;
if (exp.uses_arguments || exp.pinned()) return;
var sym = exp.argnames[parent.args.indexOf(this)];
var args = parent.args, sym;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (arg === this) {
sym = exp.argnames[i];
if (!sym && exp.rest) return;
break;
}
if (arg instanceof AST_Spread) return;
}
if (sym instanceof AST_DefaultValue) sym = sym.name;
if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return;
if (sym instanceof AST_SymbolFunarg && !can_trim(sym.definition())) return;
} else if (parent.TYPE == "Call") {
compressor.pop();
var in_bool = compressor.in_boolean_context();
compressor.push(this);
if (!in_bool) return;
switch (in_bool) {
case true:
drop = false;
case "d":
break;
default:
return;
}
} else return;
if (drop) {
drop_returns(compressor, this, true);
} else {
process_boolean_returns(this, compressor);
}
function can_trim(def) {
switch (can_trim_returns(def, self_returns, compressor)) {
case true:
drop = false;
case "d":
return true;
}
}
});
AST_BlockScope.DEFMETHOD("var_names", function() {
@@ -8155,6 +8214,118 @@ Compressor.prototype.compress = function(node) {
});
}
function drop_returns(compressor, exp, ignore_name) {
if (!(exp instanceof AST_Lambda)) return;
var arrow = is_arrow(exp);
var async = is_async(exp);
var changed = false;
var drop_body = false;
if (arrow && compressor.option("arrows")) {
if (!exp.value) {
drop_body = true;
} else if (!async || is_primitive(compressor, exp.value)) {
var dropped = exp.value.drop_side_effect_free(compressor);
if (dropped !== exp.value) {
changed = true;
exp.value = dropped;
}
}
} else if (!is_generator(exp)) {
if (!ignore_name && exp.name) {
var def = exp.name.definition();
drop_body = def.references.length == def.replaced;
} else {
drop_body = true;
}
}
if (drop_body) {
exp.process_expression(false, function(node) {
var value = node.value;
if (value) {
if (async && !is_primitive(compressor, value)) return node;
value = value.drop_side_effect_free(compressor, true);
}
changed = true;
if (!value) return make_node(AST_EmptyStatement, node);
return make_node(AST_SimpleStatement, node, { body: value });
});
scan_local_returns(exp, function(node) {
var value = node.value;
if (value) {
if (async && !is_primitive(compressor, value)) return;
var dropped = value.drop_side_effect_free(compressor);
if (dropped !== value) {
changed = true;
node.value = dropped;
}
}
});
}
if (async && compressor.option("awaits")) {
if (drop_body) exp.process_expression("awaits", function(node) {
var body = node.body;
if (body instanceof AST_Await) {
if (is_primitive(compressor, body.expression)) {
changed = true;
body = body.expression.drop_side_effect_free(compressor, true);
if (!body) return make_node(AST_EmptyStatement, node);
node.body = body;
}
} else if (body instanceof AST_Sequence) {
var exprs = body.expressions;
for (var i = exprs.length; --i >= 0;) {
var tail = exprs[i];
if (!(tail instanceof AST_Await)) break;
var value = tail.expression;
if (!is_primitive(compressor, value)) break;
changed = true;
if (exprs[i] = value.drop_side_effect_free(compressor)) break;
}
switch (i) {
case -1:
return make_node(AST_EmptyStatement, node);
case 0:
node.body = exprs[0];
break;
default:
exprs.length = i + 1;
break;
}
}
return node;
});
var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value);
var tw = new TreeWalker(function(node) {
if (abort) return true;
if (tw.parent() === exp && node.may_throw(compressor)) return abort = true;
if (node instanceof AST_Await) return abort = true;
if (node instanceof AST_ForAwaitOf) return abort = true;
if (node instanceof AST_Return) {
if (node.value && !is_primitive(compressor, node.value)) return abort = true;
return;
}
if (node instanceof AST_Scope && node !== exp) return true;
});
exp.walk(tw);
if (!abort) {
var ctor;
switch (exp.CTOR) {
case AST_AsyncArrow:
ctor = AST_Arrow;
break;
case AST_AsyncFunction:
ctor = AST_Function;
break;
case AST_AsyncGeneratorFunction:
ctor = AST_GeneratorFunction;
break;
}
return make_node(ctor, exp, exp);
}
}
return changed && exp.clone();
}
// drop_side_effect_free()
// remove side-effect-free parts which only affects return value
(function(def) {
@@ -8261,116 +8432,6 @@ Compressor.prototype.compress = function(node) {
if (!rhs) return lhs;
return make_sequence(this, [ lhs, rhs ]);
});
function drop_returns(compressor, exp) {
var arrow = is_arrow(exp);
var async = is_async(exp);
var changed = false;
var drop_body = false;
if (arrow && compressor.option("arrows")) {
if (!exp.value) {
drop_body = true;
} else if (!async || is_primitive(compressor, exp.value)) {
var dropped = exp.value.drop_side_effect_free(compressor);
if (dropped !== exp.value) {
changed = true;
exp.value = dropped;
}
}
} else if (exp instanceof AST_AsyncFunction || exp instanceof AST_Function) {
if (exp.name) {
var def = exp.name.definition();
drop_body = def.references.length == def.replaced;
} else {
drop_body = true;
}
}
if (drop_body) {
exp.process_expression(false, function(node) {
var value = node.value;
if (value) {
if (async && !is_primitive(compressor, value)) return node;
value = value.drop_side_effect_free(compressor, true);
}
changed = true;
if (!value) return make_node(AST_EmptyStatement, node);
return make_node(AST_SimpleStatement, node, { body: value });
});
scan_local_returns(exp, function(node) {
var value = node.value;
if (value) {
if (async && !is_primitive(compressor, value)) return;
var dropped = value.drop_side_effect_free(compressor);
if (dropped !== value) {
changed = true;
node.value = dropped;
}
}
});
}
if (async && compressor.option("awaits")) {
if (drop_body) exp.process_expression("awaits", function(node) {
var body = node.body;
if (body instanceof AST_Await) {
if (is_primitive(compressor, body.expression)) {
changed = true;
body = body.expression.drop_side_effect_free(compressor, true);
if (!body) return make_node(AST_EmptyStatement, node);
node.body = body;
}
} else if (body instanceof AST_Sequence) {
var exprs = body.expressions;
for (var i = exprs.length; --i >= 0;) {
var tail = exprs[i];
if (!(tail instanceof AST_Await)) break;
var value = tail.expression;
if (!is_primitive(compressor, value)) break;
changed = true;
if (exprs[i] = value.drop_side_effect_free(compressor)) break;
}
switch (i) {
case -1:
return make_node(AST_EmptyStatement, node);
case 0:
node.body = exprs[0];
break;
default:
exprs.length = i + 1;
break;
}
}
return node;
});
var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value);
var tw = new TreeWalker(function(node) {
if (abort) return true;
if (tw.parent() === exp && node.may_throw(compressor)) return abort = true;
if (node instanceof AST_Await) return abort = true;
if (node instanceof AST_ForAwaitOf) return abort = true;
if (node instanceof AST_Return) {
if (node.value && !is_primitive(compressor, node.value)) return abort = true;
return;
}
if (node instanceof AST_Scope && node !== exp) return true;
});
exp.walk(tw);
if (!abort) {
var ctor;
switch (exp.CTOR) {
case AST_AsyncArrow:
ctor = AST_Arrow;
break;
case AST_AsyncFunction:
ctor = AST_Function;
break;
case AST_AsyncGeneratorFunction:
ctor = AST_GeneratorFunction;
break;
}
return make_node(ctor, exp, exp);
}
}
return changed && exp.clone();
}
function assign_this_only(fn, compressor) {
fn.new = true;
var result = all(fn.body, function(stat) {

View File

@@ -448,6 +448,23 @@ concat_truthy: {
]
}
process_returns: {
options = {
booleans: true,
}
input: {
(function() {
return 42;
})() && console.log("PASS");
}
expect: {
(function() {
return 42;
})() && console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3465_1: {
options = {
booleans: true,

View File

@@ -692,7 +692,7 @@ funarg_inline: {
node_version: ">=6"
}
process_boolean_returns: {
process_returns: {
options = {
booleans: true,
}
@@ -706,9 +706,7 @@ process_boolean_returns: {
expect: {
console.log(function({ length }) {
return length ? "FAIL" : "PASS";
}(function() {
return 42;
}));
}(function() {}));
}
expect_stdout: "PASS"
node_version: ">=6"

View File

@@ -6341,7 +6341,7 @@ issue_4612_4: {
expect: {
console.log(function() {
function f() {
return h();
h();
}
function g() {
return h();

View File

@@ -1144,7 +1144,7 @@ conditional_assignments_3: {
expect_stdout: "PASS"
}
issue_3856: {
issue_3856_1: {
options = {
booleans: true,
conditionals: true,
@@ -1169,9 +1169,46 @@ issue_3856: {
console.log(function() {
(function() {
var a, b;
if (a) return a, 1;
for (a = 0; !console;);
if (a) a;
else {
a = 0;
for (; !console;);
}
})();
}());
}
expect_stdout: "undefined"
}
issue_3856_2: {
options = {
booleans: true,
conditionals: true,
if_return: true,
join_vars: true,
passes: 2,
sequences: true,
side_effects: true,
}
input: {
console.log(function() {
(function() {
var a;
if (!a) {
a = 0;
for (var b; !console;);
return 0;
}
if (a) return 1;
})();
}());
}
expect: {
console.log(function() {
(function() {
var a, b;
if (!a)
for (a = 0; !console;);
})();
}());
}