retrofit AST_BlockStatement as block-scoped (#4177)
This commit is contained in:
61
lib/ast.js
61
lib/ast.js
@@ -251,9 +251,40 @@ var AST_Block = DEFNODE("Block", "body", {
|
|||||||
},
|
},
|
||||||
}, AST_Statement);
|
}, AST_Statement);
|
||||||
|
|
||||||
|
var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_scope variables", {
|
||||||
|
$documentation: "Base class for all statements introducing a lexical scope",
|
||||||
|
$propdoc: {
|
||||||
|
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
|
||||||
|
functions: "[Object/S] like `variables`, but only lists function declarations",
|
||||||
|
parent_scope: "[AST_Scope?/S] link to the parent scope",
|
||||||
|
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
|
||||||
|
},
|
||||||
|
clone: function(deep) {
|
||||||
|
var node = this._clone(deep);
|
||||||
|
if (this.enclosed) node.enclosed = this.enclosed.slice();
|
||||||
|
if (this.functions) node.functions = this.functions.clone();
|
||||||
|
if (this.variables) node.variables = this.variables.clone();
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
pinned: function() {
|
||||||
|
return this.resolve().pinned();
|
||||||
|
},
|
||||||
|
resolve: function() {
|
||||||
|
return this.parent_scope.resolve();
|
||||||
|
},
|
||||||
|
_validate: function() {
|
||||||
|
if (this.parent_scope == null) return;
|
||||||
|
if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope");
|
||||||
|
if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope");
|
||||||
|
},
|
||||||
|
}, AST_Block);
|
||||||
|
|
||||||
var AST_BlockStatement = DEFNODE("BlockStatement", null, {
|
var AST_BlockStatement = DEFNODE("BlockStatement", null, {
|
||||||
$documentation: "A block statement",
|
$documentation: "A block statement",
|
||||||
}, AST_Block);
|
initialize: function() {
|
||||||
|
this.variables = new Dictionary();
|
||||||
|
},
|
||||||
|
}, AST_BlockScope);
|
||||||
|
|
||||||
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
|
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
|
||||||
$documentation: "The empty statement (empty block or simply a semicolon)"
|
$documentation: "The empty statement (empty block or simply a semicolon)"
|
||||||
@@ -412,34 +443,6 @@ var AST_With = DEFNODE("With", "expression", {
|
|||||||
|
|
||||||
/* -----[ scope and functions ]----- */
|
/* -----[ scope and functions ]----- */
|
||||||
|
|
||||||
var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_scope variables", {
|
|
||||||
$documentation: "Base class for all statements introducing a lexical scope",
|
|
||||||
$propdoc: {
|
|
||||||
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
|
|
||||||
functions: "[Object/S] like `variables`, but only lists function declarations",
|
|
||||||
parent_scope: "[AST_Scope?/S] link to the parent scope",
|
|
||||||
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
|
|
||||||
},
|
|
||||||
clone: function(deep) {
|
|
||||||
var node = this._clone(deep);
|
|
||||||
if (this.enclosed) node.enclosed = this.enclosed.slice();
|
|
||||||
if (this.functions) node.functions = this.functions.clone();
|
|
||||||
if (this.variables) node.variables = this.variables.clone();
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
pinned: function() {
|
|
||||||
return this.resolve().pinned();
|
|
||||||
},
|
|
||||||
resolve: function() {
|
|
||||||
return this.parent_scope.resolve();
|
|
||||||
},
|
|
||||||
_validate: function() {
|
|
||||||
if (this.parent_scope == null) return;
|
|
||||||
if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope");
|
|
||||||
if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope");
|
|
||||||
},
|
|
||||||
}, AST_Block);
|
|
||||||
|
|
||||||
var AST_Scope = DEFNODE("Scope", "cname uses_eval uses_with", {
|
var AST_Scope = DEFNODE("Scope", "cname uses_eval uses_with", {
|
||||||
$documentation: "Base class for all statements introducing a lexical scope",
|
$documentation: "Base class for all statements introducing a lexical scope",
|
||||||
$propdoc: {
|
$propdoc: {
|
||||||
|
|||||||
@@ -368,10 +368,10 @@ merge(Compressor.prototype, {
|
|||||||
&& !(def.init instanceof AST_Function && def.init !== def.scope)
|
&& !(def.init instanceof AST_Function && def.init !== def.scope)
|
||||||
&& def.init;
|
&& def.init;
|
||||||
if (def.fixed instanceof AST_Defun && !all(def.references, function(ref) {
|
if (def.fixed instanceof AST_Defun && !all(def.references, function(ref) {
|
||||||
var scope = ref.scope;
|
var scope = ref.scope.resolve();
|
||||||
do {
|
do {
|
||||||
if (def.scope === scope) return true;
|
if (def.scope === scope) return true;
|
||||||
} while (scope instanceof AST_Function && (scope = scope.parent_scope));
|
} while (scope instanceof AST_Function && (scope = scope.parent_scope.resolve()));
|
||||||
})) {
|
})) {
|
||||||
tw.defun_ids[def.id] = false;
|
tw.defun_ids[def.id] = false;
|
||||||
}
|
}
|
||||||
@@ -854,7 +854,7 @@ merge(Compressor.prototype, {
|
|||||||
&& !value.pinned()
|
&& !value.pinned()
|
||||||
&& (!d.in_loop || tw.parent() instanceof AST_Call)
|
&& (!d.in_loop || tw.parent() instanceof AST_Call)
|
||||||
|| !d.in_loop
|
|| !d.in_loop
|
||||||
&& d.scope === this.scope
|
&& d.scope === this.scope.resolve()
|
||||||
&& value.is_constant_expression();
|
&& value.is_constant_expression();
|
||||||
} else {
|
} else {
|
||||||
d.single_use = false;
|
d.single_use = false;
|
||||||
@@ -2585,7 +2585,7 @@ merge(Compressor.prototype, {
|
|||||||
var lhs = expr.left;
|
var lhs = expr.left;
|
||||||
if (!(lhs instanceof AST_SymbolRef)) break;
|
if (!(lhs instanceof AST_SymbolRef)) break;
|
||||||
if (is_undeclared_ref(lhs)) break;
|
if (is_undeclared_ref(lhs)) break;
|
||||||
if (lhs.scope !== scope) break;
|
if (lhs.scope.resolve() !== scope) break;
|
||||||
var def = lhs.definition();
|
var def = lhs.definition();
|
||||||
if (def.scope !== scope) break;
|
if (def.scope !== scope) break;
|
||||||
if (def.orig.length > def.eliminated + 1) break;
|
if (def.orig.length > def.eliminated + 1) break;
|
||||||
@@ -5179,12 +5179,12 @@ merge(Compressor.prototype, {
|
|||||||
if (!(node_def.id in in_use_ids)) {
|
if (!(node_def.id in in_use_ids)) {
|
||||||
in_use_ids[node_def.id] = true;
|
in_use_ids[node_def.id] = true;
|
||||||
in_use.push(node_def);
|
in_use.push(node_def);
|
||||||
if (node.scope !== node_def.scope) {
|
}
|
||||||
var redef = node_def.redefined();
|
if (node.scope !== node_def.scope) {
|
||||||
if (redef && !(redef.id in in_use_ids)) {
|
var redef = node_def.redefined();
|
||||||
in_use_ids[redef.id] = true;
|
if (redef && !(redef.id in in_use_ids)) {
|
||||||
in_use.push(redef);
|
in_use_ids[redef.id] = true;
|
||||||
}
|
in_use.push(redef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (track_assigns(node_def, node)) add_assigns(node_def, node);
|
if (track_assigns(node_def, node)) add_assigns(node_def, node);
|
||||||
@@ -7031,6 +7031,9 @@ merge(Compressor.prototype, {
|
|||||||
var stat = fn.body[i];
|
var stat = fn.body[i];
|
||||||
if (stat instanceof AST_Defun) {
|
if (stat instanceof AST_Defun) {
|
||||||
if (!safe_to_inject || var_exists(used, stat.name.name)) return false;
|
if (!safe_to_inject || var_exists(used, stat.name.name)) return false;
|
||||||
|
if (!all(stat.enclosed, function(def) {
|
||||||
|
return def.scope === stat || !catches[def.name];
|
||||||
|
})) return false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(stat instanceof AST_Var)) continue;
|
if (!(stat instanceof AST_Var)) continue;
|
||||||
@@ -7067,7 +7070,7 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
} while (!(scope instanceof AST_Scope));
|
} while (!(scope instanceof AST_Scope));
|
||||||
var safe_to_inject = (!(scope instanceof AST_Toplevel) || compressor.toplevel.vars)
|
var safe_to_inject = (!(scope instanceof AST_Toplevel) || compressor.toplevel.vars)
|
||||||
&& (exp !== fn || fn.parent_scope === compressor.find_parent(AST_Scope));
|
&& (exp !== fn || fn.parent_scope.resolve() === compressor.find_parent(AST_Scope));
|
||||||
var inline = compressor.option("inline");
|
var inline = compressor.option("inline");
|
||||||
var used = Object.create(catches);
|
var used = Object.create(catches);
|
||||||
if (!can_inject_args(catches, used, inline >= 2 && safe_to_inject)) return false;
|
if (!can_inject_args(catches, used, inline >= 2 && safe_to_inject)) return false;
|
||||||
@@ -8087,7 +8090,7 @@ merge(Compressor.prototype, {
|
|||||||
if (!compressor.option("ie8")
|
if (!compressor.option("ie8")
|
||||||
&& is_undeclared_ref(self)
|
&& is_undeclared_ref(self)
|
||||||
// testing against `self.scope.uses_with` is an optimization
|
// testing against `self.scope.uses_with` is an optimization
|
||||||
&& !(self.scope.uses_with && compressor.find_parent(AST_With))) {
|
&& !(self.scope.resolve().uses_with && compressor.find_parent(AST_With))) {
|
||||||
switch (self.name) {
|
switch (self.name) {
|
||||||
case "undefined":
|
case "undefined":
|
||||||
return make_node(AST_Undefined, self).optimize(compressor);
|
return make_node(AST_Undefined, self).optimize(compressor);
|
||||||
@@ -8104,16 +8107,14 @@ merge(Compressor.prototype, {
|
|||||||
var single_use = def.single_use && !(parent instanceof AST_Call && parent.is_expr_pure(compressor));
|
var single_use = def.single_use && !(parent instanceof AST_Call && parent.is_expr_pure(compressor));
|
||||||
if (single_use) {
|
if (single_use) {
|
||||||
if (fixed instanceof AST_Lambda) {
|
if (fixed instanceof AST_Lambda) {
|
||||||
if ((def.scope !== self.scope || def.in_loop)
|
if ((def.scope !== self.scope.resolve() || def.in_loop)
|
||||||
&& (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) {
|
&& (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) {
|
||||||
single_use = false;
|
single_use = false;
|
||||||
} else if (recursive_ref(compressor, def)) {
|
} else if (recursive_ref(compressor, def)) {
|
||||||
single_use = false;
|
single_use = false;
|
||||||
} else if (fixed.name && fixed.name.definition() !== def) {
|
} else if (fixed.name && fixed.name.definition() !== def) {
|
||||||
single_use = false;
|
single_use = false;
|
||||||
} else if (fixed.parent_scope !== self.scope
|
} else if (fixed.parent_scope !== self.scope.resolve() || def.orig[0] instanceof AST_SymbolFunarg) {
|
||||||
|| !(self.scope instanceof AST_Scope)
|
|
||||||
|| def.orig[0] instanceof AST_SymbolFunarg) {
|
|
||||||
single_use = fixed.is_constant_expression(self.scope);
|
single_use = fixed.is_constant_expression(self.scope);
|
||||||
if (single_use == "f") {
|
if (single_use == "f") {
|
||||||
var scope = self.scope;
|
var scope = self.scope;
|
||||||
@@ -8894,7 +8895,7 @@ merge(Compressor.prototype, {
|
|||||||
&& expr instanceof AST_SymbolRef
|
&& expr instanceof AST_SymbolRef
|
||||||
&& is_arguments(def = expr.definition())
|
&& is_arguments(def = expr.definition())
|
||||||
&& prop instanceof AST_Number
|
&& prop instanceof AST_Number
|
||||||
&& (fn = expr.scope) === find_lambda()) {
|
&& (fn = expr.scope.resolve()) === find_lambda()) {
|
||||||
var index = prop.value;
|
var index = prop.value;
|
||||||
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") {
|
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") {
|
||||||
if (!def.deleted) def.deleted = [];
|
if (!def.deleted) def.deleted = [];
|
||||||
|
|||||||
@@ -2993,6 +2993,38 @@ issue_4146: {
|
|||||||
expect_stdout: "function"
|
expect_stdout: "function"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var_catch_redefined: {
|
||||||
|
options = {
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
throw "PASS";
|
||||||
|
} catch (a) {
|
||||||
|
function f() {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
throw "PASS";
|
||||||
|
} catch (a) {
|
||||||
|
function f() {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
}
|
||||||
|
|
||||||
single_use_catch_redefined: {
|
single_use_catch_redefined: {
|
||||||
options = {
|
options = {
|
||||||
reduce_vars: true,
|
reduce_vars: true,
|
||||||
|
|||||||
@@ -151,15 +151,18 @@ Infinity_not_in_with_scope: {
|
|||||||
unused: true,
|
unused: true,
|
||||||
}
|
}
|
||||||
input: {
|
input: {
|
||||||
var o = { Infinity: 'oInfinity' };
|
var o = { Infinity: "FAIL" };
|
||||||
var vInfinity = "Infinity";
|
var vInfinity = "Infinity";
|
||||||
vInfinity = Infinity;
|
vInfinity = Infinity;
|
||||||
|
console.log(vInfinity);
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
var o = { Infinity: 'oInfinity' }
|
var o = { Infinity: "FAIL" };
|
||||||
var vInfinity = "Infinity"
|
var vInfinity = "Infinity";
|
||||||
vInfinity = 1/0
|
vInfinity = 1/0;
|
||||||
|
console.log(vInfinity);
|
||||||
}
|
}
|
||||||
|
expect_stdout: "Infinity"
|
||||||
}
|
}
|
||||||
|
|
||||||
Infinity_in_with_scope: {
|
Infinity_in_with_scope: {
|
||||||
@@ -167,15 +170,18 @@ Infinity_in_with_scope: {
|
|||||||
unused: true,
|
unused: true,
|
||||||
}
|
}
|
||||||
input: {
|
input: {
|
||||||
var o = { Infinity: 'oInfinity' };
|
var o = { Infinity: "PASS" };
|
||||||
var vInfinity = "Infinity";
|
var vInfinity = "Infinity";
|
||||||
with (o) { vInfinity = Infinity; }
|
with (o) { vInfinity = Infinity; }
|
||||||
|
console.log(vInfinity);
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
var o = { Infinity: 'oInfinity' }
|
var o = { Infinity: "PASS" };
|
||||||
var vInfinity = "Infinity"
|
var vInfinity = "Infinity";
|
||||||
with (o) vInfinity = Infinity
|
with (o) vInfinity = Infinity;
|
||||||
|
console.log(vInfinity);
|
||||||
}
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
}
|
}
|
||||||
|
|
||||||
assorted_Infinity_NaN_undefined_in_with_scope: {
|
assorted_Infinity_NaN_undefined_in_with_scope: {
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ issue_4112: {
|
|||||||
var o = e;
|
var o = e;
|
||||||
for (e in o);
|
for (e in o);
|
||||||
var a = function() {};
|
var a = function() {};
|
||||||
console.log;
|
console.log(typeof a);
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
@@ -479,12 +479,15 @@ issue_4112: {
|
|||||||
var a = e;
|
var a = e;
|
||||||
for (e in a);
|
for (e in a);
|
||||||
a = function() {};
|
a = function() {};
|
||||||
console.log;
|
console.log(typeof a);
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
expect_stdout: "function"
|
expect_stdout: [
|
||||||
|
"function",
|
||||||
|
"function",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
issue_4115: {
|
issue_4115: {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe("With", function() {
|
|||||||
var ast = UglifyJS.parse("with(e) {f(1, 2)}");
|
var ast = UglifyJS.parse("with(e) {f(1, 2)}");
|
||||||
ast.figure_out_scope();
|
ast.figure_out_scope();
|
||||||
assert.equal(ast.uses_with, true);
|
assert.equal(ast.uses_with, true);
|
||||||
assert.equal(ast.body[0].expression.scope.uses_with, true);
|
assert.equal(ast.body[0].expression.scope.resolve().uses_with, true);
|
||||||
assert.equal(ast.body[0].body.body[0].body.expression.scope.uses_with, true);
|
assert.equal(ast.body[0].body.body[0].body.expression.scope.resolve().uses_with, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user