fix pure_funcs & improve side_effects

- only drops side-effect-free arguments
- drop side-effect-free parts with discarded value from `AST_Seq` & `AST_SimpleStatement`

closes #1494
This commit is contained in:
alexlamsl
2017-02-20 01:46:59 +08:00
parent 8898b8a0fe
commit 26fbeece1c
3 changed files with 516 additions and 15 deletions

View File

@@ -83,6 +83,14 @@ function Compressor(options, false_by_default) {
global_defs : {},
passes : 1,
}, true);
var pure_funcs = this.options["pure_funcs"];
if (typeof pure_funcs == "function") {
this.pure_funcs = pure_funcs;
} else {
this.pure_funcs = pure_funcs ? function(node) {
return pure_funcs.indexOf(node.expression.print_to_string()) < 0;
} : return_true;
}
var top_retain = this.options["top_retain"];
if (top_retain instanceof RegExp) {
this.top_retain = function(def) {
@@ -304,6 +312,13 @@ merge(Compressor.prototype, {
}
}
function is_iife_call(node) {
if (node instanceof AST_Call && !(node instanceof AST_New)) {
return node.expression instanceof AST_Function || is_iife_call(node.expression);
}
return false;
}
function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10;
do {
@@ -1354,10 +1369,12 @@ merge(Compressor.prototype, {
def(AST_This, return_false);
def(AST_Call, function(compressor){
var pure = compressor.option("pure_funcs");
if (!pure) return true;
if (typeof pure == "function") return pure(this);
return pure.indexOf(this.expression.print_to_string()) < 0;
if (compressor.pure_funcs(this)) return true;
for (var i = this.args.length; --i >= 0;) {
if (this.args[i].has_side_effects(compressor))
return true;
}
return false;
});
def(AST_Block, function(compressor){
@@ -1855,12 +1872,151 @@ merge(Compressor.prototype, {
return self;
});
// drop_side_effect_free()
// remove side-effect-free parts which only affects return value
(function(def){
function return_this() {
return this;
}
function return_null() {
return null;
}
// Drop side-effect-free elements from an array of expressions.
// Returns an array of expressions with side-effects or null
// if all elements were dropped. Note: original array may be
// returned if nothing changed.
function trim(nodes, compressor, first_in_statement) {
var ret = [], changed = false;
for (var i = 0, ii = nodes.length; i < ii; i++) {
var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
changed |= node !== nodes[i];
if (node) {
ret.push(node);
first_in_statement = false;
}
}
return changed ? ret.length ? ret : null : nodes;
}
def(AST_Node, return_this);
def(AST_Constant, return_null);
def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){
if (compressor.pure_funcs(this)) return this;
var args = trim(this.args, compressor, first_in_statement);
return args && AST_Seq.from_array(args);
});
def(AST_Function, return_null);
def(AST_Binary, function(compressor, first_in_statement){
var right = this.right.drop_side_effect_free(compressor);
if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
switch (this.operator) {
case "&&":
case "||":
var node = this.clone();
node.right = right;
return node;
default:
var left = this.left.drop_side_effect_free(compressor, first_in_statement);
if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
return make_node(AST_Seq, this, {
car: left,
cdr: right
});
}
});
def(AST_Assign, return_this);
def(AST_Conditional, function(compressor){
var consequent = this.consequent.drop_side_effect_free(compressor);
var alternative = this.alternative.drop_side_effect_free(compressor);
if (consequent === this.consequent && alternative === this.alternative) return this;
if (!consequent) return alternative ? make_node(AST_Binary, this, {
operator: "||",
left: this.condition,
right: alternative
}) : this.condition.drop_side_effect_free(compressor);
if (!alternative) return make_node(AST_Binary, this, {
operator: "&&",
left: this.condition,
right: consequent
});
var node = this.clone();
node.consequent = consequent;
node.alternative = alternative;
return node;
});
def(AST_Unary, function(compressor, first_in_statement){
switch (this.operator) {
case "delete":
case "++":
case "--":
return this;
case "typeof":
if (this.expression instanceof AST_SymbolRef) return null;
default:
if (first_in_statement && is_iife_call(this.expression)) return this;
return this.expression.drop_side_effect_free(compressor, first_in_statement);
}
});
def(AST_SymbolRef, function() {
return this.undeclared() ? this : null;
});
def(AST_Object, function(compressor, first_in_statement){
var values = trim(this.properties, compressor, first_in_statement);
return values && AST_Seq.from_array(values);
});
def(AST_ObjectProperty, function(compressor, first_in_statement){
return this.value.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Array, function(compressor, first_in_statement){
var values = trim(this.elements, compressor, first_in_statement);
return values && AST_Seq.from_array(values);
});
def(AST_Dot, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this;
return this.expression.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Sub, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
var property = this.property.drop_side_effect_free(compressor);
if (!property) return expression;
return make_node(AST_Seq, this, {
car: expression,
cdr: property
});
});
def(AST_Seq, function(compressor){
var cdr = this.cdr.drop_side_effect_free(compressor);
if (cdr === this.cdr) return this;
if (!cdr) return this.car;
return make_node(AST_Seq, this, {
car: this.car,
cdr: cdr
});
});
})(function(node, func){
node.DEFMETHOD("drop_side_effect_free", func);
});
OPT(AST_SimpleStatement, function(self, compressor){
if (compressor.option("side_effects")) {
if (!self.body.has_side_effects(compressor)) {
var body = self.body;
if (!body.has_side_effects(compressor)) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
return make_node(AST_EmptyStatement, self);
}
var node = body.drop_side_effect_free(compressor, true);
if (!node) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
return make_node(AST_EmptyStatement, self);
}
if (node !== body) {
return make_node(AST_SimpleStatement, self, { body: node });
}
}
return self;
});
@@ -2435,13 +2591,6 @@ merge(Compressor.prototype, {
return self.negate(compressor, true);
}
return self;
function is_iife_call(node) {
if (node instanceof AST_Call && !(node instanceof AST_New)) {
return node.expression instanceof AST_Function || is_iife_call(node.expression);
}
return false;
}
});
OPT(AST_New, function(self, compressor){
@@ -2464,9 +2613,8 @@ merge(Compressor.prototype, {
OPT(AST_Seq, function(self, compressor){
if (!compressor.option("side_effects"))
return self;
if (!self.car.has_side_effects(compressor)) {
return maintain_this_binding(compressor.parent(), self, self.cdr);
}
self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects(compressor)) {