Skip to content

Commit

Permalink
Convert TypedScopeCreator.FirstOrderFunctionAnalyzer to use ES6 node …
Browse files Browse the repository at this point in the history
…traversals.

One interesting change is that FunctionTypeBuilder.FunctionContents no longer contains data that does not pertain to FunctionTypeBuilder.  Previously it had data about assigned and escaped variables that was only used by TypedScopeCreator.  This change pulled out that data into TypedScopeCreator.AstBlockContents, which stores an optional FunctionContents (if the block has a FUNCTION node at its root).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188420187
  • Loading branch information
shicks authored and lauraharker committed Mar 9, 2018
1 parent dae65ef commit e0299e1
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 179 deletions.
36 changes: 32 additions & 4 deletions src/com/google/javascript/jscomp/AbstractScope.java
Expand Up @@ -28,12 +28,27 @@
import java.util.Map; import java.util.Map;


/** /**
* Scope contains information about a variable scope in JavaScript. * Scope contains information about a variable scope in JavaScript. Scopes can be nested, a scope
* Scopes can be nested, a scope points back to its parent scope. * points back to its parent scope. A Scope contains information about variables defined in that
* A Scope contains information about variables defined in that scope. * scope.
* *
* @see NodeTraversal * <p>ES6 introduces new scoping rules, which adds some complexity to this class. In particular,
* scopes fall into four mutually exclusive categories based on their root node: block, function,
* module, or global. Function, module, and global scopes are collectively referred to as "non-block
* scopes". We also define a scope as a "hoist scope" if it is a non-block scope *or* it is the
* outermost block scope within a function (i.e. a "function block scope"). Hoist scopes are
* important because "var" declarations are hoisted to the closest hoist scope, as opposed to ES6
* "let" and "const" which are not hoisted, but instead added directly to whatever scope they're
* declared in.
*
* <p>Finally, a caution about function scopes and function block scopes: the language does not
* permit any shadowing to occur between them (with the exception of bleeding function names), so in
* many situations these scopes are treated as a single scope. Under block scoping, only function
* parameters (and optionally, bleeding function names) are declared in the function scope. It is
* kept as a separate scope so that default parameter initializers may be evaluated in a separate
* scope from the function body.
* *
* @see NodeTraversal
*/ */
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 {
Expand Down Expand Up @@ -323,6 +338,19 @@ public final S getClosestHoistScope() {
return null; return null;
} }


/**
* Returns the closest non-block scope. This is equivalent to what the current scope would have
* been for non-block-scope creators, and is thus useful for migrating code to use block scopes.
*/
public final S getClosestNonBlockScope() {
S scope = getClosestHoistScope();
if (scope.isBlockScope()) {
scope = scope.getParent();
checkState(!scope.isBlockScope());
}
return scope;
}

// This is safe because any concrete subclass of AbstractScope<S> should be assignable to S. // This is safe because any concrete subclass of AbstractScope<S> should be assignable to S.
// While it's theoretically possible to do otherwise, such a class would be very awkward to // While it's theoretically possible to do otherwise, such a class would be very awkward to
// implement, and is therefore not worth worrying about. // implement, and is therefore not worth worrying about.
Expand Down
66 changes: 0 additions & 66 deletions src/com/google/javascript/jscomp/FunctionTypeBuilder.java
Expand Up @@ -25,12 +25,8 @@
import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE; import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE;


import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
import com.google.javascript.rhino.IR; import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.JSTypeExpression;
Expand Down Expand Up @@ -919,15 +915,6 @@ static interface FunctionContents {


/** Returns if this consists of a single throw. */ /** Returns if this consists of a single throw. */
boolean mayHaveSingleThrow(); boolean mayHaveSingleThrow();

/** Gets a list of variables in this scope that are escaped. */
Iterable<String> getEscapedVarNames();

/** Gets a list of variables whose properties are escaped. */
Set<String> getEscapedQualifiedNames();

/** Gets the number of times each variable has been assigned. */
Multiset<String> getAssignedNameCounts();
} }


