First class block scope

- Make let, const, and class symbols be declared in a block scope.
- Piggy back on existing catch symbol implementation to get block-aware mangling working
- Make sure unused block-scoped declarations can be dropped
- Don't eliminate a block if it has a block-scoped declaration
- Remove silly empty anonymous blocks left over from drop_unused
- AST_Toplevel now gets to call drop_unused too, since block-scoped variables aren't global!
- Don't consider block declarations global
This commit is contained in:
Fábio Santos
2016-02-28 14:06:51 +00:00
committed by Richard van Velzen
parent 6702cae918
commit 634f231b78
7 changed files with 266 additions and 30 deletions

View File

@@ -106,11 +106,15 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var in_destructuring = null;
var in_export;
var tw = new TreeWalker(function(node, descend){
if (options.screw_ie8 && node instanceof AST_Catch) {
var create_a_block_scope =
(options.screw_ie8 && node instanceof AST_Catch) ||
((node instanceof AST_Block) && node.creates_block_scope());
if (create_a_block_scope) {
var save_scope = scope;
scope = new AST_Scope(node);
scope.init_scope_vars(nesting);
scope.parent_scope = save_scope;
scope.is_block_scope = true;
descend();
scope = save_scope;
return true;
@@ -174,7 +178,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// scope when we encounter the AST_Defun node (which is
// instanceof AST_Scope) but we get to the symbol a bit
// later.
(node.scope = defun.parent_scope).def_function(node, in_export);
var parent_lambda = defun.parent_scope;
while (parent_lambda.is_block_scope) {
parent_lambda = parent_lambda.parent_scope;
}
(node.scope = parent_lambda).def_function(node, in_export);
}
else if (node instanceof AST_SymbolClass) {
defun.def_variable(node, in_export);
@@ -188,8 +196,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
(node.scope = defun.parent_scope).def_function(node, in_export);
}
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
var def = defun.def_variable(node, in_export);
|| node instanceof AST_SymbolConst
|| node instanceof AST_SymbolLet) {
var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export);
def.constant = node instanceof AST_SymbolConst;
def.destructuring = in_destructuring;
def.init = tw.parent().value;
@@ -279,6 +288,14 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
});
AST_Block.DEFMETHOD("creates_block_scope", function() {
return (
!(this instanceof AST_Lambda) &&
!(this instanceof AST_Toplevel) &&
!(this instanceof AST_Class)
);
});
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false;
@@ -312,17 +329,10 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol, in_export){
def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def);
def.object_destructuring_arg = symbol.object_destructuring_arg;
def.global = !this.parent_scope;
if (symbol instanceof AST_SymbolImport) {
// Imports are not global
def.global = false;
// TODO The real fix comes with block scoping being first class in uglifyJS,
// enabling import definitions to behave like module-level let declarations
}
if (!this.parent_scope && in_export) {
def.global = false;
if (in_export) {
def.export = true;
}
def.global = !this.parent_scope && !(symbol instanceof AST_SymbolBlockDeclaration);
} else {
def = this.variables.get(symbol.name);
def.orig.push(symbol);
@@ -466,7 +476,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
node.mangled_name = name;
return true;
}
if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
var mangle_with_block_scope =
(options.screw_ie8 && node instanceof AST_SymbolCatch) ||
node instanceof AST_SymbolBlockDeclaration;
if (mangle_with_block_scope) {
to_mangle.push(node.definition());
return;
}