var fn = this;
fn.inlined = false;
var iife;
- if (!fn.name && (iife = tw.parent()) instanceof AST_Call && iife.expression === fn) {
+ if (!fn.name
+ && (iife = tw.parent()) instanceof AST_Call
+ && iife.expression === fn
+ && all(iife.args, function(arg) {
+ return !(arg instanceof AST_Spread);
+ })) {
var hit = false;
var aborts = false;
fn.walk(new TreeWalker(function(node) {
if (node instanceof AST_PropAccess) {
return side_effects || !value_def && node.expression.may_throw_on_access(compressor);
}
+ if (node instanceof AST_Spread) return true;
if (node instanceof AST_SymbolRef) {
if (symbol_in_lvalues(node, parent)) {
return !(parent instanceof AST_Assign && parent.operator == "=" && parent.left === node);
&& !fn.uses_arguments
&& !fn.pinned()
&& (iife = compressor.parent()) instanceof AST_Call
- && iife.expression === fn) {
+ && iife.expression === fn
+ && all(iife.args, function(arg) {
+ return !(arg instanceof AST_Spread);
+ })) {
var fn_strict = compressor.has_directive("use strict");
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
var len = fn.argnames.length;
expr.expressions.forEach(extract_candidates);
} else if (expr instanceof AST_SimpleStatement) {
extract_candidates(expr.body);
+ } else if (expr instanceof AST_Spread) {
+ extract_candidates(expr.expression);
} else if (expr instanceof AST_Sub) {
extract_candidates(expr.expression);
extract_candidates(expr.property);
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
}
if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1);
+ if (parent instanceof AST_Spread) return node;
if (parent instanceof AST_Switch) return node;
if (parent instanceof AST_Unary) return node;
if (parent instanceof AST_VarDef) return node;
}
if (parent instanceof AST_Sequence) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1);
+ if (parent instanceof AST_Spread) return node;
if (parent instanceof AST_Switch) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_VarDef) {
// determine if expression has side effects
(function(def) {
- function any(list, compressor) {
- for (var i = list.length; --i >= 0;)
- if (list[i].has_side_effects(compressor))
- return true;
- return false;
+ function any(list, compressor, spread) {
+ return !all(list, spread ? function(node) {
+ return node instanceof AST_Spread ? !spread(node, compressor) : !node.has_side_effects(compressor);
+ } : function(node) {
+ return !node.has_side_effects(compressor);
+ });
+ }
+ function array_spread(node, compressor) {
+ return !node.expression.is_string(compressor) || node.expression.has_side_effects(compressor);
}
def(AST_Node, return_true);
def(AST_Array, function(compressor) {
- return any(this.elements, compressor);
+ return any(this.elements, compressor, array_spread);
});
def(AST_Assign, function(compressor) {
var lhs = this.left;
&& (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) {
return true;
}
- return any(this.args, compressor);
+ return any(this.args, compressor, array_spread);
});
def(AST_Case, function(compressor) {
return this.expression.has_side_effects(compressor)
});
def(AST_Lambda, return_false);
def(AST_Object, function(compressor) {
- return any(this.properties, compressor);
+ return any(this.properties, compressor, function(node, compressor) {
+ var exp = node.expression;
+ if (exp instanceof AST_Object) return true;
+ if (exp instanceof AST_PropAccess) return true;
+ if (exp instanceof AST_SymbolRef) {
+ exp = exp.fixed_value();
+ if (!exp) return true;
+ if (exp instanceof AST_SymbolRef) return true;
+ if (exp instanceof AST_PropAccess) return true;
+ if (!(exp instanceof AST_Object)) return false;
+ return !all(exp.properties, function(prop) {
+ return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread);
+ });
+ }
+ return exp.has_side_effects(compressor);
+ });
});
def(AST_ObjectProperty, function(compressor) {
return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
|| this.value.has_side_effects(compressor);
});
- def(AST_Sub, function(compressor) {
- return this.expression.may_throw_on_access(compressor)
- || this.expression.has_side_effects(compressor)
- || this.property.has_side_effects(compressor);
- });
def(AST_Sequence, function(compressor) {
return any(this.expressions, compressor);
});
def(AST_SimpleStatement, function(compressor) {
return this.body.has_side_effects(compressor);
});
+ def(AST_Sub, function(compressor) {
+ return this.expression.may_throw_on_access(compressor)
+ || this.expression.has_side_effects(compressor)
+ || this.property.has_side_effects(compressor);
+ });
def(AST_Switch, function(compressor) {
return this.expression.has_side_effects(compressor)
|| any(this.body, compressor);
if (!all(self.argnames, function(argname) {
return argname instanceof AST_SymbolFunarg;
})) break;
+ if (!all(call.args, function(arg) {
+ return !(arg instanceof AST_Spread);
+ })) break;
for (var j = 0; j < len; j++) {
var arg = call.args[j];
if (!(arg instanceof AST_SymbolRef)) break;
// 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) {
+ function trim(nodes, compressor, first_in_statement, spread) {
var len = nodes.length;
if (!len) return null;
var ret = [], changed = false;
for (var i = 0; i < len; i++) {
- var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
- changed |= node !== nodes[i];
- if (node) {
- ret.push(node);
+ var node = nodes[i];
+ var trimmed;
+ if (spread && node instanceof AST_Spread) {
+ trimmed = spread(node, compressor, first_in_statement);
+ } else {
+ trimmed = node.drop_side_effect_free(compressor, first_in_statement);
+ }
+ if (trimmed !== node) changed = true;
+ if (trimmed) {
+ ret.push(trimmed);
first_in_statement = false;
}
}
return changed ? ret.length ? ret : null : nodes;
}
-
+ function array_spread(node, compressor, first_in_statement) {
+ var exp = node.expression;
+ if (!exp.is_string(compressor)) return node;
+ return exp.drop_side_effect_free(compressor, first_in_statement);
+ }
def(AST_Node, return_this);
def(AST_Accessor, return_null);
def(AST_Array, function(compressor, first_in_statement) {
- var values = trim(this.elements, compressor, first_in_statement);
- return values && make_sequence(this, values);
+ var values = trim(this.elements, compressor, first_in_statement, array_spread);
+ if (!values) return null;
+ if (all(values, function(node) {
+ return !(node instanceof AST_Spread);
+ })) return make_sequence(this, values);
+ if (values === this.elements) return this;
+ var node = this.clone();
+ node.elements = values;
+ return node;
});
def(AST_Assign, function(compressor) {
var left = this.left;
var self = this;
if (self.is_expr_pure(compressor)) {
if (self.pure) AST_Node.warn("Dropping __PURE__ call [{file}:{line},{col}]", self.start);
- var args = trim(self.args, compressor, first_in_statement);
+ var args = trim(self.args, compressor, first_in_statement, array_spread);
return args && make_sequence(self, args);
}
var exp = self.expression;
if (self.is_call_pure(compressor)) {
var exprs = self.args.slice();
exprs.unshift(exp.expression);
- exprs = trim(exprs, compressor, first_in_statement);
+ exprs = trim(exprs, compressor, first_in_statement, array_spread);
return exprs && make_sequence(self, exprs);
}
var def;
if (assign_this_only) {
var exprs = self.args.slice();
exprs.unshift(exp);
- exprs = trim(exprs, compressor, first_in_statement);
+ exprs = trim(exprs, compressor, first_in_statement, array_spread);
return exprs && make_sequence(self, exprs);
}
if (!fn.contains_this()) return make_node(AST_Call, self, self);
def(AST_Object, function(compressor, first_in_statement) {
var exprs = [];
this.properties.forEach(function(prop) {
- if (prop.key instanceof AST_Node) exprs.push(prop.key);
- exprs.push(prop.value);
+ if (prop instanceof AST_Spread) {
+ exprs.push(prop);
+ } else {
+ if (prop.key instanceof AST_Node) exprs.push(prop.key);
+ exprs.push(prop.value);
+ }
+ });
+ var values = trim(exprs, compressor, first_in_statement, function(node, compressor, first_in_statement) {
+ var exp = node.expression;
+ if (exp instanceof AST_Object) return node;
+ if (exp instanceof AST_PropAccess) return node;
+ if (exp instanceof AST_SymbolRef) {
+ exp = exp.fixed_value();
+ if (!exp) return node;
+ if (exp instanceof AST_SymbolRef) return node;
+ if (exp instanceof AST_PropAccess) return node;
+ if (!(exp instanceof AST_Object)) return null;
+ return all(exp.properties, function(prop) {
+ return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread);
+ }) ? null : node;
+ }
+ return exp.drop_side_effect_free(compressor, first_in_statement);
});
- var values = trim(exprs, compressor, first_in_statement);
- return values && make_sequence(this, values);
+ if (!values) return null;
+ if (values === exprs && !all(values, function(node) {
+ return !(node instanceof AST_Spread);
+ })) return this;
+ return make_sequence(this, values.map(function(node) {
+ return node instanceof AST_Spread ? make_node(AST_Object, node, {
+ properties: [ node ],
+ }) : node;
+ }));
});
def(AST_Sequence, function(compressor, first_in_statement) {
var expressions = trim(this.expressions, compressor, first_in_statement);
if (fn.pinned()) return;
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
var args = call.args;
+ if (!all(args, function(arg) {
+ return !(arg instanceof AST_Spread);
+ })) return;
var pos = 0, last = 0;
var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) {
if (!argname) return true;
&& !self.is_expr_pure(compressor)
&& all(fn.argnames, function(argname) {
return !(argname instanceof AST_Destructured);
+ })
+ && all(self.args, function(arg) {
+ return !(arg instanceof AST_Spread);
});
if (can_inline && stat instanceof AST_Return) {
var value = stat.value;
return !(argname instanceof AST_Destructured);
})
&& (fn !== exp || fn_name_unused(fn, compressor))) {
- var args = self.args.concat(make_node(AST_Undefined, self));
+ var args = self.args.map(function(arg) {
+ return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
+ elements: [ arg ],
+ }) : arg;
+ });
+ args.push(make_node(AST_Undefined, self));
return make_sequence(self, args).optimize(compressor);
}
}
});
});
+ OPT(AST_Spread, function(self, compressor) {
+ if (compressor.option("properties")) {
+ var exp = self.expression;
+ if (compressor.parent() instanceof AST_Object) {
+ if (exp instanceof AST_Object && all(exp.properties, function(node) {
+ return node instanceof AST_ObjectKeyVal;
+ })) return List.splice(exp.properties);
+ } else if (exp instanceof AST_Array) return List.splice(exp.elements.map(function(node) {
+ return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node;
+ }));
+ }
+ return self;
+ });
+
function safe_to_flatten(value, compressor) {
if (value instanceof AST_SymbolRef) {
value = value.fixed_value();
prop = self.property = sub.property;
}
}
- if (compressor.option("properties") && compressor.option("side_effects")
- && prop instanceof AST_Number && expr instanceof AST_Array) {
+ var elements;
+ if (compressor.option("properties")
+ && compressor.option("side_effects")
+ && prop instanceof AST_Number
+ && expr instanceof AST_Array
+ && all(elements = expr.elements, function(value) {
+ return !(value instanceof AST_Spread);
+ })) {
var index = prop.value;
- var elements = expr.elements;
var retValue = elements[index];
if (safe_to_flatten(retValue, compressor)) {
var is_hole = retValue instanceof AST_Hole;
}
function createArgs(recurmax, stmtDepth, canThrow) {
+ recurmax--;
var args = [];
- for (var n = rng(4); --n >= 0;) {
- args.push(rng(2) ? createValue() : createExpression(recurmax - 1, COMMA_OK, stmtDepth, canThrow));
+ for (var n = rng(4); --n >= 0;) switch (rng(50)) {
+ case 0:
+ case 1:
+ var name = getVarName();
+ if (canThrow && rng(8) === 0) {
+ args.push("..." + name);
+ } else {
+ args.push('...("" + ' + name + ")");
+ }
+ break;
+ case 2:
+ args.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
+ break;
+ default:
+ args.push(rng(2) ? createValue() : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
+ break;
}
return args.join(", ");
}
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
- var arr = "[";
- for (var i = rng(6); --i >= 0;) {
+ var arr = [];
+ for (var i = rng(6); --i >= 0;) switch (rng(50)) {
+ case 0:
+ case 1:
// in rare cases produce an array hole element
- var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
- arr += element + ", ";
+ arr.push("");
+ break;
+ case 2:
+ case 3:
+ var name = getVarName();
+ if (canThrow && rng(8) === 0) {
+ arr.push("..." + name);
+ } else {
+ arr.push('...("" + ' + name + ")");
+ }
+ break;
+ case 4:
+ arr.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
+ break;
+ default:
+ arr.push(createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
+ break;
}
- return arr + "]";
+ return "[" + arr.join(", ") + "]";
}
var SAFE_KEYS = [
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var obj = ["({"];
- for (var i = rng(6); --i >= 0;) switch (rng(30)) {
+ for (var i = rng(6); --i >= 0;) switch (rng(50)) {
case 0:
- obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
- break;
case 1:
obj.push(getVarName() + ",");
break;
+ case 2:
+ obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
+ break;
+ case 3:
+ obj.push("..." + getVarName() + ",");
+ break;
+ case 4:
+ obj.push("..." + createObjectLiteral(recurmax, stmtDepth, canThrow) + ",");
+ break;
default:
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
break;
} else if (result.name == "TypeError" && /'in'/.test(result.message)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("invalid `in`");' + orig.slice(index);
+ } else if (result.name == "TypeError" && /not iterable/.test(result.message)) {
+ index = result.ufuzz_catch;
+ return orig.slice(0, index) + result.ufuzz_var + ' = new Error("spread not iterable");' + orig.slice(index);
} else if (result.name == "RangeError" && result.message == "Maximum call stack size exceeded") {
index = result.ufuzz_try;
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
}
// ignore difference in error message caused by `in`
+ // ignore difference in error message caused by spread syntax
// ignore difference in depth of termination caused by infinite recursion
if (!ok) {
var orig_skipped = patch_try_catch(original_code, toplevel);