static class UnknownFunctionContents implements FunctionContents { static class UnknownFunctionContents implements FunctionContents {
Expand Down Expand Up @@ -956,29 +943,11 @@ public boolean mayHaveNonEmptyReturns() {
public boolean mayHaveSingleThrow() { public boolean mayHaveSingleThrow() {
return true; return true;
} }

@Override
public Iterable<String> getEscapedVarNames() {
return ImmutableList.of();
}

@Override
public Set<String> getEscapedQualifiedNames() {
return ImmutableSet.of();
}

@Override
public Multiset<String> getAssignedNameCounts() {
return ImmutableMultiset.of();
}
} }


static class AstFunctionContents implements FunctionContents { static class AstFunctionContents implements FunctionContents {
private final Node n; private final Node n;
private boolean hasNonEmptyReturns = false; private boolean hasNonEmptyReturns = false;
private Set<String> escapedVarNames;
private Set<String> escapedQualifiedNames;
private final Multiset<String> assignedVarNames = HashMultiset.create();


AstFunctionContents(Node n) { AstFunctionContents(Node n) {
this.n = n; this.n = n;
Expand Down Expand Up @@ -1008,40 +977,5 @@ public boolean mayHaveSingleThrow() {
Node block = n.getLastChild(); Node block = n.getLastChild();
return block.hasOneChild() && block.getFirstChild().isThrow(); return block.hasOneChild() && block.getFirstChild().isThrow();
} }

@Override
public Iterable<String> getEscapedVarNames() {
return escapedVarNames == null
? ImmutableList.<String>of() : escapedVarNames;
}

void recordEscapedVarName(String name) {
if (escapedVarNames == null) {
escapedVarNames = new HashSet<>();
}
escapedVarNames.add(name);
}

@Override
public Set<String> getEscapedQualifiedNames() {
return escapedQualifiedNames == null
? ImmutableSet.<String>of() : escapedQualifiedNames;
}

void recordEscapedQualifiedName(String name) {
if (escapedQualifiedNames == null) {
escapedQualifiedNames = new HashSet<>();
}
escapedQualifiedNames.add(name);
}

@Override
public Multiset<String> getAssignedNameCounts() {
return assignedVarNames;
}

void recordAssignedName(String name) {
assignedVarNames.add(name);
}
} }
} }
15 changes: 13 additions & 2 deletions src/com/google/javascript/jscomp/NodeTraversal.java
Expand Up @@ -967,6 +967,18 @@ public Node getClosestHoistScopeRoot() {
return scopes.peek().getClosestHoistScope().getRootNode(); return scopes.peek().getClosestHoistScope().getRootNode();
} }


public Node getClosestNonBlockScopeRoot() {
int roots = scopeRoots.size();
for (int i = roots; i > 0; i--) {
Node rootNode = scopeRoots.get(i - 1);
if (!NodeUtil.createsBlockScope(rootNode)) {
return rootNode;
}
}

return scopes.peek().getClosestNonBlockScope().getRootNode();
}

public AbstractScope<?, ?> getClosestHoistScope() { public AbstractScope<?, ?> getClosestHoistScope() {
for (int i = scopeRoots.size(); i > 0; i--) { for (int i = scopeRoots.size(); i > 0; i--) {
if (isHoistScopeRootNode(scopeRoots.get(i - 1))) { if (isHoistScopeRootNode(scopeRoots.get(i - 1))) {
Expand Down Expand Up @@ -1050,8 +1062,7 @@ public boolean inGlobalScope() {


/** Determines whether the traversal is currently in the scope of the block of a function. */ /** Determines whether the traversal is currently in the scope of the block of a function. */
public boolean inFunctionBlockScope() { public boolean inFunctionBlockScope() {
Node scopeRoot = getScopeRoot(); return NodeUtil.isFunctionBlock(getScopeRoot());
return scopeRoot.isNormalBlock() && scopeRoot.getParent().isFunction();
} }


/** /**
Expand Down

0 comments on commit e0299e1

Please sign in to comment.