Skip to content

Commit

Permalink
[NTI] Analyze all callbacks at the end during NTI.
Browse files Browse the repository at this point in the history
In preparation of inferring better signatures for unannotated callbacks in a follow-up CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175733986
  • Loading branch information
dimvar authored and Tyler Breisacher committed Nov 14, 2017
1 parent 6116456 commit dad576e
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 83 deletions.
23 changes: 15 additions & 8 deletions src/com/google/javascript/jscomp/GlobalTypeInfo.java
Expand Up @@ -59,10 +59,11 @@
*/
public class GlobalTypeInfo implements TypeIRegistry {

// An out-to-in list of the scopes, built during CollectNamedTypes
// This will be reversed at the end of GlobalTypeInfo to make sure
// that the scopes can be processed in-to-out in NewTypeInference.
private final List<NTIScope> scopes = new ArrayList<>();
// We collect function scopes during CollectNamedTypes, and put them in this list out-to-in
// (global scope first, then functions in global scope, etc).
// At the end of GlobalTypeInfo, we rearrange them in the order in which they will be
// processed during NewTypeInference. See GlobalTypeInfoCollector#reorderScopesForNTI.
private List<NTIScope> scopes;
private NTIScope globalScope;

private final List<TypeMismatch> mismatches;
Expand Down Expand Up @@ -112,10 +113,8 @@ public Void apply(Node pnameNode) {
}
}

