support const (#4190)

This commit is contained in:
Alex Lam S.L
2020-10-11 18:18:57 +01:00
committed by GitHub
parent ffcce28ce1
commit 55451e7b78
15 changed files with 1265 additions and 302 deletions

View File

@@ -203,6 +203,10 @@ var AST_Directive = DEFNODE("Directive", "value quote", {
},
}, AST_Statement);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement);
function must_be_expression(node, prop) {
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
@@ -226,6 +230,34 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
},
}, 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_Statement);
function walk_body(node, visitor) {
node.body.forEach(function(node) {
node.walk(visitor);
@@ -249,44 +281,11 @@ var AST_Block = DEFNODE("Block", "body", {
if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function");
});
},
}, AST_Statement);
var AST_BlockScope = DEFNODE("BlockScope", "cname enclosed functions make_def parent_scope variables", {
$documentation: "Base class for all statements introducing a lexical scope",
$propdoc: {
cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
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);
}, AST_BlockScope);
var AST_BlockStatement = DEFNODE("BlockStatement", null, {
$documentation: "A block statement",
}, AST_BlockScope);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement);
}, AST_Block);
var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
@@ -297,7 +296,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement");
if (this.body instanceof AST_Function) throw new Error("body cannot be AST_Function");
},
}, AST_Statement);
}, AST_BlockScope);
var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
$documentation: "Statement with a label",
@@ -451,7 +450,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", {
return this.uses_eval || this.uses_with;
},
resolve: return_this,
}, AST_BlockScope);
}, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$documentation: "The toplevel scope",
@@ -699,7 +698,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
if (!(this.bfinally instanceof AST_Finally)) throw new Error("bfinally must be AST_Finally");
}
},
}, AST_BlockScope);
}, AST_Block);
var AST_Catch = DEFNODE("Catch", "argname", {
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
@@ -718,11 +717,11 @@ var AST_Catch = DEFNODE("Catch", "argname", {
if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch");
}
},
}, AST_BlockScope);
}, AST_Block);
var AST_Finally = DEFNODE("Finally", null, {
$documentation: "A `finally` node; only makes sense as part of a `try` statement"
}, AST_BlockScope);
}, AST_Block);
/* -----[ VAR ]----- */
@@ -738,14 +737,30 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", {
defn.walk(visitor);
});
});
}
},
_validate: function() {
if (this.definitions.length < 1) throw new Error("must have at least one definition");
},
}, AST_Statement);
var AST_Const = DEFNODE("Const", null, {
$documentation: "A `const` statement",
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
if (!(node.name instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
must_be_expression(node, "value");
});
},
}, AST_Definitions);
var AST_Var = DEFNODE("Var", null, {
$documentation: "A `var` statement",
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
if (!(node.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -763,10 +778,6 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
if (node.value) node.value.walk(visitor);
});
},
_validate: function() {
if (!(this.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
if (this.value != null) must_be_expression(this, "value");
},
});
/* -----[ OTHER ]----- */
@@ -1032,12 +1043,12 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
}, AST_ObjectProperty);
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
$documentation: "Base class for all symbols",
$propdoc: {
name: "[string] name of this symbol",
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
thedef: "[SymbolDef/S] the definition of this symbol"
},
$documentation: "Base class for all symbols",
_validate: function() {
if (typeof this.name != "string") throw new Error("name must be string");
},
@@ -1051,6 +1062,10 @@ var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)",
}, AST_Symbol);
var AST_SymbolConst = DEFNODE("SymbolConst", null, {
$documentation: "Symbol defining a constant",
}, AST_SymbolDeclaration);
var AST_SymbolVar = DEFNODE("SymbolVar", null, {
$documentation: "Symbol defining a variable",
}, AST_SymbolDeclaration);

View File

