Skip to content

Commit

Permalink
Make TypedScope no longer extend Scope and TypedVar no longer extend …
Browse files Browse the repository at this point in the history
…Var.

These two changes need to be done simultaneously since TypedScopes want to return TypedVars and vice versa, and if one subclass relationship is broken then the return types of the other are no longer properly covariant.

Making the typed and untyped versions of Scope and Var more distinct allows using generics more cleanly.  It also enables us to remove the redundant overrides in the typed versions that simply called the superclass and casted the result.  Many of those methods in the abstract base class can now be marked 'final'.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=181703776
  • Loading branch information
shicks authored and brad4d committed Jan 12, 2018
1 parent 6febacc commit dd936ec
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 253 deletions.
146 changes: 75 additions & 71 deletions src/com/google/javascript/jscomp/AbstractScope.java
Expand Up @@ -37,62 +37,37 @@
*/ */
abstract class AbstractScope<S extends AbstractScope<S, V>, V extends AbstractVar<S, V>> abstract class AbstractScope<S extends AbstractScope<S, V>, V extends AbstractVar<S, V>>
implements StaticScope, Serializable { implements StaticScope, Serializable {
final Map<String, V> vars = new LinkedHashMap<>(); private final Map<String, V> vars = new LinkedHashMap<>();
S parent; private final Node rootNode;
int depth;
final Node rootNode;
private V arguments; private V arguments;


/**
* Creates a Scope given the parent Scope and the root node of the current scope.
*
* @param parent The parent Scope. Cannot be null.
* @param rootNode The root node of the current scope. Cannot be null.
*/
AbstractScope(S parent, Node rootNode) {
checkNotNull(parent);
checkArgument(NodeUtil.createsScope(rootNode), rootNode);
checkArgument(
rootNode != parent.rootNode, "rootNode should not be the parent's root node", rootNode);

this.parent = parent;
this.rootNode = rootNode;
this.depth = parent.depth + 1;
}

AbstractScope(Node rootNode) { AbstractScope(Node rootNode) {
// TODO(tbreisacher): Can we tighten this to just NodeUtil.createsScope?
checkArgument(
NodeUtil.createsScope(rootNode) || rootNode.isScript() || rootNode.isRoot(), rootNode);
this.parent = null;
this.rootNode = rootNode; this.rootNode = rootNode;
this.depth = 0;
} }


/** The depth of the scope. The global scope has depth 0. */
public abstract int getDepth();

/** Returns the parent scope, or null if this is the global scope. */
public abstract S getParent();

@Override @Override
public String toString() { public final String toString() {
return "Scope@" + rootNode; return "Scope@" + rootNode;
} }


/** The depth of the scope. The global scope has depth 0. */
public int getDepth() {
return depth;
}

public Scope untyped() { public Scope untyped() {
checkState(!(this instanceof TypedScope)); throw new IllegalStateException("untyped() called, but not an untyped scope.");
return (Scope) this;
} }


public TypedScope typed() { public TypedScope typed() {
checkState(this instanceof TypedScope); throw new IllegalStateException("typed() called, but not a typed scope.");
return (TypedScope) this;
} }


/** /**
* @return True if this scope contains {@code other}, or is the same scope as {@code other}. * @return True if this scope contains {@code other}, or is the same scope as {@code other}.
*/ */
boolean contains(S other) { final boolean contains(S other) {
S s = checkNotNull(other); S s = checkNotNull(other);
while (s != null) { while (s != null) {
if (s == this) { if (s == this) {
Expand All @@ -107,16 +82,14 @@ boolean contains(S other) {
* Gets the container node of the scope. This is typically the FUNCTION * Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node. * node or the global BLOCK/SCRIPT node.
*/ */
// Non-final for jsdev tests
@Override @Override
public Node getRootNode() { public Node getRootNode() {
return rootNode; return rootNode;
} }


public S getParent() { /** Walks up the tree to find the global scope. */
return parent; final S getGlobalScope() {
}

S getGlobalScope() {
S result = thisScope(); S result = thisScope();
while (result.getParent() != null) { while (result.getParent() != null) {
result = result.getParent(); result = result.getParent();
Expand All @@ -125,8 +98,8 @@ S getGlobalScope() {
} }


@Override @Override
public S getParentScope() { public final S getParentScope() {
return parent; return getParent();
} }


abstract V makeArgumentsVar(); abstract V makeArgumentsVar();
Expand All @@ -135,42 +108,51 @@ public S getParentScope() {
* Undeclares a variable, to be used when the compiler optimizes out * Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope. * a variable and removes it from the scope.
*/ */
void undeclare(V var) { final void undeclare(V var) {
checkState(var.scope == this); checkState(var.scope == this);
checkState(vars.get(var.name).equals(var)); checkState(vars.get(var.name).equals(var));
undeclareInteral(var); undeclareInteral(var);
} }


/** Without any safety checks */ /** Without any safety checks */
void undeclareInteral(V var) { final void undeclareInteral(V var) {
vars.remove(var.name); vars.remove(var.name);
} }


final void declareInternal(String name, V var) {
vars.put(name, var);
}

final void clearVarsInternal() {
vars.clear();
}

@Override @Override
public V getSlot(String name) { public final V getSlot(String name) {
return getVar(name); return getVar(name);
} }


@Override @Override
public V getOwnSlot(String name) { public final V getOwnSlot(String name) {
return vars.get(name); return vars.get(name);
} }


/** /**
* Returns the variable, may be null * Returns the variable, may be null
*/ */
// Non-final for jsdev tests
public V getVar(String name) { public V getVar(String name) {
S scope = thisScope(); S scope = thisScope();
while (scope != null) { while (scope != null) {
V var = scope.vars.get(name); V var = scope.getOwnSlot(name);
if (var != null) { if (var != null) {
return var; return var;
} }
if (Var.ARGUMENTS.equals(name) && NodeUtil.isVanillaFunction(scope.getRootNode())) { if (Var.ARGUMENTS.equals(name) && NodeUtil.isVanillaFunction(scope.getRootNode())) {
return scope.getArgumentsVar(); return scope.getArgumentsVar();
} }
// Recurse up the parent Scope // Recurse up the parent Scope
scope = scope.parent; scope = scope.getParent();
} }
return null; return null;
} }
Expand All @@ -184,7 +166,7 @@ public final V getArgumentsVar() {
} }


if (!isFunctionScope() || rootNode.isArrowFunction()) { if (!isFunctionScope() || rootNode.isArrowFunction()) {
return parent.getArgumentsVar(); return getParent().getArgumentsVar();
} }


if (arguments == null) { if (arguments == null) {
Expand All @@ -195,28 +177,29 @@ public final V getArgumentsVar() {


/** /**
* Use only when in a function block scope and want to tell if a name is either at the top of the * Use only when in a function block scope and want to tell if a name is either at the top of the
* function block scope or the function parameter scope * function block scope or the function parameter scope. This obviously only applies to ES6 block
* scopes.
*/ */
public boolean isDeclaredInFunctionBlockOrParameter(String name) { public final boolean isDeclaredInFunctionBlockOrParameter(String name) {
// In ES6, we create a separate "function parameter scope" above the function block scope to // In ES6, we create a separate "function parameter scope" above the function block scope to
// handle default parameters. Since nothing in the function block scope is allowed to shadow // handle default parameters. Since nothing in the function block scope is allowed to shadow
// the variables in the function scope, we treat the two scopes as one in this method. // the variables in the function scope, we treat the two scopes as one in this method.
checkState(isFunctionBlockScope()); checkState(isFunctionBlockScope());
return isDeclared(name, false) || parent.isDeclared(name, false); return isDeclared(name, false) || (getParent().isDeclared(name, false));
} }


/** /**
* Returns true if a variable is declared. * Returns true if a variable is declared.
*/ */
public boolean isDeclared(String name, boolean recurse) { public final boolean isDeclared(String name, boolean recurse) {
S scope = thisScope(); S scope = thisScope();
while (true) { while (true) {
if (scope.vars.containsKey(name)) { if (scope.getOwnSlot(name) != null) {
return true; return true;
} }


if (scope.parent != null && recurse) { if (scope.getParent() != null && recurse) {
scope = scope.parent; scope = scope.getParent();
continue; continue;
} }
return false; return false;
Expand All @@ -227,7 +210,8 @@ public boolean isDeclared(String name, boolean recurse) {
* Return an iterable over all of the variables declared in this scope * Return an iterable over all of the variables declared in this scope
* (except the special 'arguments' variable). * (except the special 'arguments' variable).
*/ */
public Iterable<? extends V> getVarIterable() { // Non-final for jsdev tests
public Iterable<V> getVarIterable() {
return vars.values(); return vars.values();
} }


Expand All @@ -243,7 +227,7 @@ public Iterable<? extends V> getVarIterable() {
* *
* <p>We do not include the special 'arguments' variable. * <p>We do not include the special 'arguments' variable.
*/ */
public Iterable<? extends V> getAllAccessibleVariables() { public final Iterable<V> getAllAccessibleVariables() {
Map<String, V> accessibleVars = new LinkedHashMap<>(); Map<String, V> accessibleVars = new LinkedHashMap<>();
S s = thisScope(); S s = thisScope();


Expand All @@ -258,48 +242,52 @@ public Iterable<? extends V> getAllAccessibleVariables() {
return accessibleVars.values(); return accessibleVars.values();
} }


public Iterable<? extends V> getAllSymbols() { // Non-final for jsdev tests
public Iterable<V> getAllSymbols() {
return Collections.unmodifiableCollection(vars.values()); return Collections.unmodifiableCollection(vars.values());
} }


/** /**
* Returns number of variables in this scope (excluding the special 'arguments' variable) * Returns number of variables in this scope (excluding the special 'arguments' variable)
*/ */
// Non-final for jsdev tests
public int getVarCount() { public int getVarCount() {
return vars.size(); return vars.size();
} }


/** /**
* Returns whether this is the global scope. * Returns whether this is the global scope.
*/ */
// Non-final for jsdev tests
public boolean isGlobal() { public boolean isGlobal() {
return parent == null; return getParent() == null;
} }


/** /**
* Returns whether this is a local scope (i.e. not the global scope). * Returns whether this is a local scope (i.e. not the global scope).
*/ */
// Non-final for jsdev tests
public boolean isLocal() { public boolean isLocal() {
return parent != null; return getParent() != null;
} }


public boolean isBlockScope() { public final boolean isBlockScope() {
return NodeUtil.createsBlockScope(rootNode); return NodeUtil.createsBlockScope(rootNode);
} }


public boolean isFunctionBlockScope() { public final boolean isFunctionBlockScope() {
return NodeUtil.isFunctionBlock(getRootNode()); return NodeUtil.isFunctionBlock(getRootNode());
} }


public boolean isFunctionScope() { public final boolean isFunctionScope() {
return getRootNode().isFunction(); return getRootNode().isFunction();
} }


public boolean isModuleScope() { public final boolean isModuleScope() {
return getRootNode().isModuleBody(); return getRootNode().isModuleBody();
} }


public boolean isCatchScope() { public final boolean isCatchScope() {
return getRootNode().isNormalBlock() return getRootNode().isNormalBlock()
&& getRootNode().hasOneChild() && getRootNode().hasOneChild()
&& getRootNode().getFirstChild().isCatch(); && getRootNode().getFirstChild().isCatch();
Expand All @@ -313,7 +301,7 @@ && getRootNode().hasOneChild()
* inside function parameters, it would make less sense to say that if you did declare one in * inside function parameters, it would make less sense to say that if you did declare one in
* the function parameters, it would be hoisted somewhere else. * the function parameters, it would be hoisted somewhere else.
*/ */
boolean isHoistScope() { final boolean isHoistScope() {
return isFunctionScope() || isFunctionBlockScope() || isGlobal() || isModuleScope(); return isFunctionScope() || isFunctionBlockScope() || isGlobal() || isModuleScope();
} }


Expand All @@ -324,13 +312,13 @@ boolean isHoistScope() {
* to declare a var inside function parameters, it would make even less sense to say that * to declare a var inside function parameters, it would make even less sense to say that
* such declarations would be "hoisted" somewhere else. * such declarations would be "hoisted" somewhere else.
*/ */
public S getClosestHoistScope() { public final S getClosestHoistScope() {
S current = thisScope(); S current = thisScope();
while (current != null) { while (current != null) {
if (current.isHoistScope()) { if (current.isHoistScope()) {
return current; return current;
} }
current = current.parent; current = current.getParent();
} }
return null; return null;
} }
Expand All @@ -342,4 +330,20 @@ public S getClosestHoistScope() {
private S thisScope() { private S thisScope() {
return (S) this; return (S) this;
} }

/** Performs simple validity checks on when constructing a child scope. */
void checkChildScope(S parent) {
checkNotNull(parent);
checkArgument(NodeUtil.createsScope(rootNode), rootNode);
checkArgument(
rootNode != parent.getRootNode(),
"rootNode should not be the parent's root node: %s", rootNode);
}

/** Performs simple validity checks on when constructing a root scope. */
void checkRootScope() {
// TODO(tbreisacher): Can we tighten this to just NodeUtil.createsScope?
checkArgument(
NodeUtil.createsScope(rootNode) || rootNode.isScript() || rootNode.isRoot(), rootNode);
}
} }
9 changes: 3 additions & 6 deletions src/com/google/javascript/jscomp/AbstractVar.java
Expand Up @@ -77,15 +77,13 @@ public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile(); return nameNode.getStaticSourceFile();
} }


// Temporarily non-final for TypedVar
@Override @Override
public V getSymbol() { public final V getSymbol() {
return thisVar(); return thisVar();
} }


// Temporarily non-final for TypedVar
@Override @Override
public V getDeclaration() { public final V getDeclaration() {
return nameNode == null ? null : thisVar(); return nameNode == null ? null : thisVar();
} }


Expand All @@ -101,8 +99,7 @@ public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode()); return NodeUtil.isFunctionExpression(getParentNode());
} }


// Temporarily non-final for TypedVar public final S getScope() {
public S getScope() {
return scope; return scope;
} }


Expand Down
Expand Up @@ -81,7 +81,7 @@ private static class DefaultScopeFactory implements ScopeFactory {
public Scope create(Scope parent, Node n) { public Scope create(Scope parent, Node n) {
return (parent == null) return (parent == null)
? Scope.createGlobalScope(n) ? Scope.createGlobalScope(n)
: new Scope(parent, n); : Scope.createChildScope(parent, n);
} }
} }


Expand Down
8 changes: 7 additions & 1 deletion src/com/google/javascript/jscomp/GlobalVarReferenceMap.java
Expand Up @@ -217,6 +217,8 @@ public void updateReferencesWithGlobalScope(Scope globalScope) {
for (ReferenceCollection collection : refMap.values()) { for (ReferenceCollection collection : refMap.values()) {
List<Reference> newRefs = new ArrayList<>(collection.references.size()); List<Reference> newRefs = new ArrayList<>(collection.references.size());
for (Reference ref : collection) { for (Reference ref : collection) {
// This is only ever called with compiler.getTopScope() in production, which is
// a TypedScope. But References are only ever created with
if (ref.getScope() != globalScope) { if (ref.getScope() != globalScope) {
newRefs.add(ref.cloneWithNewScope(globalScope)); newRefs.add(ref.cloneWithNewScope(globalScope));
} else { } else {
Expand Down Expand Up @@ -246,7 +248,11 @@ public GlobalVarRefCleanupPass(AbstractCompiler compiler) {
public void hotSwapScript(Node scriptRoot, Node originalRoot) { public void hotSwapScript(Node scriptRoot, Node originalRoot) {
GlobalVarReferenceMap refMap = compiler.getGlobalVarReferences(); GlobalVarReferenceMap refMap = compiler.getGlobalVarReferences();
if (refMap != null) { if (refMap != null) {
refMap.updateReferencesWithGlobalScope(compiler.getTopScope()); // We don't have a suitable untyped scope to use, but the actual scope probably doesn't
// matter, so long as it's a global scope and doesn't persist references to things that
// need to be cleaned up. So we just generate a new simple root scope.
refMap.updateReferencesWithGlobalScope(
Scope.createGlobalScope(compiler.getTopScope().getRootNode()));
} }
} }


Expand Down

0 comments on commit dd936ec

Please sign in to comment.