void initGlobalTypeInfo(Node root) {
this.globalScope = new NTIScope(root, null, ImmutableList.<String>of(), this.getCommonTypes());
this.globalScope.addUnknownTypeNames(this.unknownTypeNames);
this.scopes.add(this.globalScope);
void setGlobalScope(NTIScope globalScope) {
this.globalScope = globalScope;
}

void recordPropertyName(Node pnameNode) {
Expand Down Expand Up @@ -162,10 +161,18 @@ List<NTIScope> getScopes() {
return this.scopes;
}

void setScopes(List<NTIScope> scopes) {
this.scopes = scopes;
}

NTIScope getGlobalScope() {
return this.globalScope;
}

Set<String> getUnknownTypeNames() {
return this.unknownTypeNames;
}

JSTypes getCommonTypes() {
return this.commonTypes;
}
Expand Down
80 changes: 59 additions & 21 deletions src/com/google/javascript/jscomp/GlobalTypeInfoCollector.java
Expand Up @@ -300,6 +300,8 @@ public class GlobalTypeInfoCollector implements CompilerPass {
private final SimpleInference simpleInference;
private final OrderedExterns orderedExterns;
private RawNominalType window;
// This list is populated during CollectNamedTypes and is complete during ProcessScope.
private final List<NTIScope> scopes;

public GlobalTypeInfoCollector(AbstractCompiler compiler) {
this.warnings = new WarningReporter(compiler);
Expand All @@ -309,6 +311,7 @@ public GlobalTypeInfoCollector(AbstractCompiler compiler) {
this.simpleInference = new SimpleInference(this.globalTypeInfo);
this.convention = compiler.getCodingConvention();
this.orderedExterns = new OrderedExterns();
this.scopes = new ArrayList<>();
}

@Override
Expand All @@ -319,23 +322,27 @@ public void process(Node externs, Node root) {

this.compiler.setMostRecentTypechecker(MostRecentTypechecker.NTI);

this.globalTypeInfo.initGlobalTypeInfo(root);
NTIScope globalScope = new NTIScope(root, null, ImmutableList.<String>of(), getCommonTypes());
globalScope.addUnknownTypeNames(this.globalTypeInfo.getUnknownTypeNames());
this.globalTypeInfo.setGlobalScope(globalScope);
this.scopes.add(globalScope);

// Processing of a scope is split into many separate phases, and it's not
// straightforward to remember which phase does what.

// (1) Find names of classes, interfaces, typedefs, enums, and namespaces
// defined in the global scope.
CollectNamedTypes rootCnt = new CollectNamedTypes(getGlobalScope());
CollectNamedTypes rootCnt = new CollectNamedTypes(globalScope);
NodeTraversal.traverseEs6(this.compiler, externs, this.orderedExterns);
rootCnt.collectNamedTypesInExterns();
defineObjectAndFunctionIfMissing();
NodeTraversal.traverseEs6(compiler, root, rootCnt);
// (2) Determine the type represented by each typedef and each enum
getGlobalScope().resolveTypedefs(getTypeParser());
getGlobalScope().resolveEnums(getTypeParser());
globalScope.resolveTypedefs(getTypeParser());
globalScope.resolveEnums(getTypeParser());
// (3) Repeat steps 1-2 for all the other scopes (outer-to-inner)
for (int i = 1; i < getScopes().size(); i++) {
NTIScope s = getScopes().get(i);
for (int i = 1; i < this.scopes.size(); i++) {
NTIScope s = this.scopes.get(i);
CollectNamedTypes cnt = new CollectNamedTypes(s);
NodeTraversal.traverseEs6(compiler, s.getBody(), cnt);
s.resolveTypedefs(getTypeParser());
Expand All @@ -348,7 +355,7 @@ public void process(Node externs, Node root) {
// (4) The bulk of the global-scope processing happens here:
// - Create scopes for functions
// - Declare properties on types
ProcessScope rootPs = new ProcessScope(getGlobalScope());
ProcessScope rootPs = new ProcessScope(globalScope);
if (externs != null) {
NodeTraversal.traverseEs6(compiler, externs, rootPs);
}
Expand All @@ -357,8 +364,8 @@ public void process(Node externs, Node root) {
rootPs.finishProcessingScope();

// (6) Repeat steps 4-5 for all the other scopes (outer-to-inner)
for (int i = 1; i < getScopes().size(); i++) {
NTIScope s = getScopes().get(i);
for (int i = 1; i < this.scopes.size(); i++) {
NTIScope s = this.scopes.get(i);
ProcessScope ps = new ProcessScope(s);
NodeTraversal.traverseEs6(compiler, s.getBody(), ps);
ps.finishProcessingScope();
Expand All @@ -382,7 +389,7 @@ public void process(Node externs, Node root) {
if (this.window != null) {
// Copy properties from window to Window.prototype, because sometimes
// people pass window around rather than using it directly.
Namespace winNs = getGlobalScope().getNamespace(WINDOW_INSTANCE);
Namespace winNs = globalScope.getNamespace(WINDOW_INSTANCE);
if (winNs != null) {
winNs.copyWindowProperties(getCommonTypes(), this.window);
}
Expand All @@ -396,14 +403,14 @@ public void process(Node externs, Node root) {
globalThisType = getCommonTypes().getTopObject().withLoose();
}
getCommonTypes().setGlobalThis(globalThisType);
getGlobalScope().setDeclaredType(
globalScope.setDeclaredType(
(new FunctionTypeBuilder(getCommonTypes())).
addReceiverType(globalThisType).buildDeclaration());

this.globalTypeInfo.setRawNominalTypes(nominaltypesByNode.values());
nominaltypesByNode = null;
propertyDefs = null;
for (NTIScope s : getScopes()) {
for (NTIScope s : this.scopes) {
s.freezeScope();
}
this.simpleInference.setScopesAreFrozen();
Expand Down Expand Up @@ -439,13 +446,48 @@ public void visit(NodeTraversal t, Node n, Node parent) {
this.warnings = null;
this.funNameGen = null;

// If a scope s1 contains a scope s2, then s2 must be before s1 in scopes.
// The type inference relies on this fact to process deeper scopes before shallower scopes.
Collections.reverse(getScopes());
reorderScopesForNTI();

this.compiler.setExternProperties(ImmutableSet.copyOf(getExternPropertyNames()));
}

/**
* After ProcessScope, scopes are stored in a list; shallower scopes appear before deeper
* scopes (i.e., the top level is first). This method rearranges them in the order in which
* we will process them in NewTypeInference. The order in which scopes are processed in NTI is
* crucial for good type checking, because each function is only checked once; there is no
* global-fixpoint phase.
*
* Unannotated callbacks are processed at the end. This means that by the time we typecheck a
* callback, we have checked the surrounding scope, and we have inferred a good signature.
*
* For the rest of the scopes, we process deeper scopes before shallower scopes
* (the top level is processed last). Then, if a declared function f is called in the same
* scope S in which it is defined, we typecheck S after f, and we have a function summary for f.
*
* TODO(dimvar): We also want to use summaries when type checking method calls to unannotated
* methods. To do that, we will need to process all methods in the beginning, and then the
* remaining scopes.
*/
private void reorderScopesForNTI() {
List<NTIScope> scopes = this.scopes;
ArrayList<NTIScope> result = new ArrayList<>(scopes.size());
ArrayList<NTIScope> callbacks = new ArrayList<>();
for (int i = scopes.size() - 1; i >= 0; i--) {
NTIScope s = scopes.get(i);
if (NodeUtil.isUnannotatedCallback(s.getRoot())) {
callbacks.add(s);
} else {
result.add(s);
}
}
// Outer unannotated callbacks are processed before inner unannotated callbacks, so that if
// a callback contains another callback, the outer one is analyzed first.
Collections.reverse(callbacks);
result.addAll(callbacks);
this.globalTypeInfo.setScopes(result);
}

private void setWindow(RawNominalType rawType) {
this.window = rawType;
}
Expand Down Expand Up @@ -1217,7 +1259,7 @@ private void visitFunctionEarly(Node fn) {
NTIScope fnScope =
new NTIScope(fn, this.currentScope, collectFormals(fn, fnDoc), getCommonTypes());
if (!fn.isFromExterns()) {
getScopes().add(fnScope);
GlobalTypeInfoCollector.this.scopes.add(fnScope);
}
this.currentScope.addLocalFunDef(internalName, fnScope);
maybeRecordNominalType(fn, nameNode, fnDoc, isRedeclaration);
Expand Down Expand Up @@ -2527,7 +2569,7 @@ private DeclaredFunctionType getDeclaredFunctionTypeFromContext(
}

// The function literal is an argument at a call
if (parent.isCall() && declNode != parent.getFirstChild()) {
if (NodeUtil.isUnannotatedCallback(declNode)) {
Node callee = parent.getFirstChild();
JSType calleeType = simpleInferExpr(callee, this.currentScope);
FunctionType calleeFunType = calleeType == null ? null : calleeType.getFunType();
Expand Down Expand Up @@ -2861,10 +2903,6 @@ private JSTypes getCommonTypes() {
return this.globalTypeInfo.getCommonTypes();
}

private List<NTIScope> getScopes() {
return this.globalTypeInfo.getScopes();
}

private JSTypeCreatorFromJSDoc getTypeParser() {
return this.globalTypeInfo.getTypeParser();
}
Expand Down
75 changes: 40 additions & 35 deletions src/com/google/javascript/jscomp/NTIScope.java
Expand Up @@ -54,33 +54,46 @@
*/
final class NTIScope implements DeclaredTypeRegistry, Serializable, TypeIEnv<JSType> {

static enum VarKind {
DECLARED,
INFERRED
}

/**
* Used for local variables.
*/
static class TaggedType implements Serializable {
static class LocalVarInfo implements Serializable {
// When we don't know the type of a local variable, this field is null, not ?.
private final JSType type;
private final boolean isDeclared;
private final VarKind kind;
// Whether this variable is referenced in other scopes.
private final boolean escapes;

private TaggedType(JSType type, boolean isDeclared) {
private LocalVarInfo(JSType type, VarKind kind, boolean escapes) {
this.type = type;
this.isDeclared = isDeclared;
this.kind = kind;
this.escapes = escapes;
}

static TaggedType makeDeclared(JSType t) {
return new TaggedType(t, true);
static LocalVarInfo makeDeclared(JSType t) {
return new LocalVarInfo(t, VarKind.DECLARED, false);
}

static TaggedType makeInferred(JSType t) {
return new TaggedType(t, false);
LocalVarInfo withEscaped() {
return new LocalVarInfo(this.type, this.kind, true);
}

JSType getInferredType() {
return !isDeclared ? type : null;
return this.kind == VarKind.INFERRED ? type : null;
}

JSType getDeclaredType() {
return isDeclared ? type : null;
return this.kind == VarKind.DECLARED ? type : null;
}

@Override
public String toString() {
return "LocalVarInfo(" + this.type + "," + this.kind + "," + this.escapes + ")";
}
}

Expand All @@ -92,12 +105,10 @@ JSType getDeclaredType() {
// Becomes true after freezeScope is run; so it's true during NTI.
private boolean isFrozen = false;

private final Map<String, TaggedType> locals = new LinkedHashMap<>();
private final Map<String, LocalVarInfo> locals = new LinkedHashMap<>();
private final Map<String, JSType> externs;
private final Set<String> constVars = new LinkedHashSet<>();
private final List<String> formals;
// Variables that are defined in this scope and used in inner scopes.
private Set<String> escapedVars = new LinkedHashSet<>();
// outerVars are the variables that appear free in this scope
// and are defined in an outer scope.
private final Set<String> outerVars = new LinkedHashSet<>();
Expand Down Expand Up @@ -379,7 +390,8 @@ boolean isUndeclaredOuterVar(String name) {
}

boolean isEscapedVar(String name) {
return this.escapedVars.contains(name);
LocalVarInfo info = this.locals.get(name);
return info != null && info.escapes;
}

boolean hasThis() {
Expand Down Expand Up @@ -499,21 +511,22 @@ void addDeclaredLocal(String name, JSType type, boolean isConstant, boolean isFr
if (isFromExterns) {
externs.put(name, type);
} else {
locals.put(name, TaggedType.makeDeclared(type));
LocalVarInfo info = this.locals.get(name);
this.locals.put(
name, new LocalVarInfo(type, VarKind.DECLARED, info != null && info.escapes));
}
}

void addInferredLocal(String name, JSType type) {
checkArgument(!name.contains("."));
locals.put(name, TaggedType.makeInferred(type));
LocalVarInfo info = this.locals.get(name);
this.locals.put(name, new LocalVarInfo(type, VarKind.INFERRED, info != null && info.escapes));
}

void clearInferredTypeOfVar(String name) {
if (locals.containsKey(name)) {
TaggedType localType = locals.get(name);
if (localType.getInferredType() != null) {
locals.put(name, TaggedType.makeInferred(null));
}
LocalVarInfo info = this.locals.get(name);
if (info != null && info.getInferredType() != null) {
this.locals.put(name, new LocalVarInfo(null, VarKind.INFERRED, info.escapes));
} else if (!isDefinedLocally(name, false) && this.parent != null) {
this.parent.clearInferredTypeOfVar(name);
}
Expand All @@ -525,7 +538,10 @@ static void mayRecordEscapedVar(NTIScope s, String name) {
}
while (s != null) {
if (s.isDefinedLocally(name, false)) {
s.escapedVars.add(name);
LocalVarInfo info = s.locals.get(name);
if (info != null) {
s.locals.put(name, info.withEscaped());
}
return;
}
s = s.parent;
Expand Down Expand Up @@ -585,16 +601,6 @@ void addNamespaceLit(QualifiedName qname, Node defSite) {
addNamespace(qname, defSite, new NamespaceLit(this.commonTypes, qname.toString(), defSite));
}

void updateType(String name, JSType newDeclType) {
if (isDefinedLocally(name, false)) {
locals.put(name, TaggedType.makeDeclared(newDeclType));
} else if (parent != null) {
parent.updateType(name, newDeclType);
} else {
throw new RuntimeException("Cannot update type of unknown variable: " + name);
}
}

void addOuterVar(String name) {
outerVars.add(name);
}
Expand Down Expand Up @@ -809,16 +815,15 @@ void freezeScope() {
if (externs.containsKey(name)) {
externs.put(name, t);
} else {
locals.put(name, TaggedType.makeDeclared(t));
locals.put(name, LocalVarInfo.makeDeclared(t));
}
}
for (String typedefName : localTypedefs.keySet()) {
locals.put(typedefName, TaggedType.makeDeclared(this.commonTypes.UNDEFINED));
locals.put(typedefName, LocalVarInfo.makeDeclared(this.commonTypes.UNDEFINED));
}
copyOuterVarsTransitively(this);
preservedNamespaces = localNamespaces;
localNamespaces = ImmutableMap.of();
escapedVars = ImmutableSet.of();
isFrozen = true;
}

Expand Down

0 comments on commit dad576e

Please sign in to comment.