Index: lib/src/codegen/js_names.dart |
diff --git a/lib/src/codegen/js_names.dart b/lib/src/codegen/js_names.dart |
index 2002e283fae953cd4a9233b48415251e6d83fd67..ea3eed336713814fcd80e24c90e60caa6b5261f4 100644 |
--- a/lib/src/codegen/js_names.dart |
+++ b/lib/src/codegen/js_names.dart |
@@ -34,7 +34,7 @@ class JSNamer extends LocalNamer { |
JSNamer(Node node) : renames = new _RenameVisitor.build(node).renames; |
String getName(Identifier node) { |
- var rename = renames[renameKey(node)]; |
+ var rename = renames[identifierKey(node)]; |
if (rename != null) return rename; |
assert(!needsRename(node)); |
@@ -45,81 +45,108 @@ class JSNamer extends LocalNamer { |
void leaveScope() {} |
} |
+/// Represents a complete function scope in JS. |
+/// |
+/// We don't currently track ES6 block scopes, because we don't represent them |
+/// in js_ast yet. |
class _FunctionScope { |
+ /// The parent scope. |
final _FunctionScope parent; |
- final names = new HashSet<String>(); |
+ |
+ /// All names declared in this scope. |
+ final declared = new HashSet<Object>(); |
+ |
+ /// All names [declared] in this scope or its [parent]s, that is used in this |
+ /// scope and/or children. This is exactly the set of variable names we must |
+ /// not collide with inside this scope. |
+ final used = new HashSet<String>(); |
+ |
+ /// Nested functions, these are visited after everything else so the names |
+ /// they might need are in scope. |
+ final functions = new List<FunctionExpression>(); |
+ |
_FunctionScope(this.parent); |
} |
/// Collects all names used in the visited tree. |
-class _RenameVisitor extends BaseVisitor { |
+class _RenameVisitor extends VariableDeclarationVisitor { |
final pendingRenames = new Map<Object, Set<_FunctionScope>>(); |
final renames = new HashMap<Object, String>(); |
- _FunctionScope scope = new _FunctionScope(null); |
+ final _FunctionScope rootScope = new _FunctionScope(null); |
+ _FunctionScope scope; |
_RenameVisitor.build(Node root) { |
+ scope = rootScope; |
root.accept(this); |
+ _finishFunctions(); |
_finishNames(); |
} |
+ declare(Identifier node) { |
+ var id = identifierKey(node); |
+ scope.declared.add(id); |
+ _markUsed(node, id, scope); |
+ } |
+ |
visitIdentifier(Identifier node) { |
Jennifer Messerly
2015/04/22 16:19:53
previously, we simply recorded usage of a name, an
|
+ var id = identifierKey(node); |
+ |
+ // Find where the node was declared. |
+ var declScope = scope; |
+ while (declScope != null && !declScope.declared.contains(id)) { |
+ declScope = declScope.parent; |
+ } |
+ if (declScope == null) { |
+ // Assume it comes from the global scope. |
+ declScope = rootScope; |
+ declScope.declared.add(id); |
+ } |
+ _markUsed(node, id, declScope); |
+ } |
+ |
+ _markUsed(Identifier node, Object id, _FunctionScope declScope) { |
+ // If it needs rename, we can't add it to the used name set yet, instead we |
+ // will record all scopes it is visible in. |
+ Set<_FunctionScope> usedIn = null; |
if (needsRename(node)) { |
- // We can't assign the name yet, but we can add it to the list of things |
- // that need a name. |
- var id = renameKey(node); |
- pendingRenames.putIfAbsent(id, () => new HashSet()).add(scope); |
- } else { |
- scope.names.add(node.name); |
+ usedIn = pendingRenames.putIfAbsent(id, () => new HashSet()); |
+ } |
+ |
+ for (var s = scope, end = declScope.parent; s != end; s = s.parent) { |
+ if (usedIn != null) { |
+ usedIn.add(s); |
+ } else { |
+ s.used.add(node.name); |
+ } |
} |
} |
visitFunctionExpression(FunctionExpression node) { |
- scope = new _FunctionScope(scope); |
- super.visitFunctionExpression(node); |
- scope = scope.parent; |
+ // Visit nested functions after all identifiers are declared. |
+ scope.functions.add(node); |
+ } |
+ |
+ void _finishFunctions() { |
+ for (var f in scope.functions) { |
+ scope = new _FunctionScope(scope); |
+ super.visitFunctionExpression(f); |
+ _finishFunctions(); |
+ scope = scope.parent; |
+ } |
} |
void _finishNames() { |
+ var allNames = new Set<String>(); |
pendingRenames.forEach((id, scopes) { |
- var name = _findName(id, _allNamesInScope(scopes)); |
- renames[id] = name; |
- for (var s in scopes) s.names.add(name); |
- }); |
- } |
+ allNames.clear(); |
+ for (var s in scopes) allNames.addAll(s.used); |
- // Given a set of scopes, populates [allNames] to include all names in those |
- // scopes as well as intermediate scopes. Returns the common parent of |
- // all scopes. For example: |
- // |
- // function outer(t) { |
- // function middle(x) { |
- // function inner() { return t; } |
- // foo(x); |
- // } |
- // } |
- // |
- // Here `t` is used in `inner` and `outer` but we need to include `middle` |
- // as well, so we know the rename of `t` to `x` is not valid. |
- static Set<String> _allNamesInScope(Set<_FunctionScope> scopes) { |
- // As we iterate, we'll add more scopes. We don't need to consider these |
- // as intermediate scopes can't introduce new intermediates. |
- var candidates = []; |
- var allScopes = scopes.toSet(); |
- for (var scope in scopes) { |
- for (var p = scope.parent; p != null; p = p.parent) { |
- if (allScopes.contains(p)) { |
- allScopes.addAll(candidates); |
- break; |
- } |
- candidates.add(p); |
- } |
- // Discard these, we already added them or we didn't find a parent scope. |
- candidates.clear(); |
- } |
+ var name = _findName(id, allNames); |
+ renames[id] = name; |
- // Now collect all names found. |
- return allScopes.expand((s) => s.names).toSet(); |
+ for (var s in scopes) s.used.add(name); |
+ }); |
} |
static String _findName(Object id, Set<String> usedNames) { |
@@ -154,7 +181,7 @@ class _RenameVisitor extends BaseVisitor { |
bool needsRename(Identifier node) => |
node is JSTemporary || node.allowRename && invalidJSVariableName(node.name); |
-Object /*String|JSTemporary*/ renameKey(Identifier node) => |
+Object /*String|JSTemporary*/ identifierKey(Identifier node) => |
node is JSTemporary ? node : node.name; |
/// Returns true for invalid JS variable names, such as keywords. |