@@ -370,7 +370,8 @@ merge(Compressor.prototype, {
def.cross_loop = false;
def.direct_access = false;
def.escaped = [];
def.fixed = !def.scope.pinned()
def.fixed = !def.const_redefs
&& !def.scope.pinned()
&& !compressor.exposed(def)
&& !(def.init instanceof AST_Function && def.init !== def.scope)
&& def.init;
@@ -481,8 +482,10 @@ merge(Compressor.prototype, {
return def.fixed instanceof AST_Defun;
}
function safe_to_assign(tw, def) {
if (def.fixed === undefined) return true;
function safe_to_assign(tw, def, declare) {
if (def.fixed === undefined) return declare || all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolConst);
});
if (def.fixed === null && def.safe_ids) {
def.safe_ids[def.id] = false;
delete def.safe_ids;
@@ -654,6 +657,11 @@ merge(Compressor.prototype, {
pop(tw);
return true;
});
def(AST_BlockScope, function(tw, descend, compressor) {
this.variables.each(function(def) {
reset_def(tw, compressor, def);
});
});
def(AST_Call, function(tw, descend) {
tw.find_parent(AST_Scope).may_call_this();
var exp = this.expression;
@@ -717,7 +725,10 @@ merge(Compressor.prototype, {
tw.in_loop = saved_loop;
return true;
});
def(AST_For, function(tw) {
def(AST_For, function(tw, descend, compressor) {
this.variables.each(function(def) {
reset_def(tw, compressor, def);
});
if (this.init) this.init.walk(tw);
var saved_loop = tw.in_loop;
tw.in_loop = this;
@@ -735,7 +746,10 @@ merge(Compressor.prototype, {
tw.in_loop = saved_loop;
return true;
});
def(AST_ForIn, function(tw) {
def(AST_ForIn, function(tw, descend, compressor) {
this.variables.each(function(def) {
reset_def(tw, compressor, def);
});
this.object.walk(tw);
var saved_loop = tw.in_loop;
tw.in_loop = this;
@@ -816,7 +830,10 @@ merge(Compressor.prototype, {
pop(tw);
return true;
});
def(AST_Switch, function(tw) {
def(AST_Switch, function(tw, descend, compressor) {
this.variables.each(function(def) {
reset_def(tw, compressor, def);
});
this.expression.walk(tw);
var first = true;
this.body.forEach(function(branch) {
@@ -900,7 +917,10 @@ merge(Compressor.prototype, {
walk_defuns(tw, this);
return true;
});
def(AST_Try, function(tw) {
def(AST_Try, function(tw, descend, compressor) {
this.variables.each(function(def) {
reset_def(tw, compressor, def);
});
push(tw);
walk_body(this, tw);
pop(tw);
@@ -963,7 +983,7 @@ merge(Compressor.prototype, {
if (!node.value) return;
descend();
var d = node.name.definition();
if (safe_to_assign(tw, d)) {
if (safe_to_assign(tw, d, true)) {
mark(tw, d);
tw.loop_ids[d.id] = tw.in_loop;
d.fixed = function() {
@@ -1667,8 +1687,6 @@ merge(Compressor.prototype, {
extract_candidates(expr.condition);
extract_candidates(expr.consequent);
extract_candidates(expr.alternative);
} else if (expr instanceof AST_Definitions) {
expr.definitions.forEach(extract_candidates);
} else if (expr instanceof AST_Dot) {
extract_candidates(expr.expression);
} else if (expr instanceof AST_DWLoop) {
@@ -1722,6 +1740,8 @@ merge(Compressor.prototype, {
} else {
extract_candidates(expr.expression);
}
} else if (expr instanceof AST_Var) {
expr.definitions.forEach(extract_candidates);
} else if (expr instanceof AST_VarDef) {
if (expr.value) {
var def = expr.name.definition();
@@ -1895,6 +1915,7 @@ merge(Compressor.prototype, {
function get_lhs(expr) {
if (expr instanceof AST_VarDef) {
var def = expr.name.definition();
if (def.const_redefs) return;
if (!member(expr.name, def.orig)) return;
var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0);
var referenced = def.references.length - def.replaced - (assignments[def.name] || 0);
@@ -2139,11 +2160,15 @@ merge(Compressor.prototype, {
for (var i = 0; i < statements.length;) {
var stat = statements[i];
if (stat instanceof AST_BlockStatement) {
CHANGED = true;
eliminate_spurious_blocks(stat.body);
[].splice.apply(statements, [i, 1].concat(stat.body));
i += stat.body.length;
continue;
if (all(stat.body, function(stat) {
return !(stat instanceof AST_Const);
})) {
CHANGED = true;
eliminate_spurious_blocks(stat.body);
[].splice.apply(statements, [i, 1].concat(stat.body));
i += stat.body.length;
continue;
}
}
if (stat instanceof AST_Directive) {
if (member(stat.value, seen_dirs)) {
@@ -2379,12 +2404,15 @@ merge(Compressor.prototype, {
}
function as_statement_array_with_return(node, ab) {
var body = as_statement_array(node).slice(0, -1);
if (ab.value) {
body.push(make_node(AST_SimpleStatement, ab.value, {
body: ab.value.expression
}));
var body = as_statement_array(node);
var block = body, last;
while ((last = block[block.length - 1]) !== ab) {
block = last.body;
}
block.pop();
if (ab.value) body.push(make_node(AST_SimpleStatement, ab.value, {
body: ab.value.expression
}));
return body;
}
@@ -2430,7 +2458,7 @@ merge(Compressor.prototype, {
statements.length = n;
CHANGED = n != len;
if (has_quit) has_quit.forEach(function(stat) {
extract_declarations_from_unreachable_code(stat, statements);
extract_declarations_from_unreachable_code(compressor, stat, statements);
});
}
@@ -2574,7 +2602,7 @@ merge(Compressor.prototype, {
if (merge_conditional_assignments(def, exprs, keep)) trimmed = true;
break;
}
if (join_var_assign(defn.definitions, exprs, keep)) trimmed = true;
if (defn instanceof AST_Var && join_var_assign(defn.definitions, exprs, keep)) trimmed = true;
}
return trimmed && exprs;
}
@@ -2668,7 +2696,7 @@ merge(Compressor.prototype, {
CHANGED = true;
} else {
statements[++j] = stat;
defs = stat;
if (stat instanceof AST_Var) defs = stat;
}
continue;
} else if (stat instanceof AST_Exit) {
@@ -2690,7 +2718,7 @@ merge(Compressor.prototype, {
defs.definitions = defs.definitions.concat(stat.init.definitions);
stat.init = null;
CHANGED = true;
} else if (stat.init instanceof AST_Definitions) {
} else if (stat.init instanceof AST_Var) {
defs = stat.init;
}
} else if (stat instanceof AST_ForIn) {
@@ -2750,25 +2778,46 @@ merge(Compressor.prototype, {
}
}
function extract_declarations_from_unreachable_code(stat, target) {
function extract_declarations_from_unreachable_code(compressor, stat, target) {
if (!(stat instanceof AST_Defun)) {
AST_Node.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
}
stat.walk(new TreeWalker(function(node) {
var block;
stat.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_Definitions) {
AST_Node.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
node.remove_initializers();
target.push(node);
node.remove_initializers(compressor);
push(node);
return true;
}
if (node instanceof AST_Defun) {
target.push(node);
push(node);
return true;
}
if (node instanceof AST_Scope) {
if (node instanceof AST_Scope) return true;
if (node instanceof AST_BlockScope) {
var save = block;
block = [];
descend();
if (block.required) {
target.push(make_node(AST_BlockStatement, stat, {
body: block
}));
} else if (block.length) {
[].push.apply(target, block);
}
block = save;
return true;
}
}));
function push(node) {
if (block) {
block.push(node);
if (node instanceof AST_Const) block.required = true;
} else {
target.push(node);
}
}
}
function is_undefined(node, compressor) {
@@ -4038,7 +4087,9 @@ merge(Compressor.prototype, {
});
def(AST_SymbolDeclaration, return_false);
def(AST_SymbolRef, function(compressor) {
return !this.is_declared(compressor);
return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolConst);
}));
});
def(AST_This, return_false);
def(AST_Try, function(compressor) {
@@ -4285,12 +4336,15 @@ merge(Compressor.prototype, {
return self;
});
function trim_block(stat) {
switch (stat.body.length) {
case 1: return stat.body[0];
case 0: return make_node(AST_EmptyStatement, stat);
function trim_block(node) {
switch (node.body.length) {
case 0:
return make_node(AST_EmptyStatement, node);
case 1:
var stat = node.body[0];
if (!(stat instanceof AST_Const)) return stat;
}
return stat;
return node;
}
OPT(AST_BlockStatement, function(self, compressor) {
@@ -4386,6 +4440,16 @@ merge(Compressor.prototype, {
pop();
return true;
}
if (node instanceof AST_Const) {
node.definitions.forEach(function(defn) {
var def = defn.name.definition();
references[def.id] = false;
def = def.redefined();
if (def) references[def.id] = false;
defn.value.walk(tw);
});
return true;
}
if (node instanceof AST_For) {
if (node.init) node.init.walk(tw);
push();
@@ -4633,7 +4697,7 @@ merge(Compressor.prototype, {
if (!(sym instanceof AST_SymbolRef)) return;
if (compressor.exposed(sym.definition())) return;
if (!all(sym.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolLambda);
return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda);
})) return;
return sym;
};
@@ -4668,42 +4732,40 @@ merge(Compressor.prototype, {
});
}
if (node === self) return;
if (node instanceof AST_Defun) {
var node_def = node.name.definition();
if (!drop_funcs && scope === self) {
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
if (scope === self) {
if (node instanceof AST_Defun) {
var def = node.name.definition();
if (!drop_funcs && !(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
in_use.push(def);
}
initializations.add(def.id, node);
return true; // don't go in nested scopes
}
initializations.add(node_def.id, node);
return true; // don't go in nested scopes
}
if (node instanceof AST_SymbolFunarg && scope === self) {
var node_def = node.definition();
var_defs_by_id.add(node_def.id, node);
assignments.add(node_def.id, node);
}
if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def) {
var node_def = def.name.definition();
var_defs_by_id.add(node_def.id, def);
if (!drop_vars) {
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
if (node instanceof AST_Definitions) {
node.definitions.forEach(function(defn) {
var def = defn.name.definition();
var_defs_by_id.add(def.id, defn);
if (!drop_vars && !(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
in_use.push(def);
}
}
if (def.value) {
if (def.value.has_side_effects(compressor)) {
def.value.walk(tw);
if (!defn.value) return;
if (defn.value.has_side_effects(compressor)) {
defn.value.walk(tw);
} else {
initializations.add(node_def.id, def.value);
initializations.add(def.id, defn.value);
}
assignments.add(node_def.id, def);
}
});
return true;
assignments.add(def.id, defn);
});
return true;
}
if (node instanceof AST_SymbolFunarg) {
var def = node.definition();
var_defs_by_id.add(def.id, node);
assignments.add(def.id, node);
return true;
}
}
return scan_ref_scoped(node, descend, true);
});
@@ -4714,7 +4776,14 @@ merge(Compressor.prototype, {
// symbols (that may not be in_use).
tw = new TreeWalker(scan_ref_scoped);
for (var i = 0; i < in_use.length; i++) {
var init = initializations.get(in_use[i].id);
var in_use_def = in_use[i];
if (in_use_def.const_redefs) in_use_def.const_redefs.forEach(function(def) {
if (!(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
in_use.push(def);
}
});
var init = initializations.get(in_use_def.id);
if (init) init.forEach(function(init) {
init.walk(tw);
});
@@ -4915,7 +4984,8 @@ merge(Compressor.prototype, {
}
tail.push(def);
}
} else if (sym.orig[0] instanceof AST_SymbolCatch) {
} else if (sym.orig[0] instanceof AST_SymbolCatch
&& sym.scope.resolve() === def.name.scope.resolve()) {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) side_effects.push(value);
var var_defs = var_defs_by_id.get(sym.id);
@@ -5012,7 +5082,13 @@ merge(Compressor.prototype, {
return node;
}
}, function(node, in_list) {
if (node instanceof AST_For) {
if (node instanceof AST_BlockStatement) switch (node.body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1:
var stat = node.body[0];
if (!(stat instanceof AST_Const)) return stat;
} else if (node instanceof AST_For) {
// Certain combination of unused name + side effect leads to invalid AST:
// https://github.com/mishoo/UglifyJS/issues/44
// https://github.com/mishoo/UglifyJS/issues/1838
@@ -5574,6 +5650,9 @@ merge(Compressor.prototype, {
})
&& all(def.references, function(ref) {
return ref.fixed_value() === right;
})
&& all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolConst);
});
}
});
@@ -5790,7 +5869,9 @@ merge(Compressor.prototype, {
return make_sequence(this, [ expression, property ]);
});
def(AST_SymbolRef, function(compressor) {
return this.is_declared(compressor) ? null : this;
return this.is_declared(compressor) && all(this.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolConst);
}) ? null : this;
});
def(AST_This, return_null);
def(AST_Unary, function(compressor, first_in_statement) {
@@ -5848,26 +5929,24 @@ merge(Compressor.prototype, {
if (!compressor.option("loops")) return self;
var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
if (!(cond instanceof AST_Node)) {
if (cond) return make_node(AST_For, self, {
if (cond && !has_loop_control(self, compressor.parent(), AST_Continue)) return make_node(AST_For, self, {
body: make_node(AST_BlockStatement, self.body, {
body: [
self.body,
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
})
}),
]
})
}).optimize(compressor);
if (!has_loop_control(self, compressor.parent())) {
return make_node(AST_BlockStatement, self.body, {
body: [
self.body,
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
})
]
}).optimize(compressor);
}
if (!has_loop_control(self, compressor.parent())) return make_node(AST_BlockStatement, self.body, {
body: [
self.body,
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}),
]
}).optimize(compressor);
}
if (self.body instanceof AST_BlockStatement && !has_loop_control(self, compressor.parent(), AST_Continue)) {
var body = self.body.body;
@@ -5877,6 +5956,7 @@ merge(Compressor.prototype, {
&& !stat.alternative
&& stat.body instanceof AST_Break
&& compressor.loopcontrol_target(stat.body) === self) {
if (has_block_scope_refs(stat.condition)) break;
self.condition = make_node(AST_Binary, self, {
operator: "&&",
left: stat.condition.negate(compressor),
@@ -5884,6 +5964,7 @@ merge(Compressor.prototype, {
});
body.splice(i, 1);
} else if (stat instanceof AST_SimpleStatement) {
if (has_block_scope_refs(stat.body)) break;
self.condition = make_sequence(self, [
stat.body,
self.condition,
@@ -5904,6 +5985,18 @@ merge(Compressor.prototype, {
body: make_node(AST_EmptyStatement, self)
}).optimize(compressor);
return self;
function has_block_scope_refs(node) {
var found = false;
node.walk(new TreeWalker(function(node) {
if (found) return true;
if (node instanceof AST_SymbolRef) {
if (!member(node.definition(), self.enclosed)) found = true;
return true;
}
}));
return found;
}
});
function if_break_in_loop(self, compressor) {
@@ -5934,7 +6027,7 @@ merge(Compressor.prototype, {
} else if (retain) {
body.push(first);
}
extract_declarations_from_unreachable_code(self.body, body);
extract_declarations_from_unreachable_code(compressor, self.body, body);
return make_node(AST_BlockStatement, self, {
body: body
});
@@ -5952,7 +6045,7 @@ merge(Compressor.prototype, {
self.condition = first.condition.negate(compressor);
}
var body = as_statement_array(first.alternative);
extract_declarations_from_unreachable_code(first.body, body);
extract_declarations_from_unreachable_code(compressor, first.body, body);
return drop_it(body);
}
ab = first_statement(first.alternative);
@@ -5967,7 +6060,7 @@ merge(Compressor.prototype, {
self.condition = first.condition;
}
var body = as_statement_array(first.body);
extract_declarations_from_unreachable_code(first.alternative, body);
extract_declarations_from_unreachable_code(compressor, first.alternative, body);
return drop_it(body);
}
}
@@ -6015,7 +6108,6 @@ merge(Compressor.prototype, {
if (!cond) {
if (compressor.option("dead_code")) {
var body = [];
extract_declarations_from_unreachable_code(self.body, body);
if (self.init instanceof AST_Statement) {
body.push(self.init);
} else if (self.init) {
@@ -6026,6 +6118,7 @@ merge(Compressor.prototype, {
body.push(make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}));
extract_declarations_from_unreachable_code(compressor, self.body, body);
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
}
} else if (self.condition && !(cond instanceof AST_Node)) {
@@ -6121,21 +6214,23 @@ merge(Compressor.prototype, {
}
if (!cond) {
AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
var body = [];
extract_declarations_from_unreachable_code(self.body, body);
body.push(make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}));
var body = [
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}),
];
extract_declarations_from_unreachable_code(compressor, self.body, body);
if (self.alternative) body.push(self.alternative);
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
} else if (!(cond instanceof AST_Node)) {
AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
var body = [];
if (self.alternative) extract_declarations_from_unreachable_code(self.alternative, body);
body.push(make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}));
body.push(self.body);
var body = [
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}),
self.body,
];
if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body);
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
}
}
@@ -6459,7 +6554,7 @@ merge(Compressor.prototype, {
if (prev && !aborts(prev)) {
prev.body = prev.body.concat(branch.body);
} else {
extract_declarations_from_unreachable_code(branch, decl);
extract_declarations_from_unreachable_code(compressor, branch, decl);
}
}
});
@@ -6470,9 +6565,9 @@ merge(Compressor.prototype, {
if (has_declarations_only(self)) {
var body = [];
if (self.bcatch) {
extract_declarations_from_unreachable_code(self.bcatch, body);
extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
body.forEach(function(stat) {
if (!(stat instanceof AST_Definitions)) return;
if (!(stat instanceof AST_Var)) return;
stat.definitions.forEach(function(var_def) {
var def = var_def.name.definition().redefined();
if (!def) return;
@@ -6499,7 +6594,13 @@ merge(Compressor.prototype, {
return self;
});
AST_Definitions.DEFMETHOD("remove_initializers", function() {
AST_Const.DEFMETHOD("remove_initializers", function(compressor) {
this.definitions.forEach(function(def) {
def.value = make_node(AST_Undefined, def).optimize(compressor);
});
});
AST_Var.DEFMETHOD("remove_initializers", function() {
this.definitions.forEach(function(def) {
def.value = null;
});
@@ -6526,8 +6627,31 @@ merge(Compressor.prototype, {
return make_sequence(this, assignments);
});
OPT(AST_Definitions, function(self, compressor) {
return self.definitions.length ? self : make_node(AST_EmptyStatement, self);
OPT(AST_Const, function(self, compressor) {
return all(self.definitions, function(defn) {
var node = defn.name;
if (!node.fixed_value()) return false;
var def = node.definition();
var scope = def.scope.resolve();
if (scope instanceof AST_Toplevel) {
if (!compressor.toplevel.vars) return false;
if (def.scope === scope) return true;
return !scope.variables.has(node.name) && !scope.globals.has(node.name);
}
return def.scope === scope || !scope.find_variable(node);
}) ? make_node(AST_Var, self, {
definitions: self.definitions.map(function(defn) {
var name = make_node(AST_SymbolVar, defn.name, defn.name);
var def = name.definition();
def.orig[def.orig.indexOf(defn.name)] = name;
var scope = def.scope.resolve();
if (def.scope !== scope) scope.variables.set(def.name, def);
return make_node(AST_VarDef, defn, {
name: name,
value: defn.value
});
})
}) : self;
});
function lift_sequence_in_expression(node, compressor) {

View File

@@ -835,10 +835,6 @@ function OutputStream(options) {
use_asm = was_asm;
}
AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
force_statement(this.body, output);
});
DEFPRINT(AST_Statement, function(output) {
this.body.print(output);
output.semicolon();
@@ -897,7 +893,7 @@ function OutputStream(options) {
self.condition.print(output);
});
output.space();
self._do_print_body(output);
force_statement(self.body, output);
});
DEFPRINT(AST_For, function(output) {
var self = this;
@@ -927,7 +923,7 @@ function OutputStream(options) {
}
});
output.space();
self._do_print_body(output);
force_statement(self.body, output);
});
DEFPRINT(AST_ForIn, function(output) {
var self = this;
@@ -941,7 +937,7 @@ function OutputStream(options) {
self.object.print(output);
});
output.space();
self._do_print_body(output);
force_statement(self.body, output);
});
DEFPRINT(AST_With, function(output) {
var self = this;
@@ -951,7 +947,7 @@ function OutputStream(options) {
self.expression.print(output);
});
output.space();
self._do_print_body(output);
force_statement(self.body, output);
});
/* -----[ functions ]----- */
@@ -1036,7 +1032,7 @@ function OutputStream(options) {
else
force_statement(self.alternative, output);
} else {
self._do_print_body(output);
force_statement(self.body, output);
}
});
@@ -1114,17 +1110,21 @@ function OutputStream(options) {
print_braced(this, output);
});
DEFPRINT(AST_Var, function(output) {
var self = this;
output.print("var");
output.space();
self.definitions.forEach(function(def, i) {
if (i) output.comma();
def.print(output);
});
var p = output.parent();
if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon();
});
function print_definitinos(type) {
return function(output) {
var self = this;
output.print(type);
output.space();
self.definitions.forEach(function(def, i) {
if (i) output.comma();
def.print(output);
});
var p = output.parent();
if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon();
};
}
DEFPRINT(AST_Const, print_definitinos("const"));
DEFPRINT(AST_Var, print_definitinos("var"));
function parenthesize_for_noin(node, output, noin) {
var parens = false;
@@ -1383,11 +1383,12 @@ function OutputStream(options) {
function force_statement(stat, output) {
if (output.option("braces")) {
make_block(stat, output);
} else if (!stat || stat instanceof AST_EmptyStatement) {
output.force_semicolon();
} else if (stat instanceof AST_Const) {
make_block(stat, output);
} else {
if (!stat || stat instanceof AST_EmptyStatement)
output.force_semicolon();
else
stat.print(output);
stat.print(output);
}
}

View File

@@ -832,6 +832,12 @@ function parse($TEXT, options) {
next();
return break_cont(AST_Break);
case "const":
next();
var node = const_();
semicolon();
return node;
case "continue":
next();
return break_cont(AST_Continue);
@@ -988,7 +994,9 @@ function parse($TEXT, options) {
expect("(");
var init = null;
if (!is("punc", ";")) {
init = is("keyword", "var")
init = is("keyword", "const")
? (next(), const_(true))
: is("keyword", "var")
? (next(), var_(true))
: expression(true, true);
if (is("operator", "in")) {
@@ -1161,13 +1169,22 @@ function parse($TEXT, options) {
});
}
function vardefs(no_in) {
function vardefs(type, no_in, must_init) {
var a = [];
for (;;) {
var start = S.token;
var name = as_symbol(type);
var value = null;
if (is("operator", "=")) {
next();
value = expression(false, no_in);
} else if (must_init) {
croak("Missing initializer in declaration");
}
a.push(new AST_VarDef({
start : S.token,
name : as_symbol(AST_SymbolVar),
value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
start : start,
name : name,
value : value,
end : prev()
}));
if (!is("punc", ","))
@@ -1177,10 +1194,18 @@ function parse($TEXT, options) {
return a;
}
var const_ = function(no_in) {
return new AST_Const({
start : prev(),
definitions : vardefs(AST_SymbolConst, no_in, true),
end : prev()
});
};
var var_ = function(no_in) {
return new AST_Var({
start : prev(),
definitions : vardefs(no_in),
definitions : vardefs(AST_SymbolVar, no_in),
end : prev()
});
};

View File

@@ -80,7 +80,12 @@ SymbolDef.prototype = {
}
},
redefined: function() {
return this.defun && this.defun.variables.get(this.name);
var scope = this.defun;
if (!scope) return;
var def = scope.variables.get(this.name);
if (!def && scope instanceof AST_Toplevel) def = scope.globals.get(this.name);
if (def === this) return;
return def;
},
unmangleable: function(options) {
return this.global && !options.toplevel
@@ -114,6 +119,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
});
return true;
}
if (node instanceof AST_SwitchBranch) {
node.init_vars(scope);
descend();
return true;
}
if (node instanceof AST_Try) {
walk_scope(function() {
walk_body(node, tw);
@@ -122,10 +132,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (node.bfinally) node.bfinally.walk(tw);
return true;
}
if (node instanceof AST_BlockScope) {
walk_scope(descend);
return true;
}
if (node instanceof AST_With) {
var s = scope;
do {
@@ -133,7 +139,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (s.uses_with) break;
s.uses_with = true;
} while (s = s.parent_scope);
return;
walk_scope(descend);
return true;
}
if (node instanceof AST_BlockScope) {
walk_scope(descend);
return true;
}
if (node instanceof AST_Symbol) {
node.scope = scope;
@@ -144,6 +155,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
}
if (node instanceof AST_SymbolCatch) {
scope.def_variable(node).defun = defun;
} else if (node instanceof AST_SymbolConst) {
scope.def_variable(node).defun = defun;
} else if (node instanceof AST_SymbolDefun) {
defun.def_function(node, tw.parent());
entangle(defun, scope);
@@ -216,7 +229,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
node.reference(options);
return true;
}
// ensure mangling works if catch reuses a scope variable
// ensure mangling works if `catch` reuses a scope variable
if (node instanceof AST_SymbolCatch) {
var def = node.definition().redefined();
if (def) for (var s = node.scope; s; s = s.parent_scope) {
@@ -225,6 +238,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
}
return true;
}
// ensure compression works if `const` reuses a scope variable
if (node instanceof AST_SymbolConst) {
var def = node.definition();
var redef = def.redefined();
if (redef) {
if (!redef.const_redefs) redef.const_redefs = [];
redef.const_redefs.push(def);
}
return true;
}
});
self.walk(tw);
@@ -290,7 +313,6 @@ AST_Toplevel.DEFMETHOD("def_global", function(node) {
});
function init_block_vars(scope, parent) {
scope.cname = -1; // the current index for mangling functions/variables
scope.enclosed = []; // variables from this or outer scope(s) that are referenced from this or inner scopes
scope.parent_scope = parent; // the parent scope (null if this is the top level)
scope.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
@@ -368,8 +390,9 @@ AST_BlockScope.DEFMETHOD("def_variable", function(symbol, init) {
function names_in_use(scope, options) {
var names = scope.names_in_use;
if (!names) {
scope.names_in_use = names = Object.create(null);
scope.cname = -1;
scope.cname_holes = [];
scope.names_in_use = names = Object.create(null);
var cache = options.cache && options.cache.props;
scope.enclosed.forEach(function(def) {
if (def.unmangleable(options)) names[def.name] = true;
@@ -467,7 +490,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
lname = save_nesting;
return true;
}
if (node instanceof AST_Scope) {
if (node instanceof AST_BlockScope) {
var to_mangle = [];
node.variables.each(function(def) {
if (!defer_redef(def)) to_mangle.push(def);
});
descend();
if (options.cache && node instanceof AST_Toplevel) {
node.globals.each(mangle);
@@ -477,9 +504,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
sym.scope = node;
sym.reference(options);
}
node.variables.each(function(def) {
if (!defer_redef(def)) mangle(def);
});
to_mangle.forEach(mangle);
return true;
}
if (node instanceof AST_Label) {
@@ -490,13 +515,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
node.mangled_name = name;
return true;
}
if (!options.ie8 && node instanceof AST_Catch && node.argname) {
var def = node.argname.definition();
var redef = defer_redef(def, node.argname);
descend();
if (!redef) mangle(def);
return true;
}
});
this.walk(tw);
redefined.forEach(mangle);
@@ -511,7 +529,8 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
if (!redef) return false;
redefined.push(def);
def.references.forEach(reference);
if (node) reference(node);
var node = def.orig[0];
if (node instanceof AST_SymbolCatch || node instanceof AST_SymbolConst) reference(node);
return true;
function reference(sym) {