diff --git a/lib/compress.js b/lib/compress.js index 85cc6ddf3d..5328b517e0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -418,29 +418,27 @@ merge(Compressor.prototype, { } } - function mark_escaped(tw, d, scope, node, value, level) { + function mark_escaped(tw, d, scope, node, value, level, depth) { var parent = tw.parent(level); - if (value) { - if (value.is_constant()) return; - if (level > 0 && value.is_constant_expression(scope)) return; - } + if (value && value.is_constant()) return; if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right || parent instanceof AST_Call && node !== parent.expression || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope || parent instanceof AST_VarDef && node === parent.value) { - d.escaped = true; + if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1; + if (!d.escaped || d.escaped > depth) d.escaped = depth; return; } else if (parent instanceof AST_Array || parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Conditional && node !== parent.condition || parent instanceof AST_Sequence && node === parent.tail_node()) { - mark_escaped(tw, d, scope, parent, parent, level + 1); + mark_escaped(tw, d, scope, parent, parent, level + 1, depth); } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { var obj = tw.parent(level + 1); - mark_escaped(tw, d, scope, obj, obj, level + 2); + mark_escaped(tw, d, scope, obj, obj, level + 2, depth); } else if (parent instanceof AST_PropAccess && node === parent.expression) { value = read_property(value, parent.property); - mark_escaped(tw, d, scope, parent, value, level + 1); + mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1); if (value) return; } if (level == 0) d.direct_access = true; @@ -637,7 +635,7 @@ merge(Compressor.prototype, { } } } - mark_escaped(tw, d, this.scope, this, value, 0); + mark_escaped(tw, d, this.scope, this, value, 0, 1); }); def(AST_Toplevel, function(tw, descend, compressor) { this.globals.each(function(def) { @@ -1909,7 +1907,7 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - var val = this._eval(compressor); + var val = this._eval(compressor, 1); return !val || val instanceof RegExp || typeof val != "object" ? val : this; }); var unaryPrefix = makePredicate("! ~ - + void"); @@ -1928,22 +1926,17 @@ merge(Compressor.prototype, { throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); def(AST_Lambda, return_this); - function ev(node, compressor) { - if (!compressor) throw new Error("Compressor must be passed"); - - return node._eval(compressor); - }; def(AST_Node, return_this); def(AST_Constant, function(){ return this.getValue(); }); - def(AST_Array, function(compressor){ + def(AST_Array, function(compressor, depth) { if (compressor.option("unsafe")) { var elements = []; for (var i = 0, len = this.elements.length; i < len; i++) { var element = this.elements[i]; if (element instanceof AST_Function) continue; - var value = ev(element, compressor); + var value = element._eval(compressor, depth); if (element === value) return this; elements.push(value); } @@ -1951,7 +1944,7 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Object, function(compressor){ + def(AST_Object, function(compressor, depth) { if (compressor.option("unsafe")) { var val = {}; for (var i = 0, len = this.properties.length; i < len; i++) { @@ -1960,21 +1953,21 @@ merge(Compressor.prototype, { if (key instanceof AST_Symbol) { key = key.name; } else if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { return this; } if (prop.value instanceof AST_Function) continue; - val[key] = ev(prop.value, compressor); + val[key] = prop.value._eval(compressor, depth); if (val[key] === prop.value) return this; } return val; } return this; }); - def(AST_UnaryPrefix, function(compressor){ + def(AST_UnaryPrefix, function(compressor, depth) { var e = this.expression; // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. @@ -1985,7 +1978,7 @@ merge(Compressor.prototype, { && e.fixed_value() instanceof AST_Lambda)) { return typeof function(){}; } - e = ev(e, compressor); + e = e._eval(compressor, depth); if (e === this.expression) return this; switch (this.operator) { case "!": return !e; @@ -2001,10 +1994,10 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Binary, function(compressor){ - var left = ev(this.left, compressor); + def(AST_Binary, function(compressor, depth) { + var left = this.left._eval(compressor, depth); if (left === this.left) return this; - var right = ev(this.right, compressor); + var right = this.right._eval(compressor, depth); if (right === this.right) return this; var result; switch (this.operator) { @@ -2038,30 +2031,32 @@ merge(Compressor.prototype, { } return result; }); - def(AST_Conditional, function(compressor){ - var condition = ev(this.condition, compressor); + def(AST_Conditional, function(compressor, depth) { + var condition = this.condition._eval(compressor, depth); if (condition === this.condition) return this; var node = condition ? this.consequent : this.alternative; - var value = ev(node, compressor); + var value = node._eval(compressor, depth); return value === node ? this : value; }); - def(AST_SymbolRef, function(compressor){ + def(AST_SymbolRef, function(compressor, depth) { var fixed = this.fixed_value(); if (!fixed) return this; - this._eval = return_this; - var value = ev(fixed, compressor); - if (value === fixed) { + var value; + if (HOP(fixed, "_eval")) { + value = fixed._eval(); + } else { + this._eval = return_this; + value = fixed._eval(compressor, depth); delete this._eval; - return this; + if (value === fixed) return this; + fixed._eval = function() { + return value; + }; } - if (!HOP(fixed, "_eval")) fixed._eval = function() { - return value; - }; - if (value && typeof value == "object" && this.definition().escaped) { - delete this._eval; - return this; + if (value && typeof value == "object") { + var escaped = this.definition().escaped; + if (escaped && depth > escaped) return this; } - this._eval = fixed._eval; return value; }); var global_objs = { @@ -2095,11 +2090,11 @@ merge(Compressor.prototype, { ], }; convert_to_predicate(static_values); - def(AST_PropAccess, function(compressor){ + def(AST_PropAccess, function(compressor, depth) { if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === this.property) return this; } var exp = this.expression; @@ -2108,7 +2103,7 @@ merge(Compressor.prototype, { if (!(static_values[exp.name] || return_false)(key)) return this; val = global_objs[exp.name]; } else { - val = ev(exp, compressor); + val = exp._eval(compressor, depth + 1); if (!val || val === exp || !HOP(val, key)) return this; } return val[key]; @@ -2186,12 +2181,12 @@ merge(Compressor.prototype, { ], }; convert_to_predicate(static_fns); - def(AST_Call, function(compressor){ + def(AST_Call, function(compressor, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { var key = exp.property; if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === exp.property) return this; } var val; @@ -2200,13 +2195,13 @@ merge(Compressor.prototype, { if (!(static_fns[e.name] || return_false)(key)) return this; val = global_objs[e.name]; } else { - val = ev(e, compressor); + val = e._eval(compressor, depth + 1); if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this; } var args = []; for (var i = 0, len = this.args.length; i < len; i++) { var arg = this.args[i]; - var value = ev(arg, compressor); + var value = arg._eval(compressor, depth); if (arg === value) return this; args.push(value); } @@ -3083,7 +3078,7 @@ merge(Compressor.prototype, { if (node instanceof AST_VarDef) { var sym = node.name, def, value; if (sym.scope === self - && !(def = sym.definition()).escaped + && (def = sym.definition()).escaped != 1 && !def.single_use && !def.direct_access && !top_retain(def) @@ -4698,7 +4693,7 @@ merge(Compressor.prototype, { if (d.single_use && fixed instanceof AST_Function) { if (d.scope !== self.scope && (!compressor.option("reduce_funcs") - || d.escaped + || d.escaped == 1 || fixed.inlined)) { d.single_use = false; } else if (recursive_ref(compressor, d)) { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index bf1155b723..2b2c77da00 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -3422,6 +3422,37 @@ escaped_prop_1: { } escaped_prop_2: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + passes: 2, + pure_getters: "strict", + reduce_funcs: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var obj = { o: { a: 1 } }; + (function(o) { + o.a++; + })(obj.o); + (function(o) { + console.log(o.a); + })(obj.o); + } + expect: { + var obj = { o: { a: 1 } }; + obj.o.a++; + console.log(obj.o.a); + } + expect_stdout: "2" +} + +escaped_prop_3: { options = { reduce_funcs: true, reduce_vars: true,