Skip to content

Commit

Permalink
[NTI] Handle most of property disambiguation using NTI types.
Browse files Browse the repository at this point in the history
Summary of changes:
- Mark types in externs in GlobalTypeInfo.
- Record mismatches and implicit uses in NTI.
- Change disambiguation unit tests for the cases where OTI and NTI differ.
- Implement some TypeI functions in NTI.
- Support inferred prototype properties in NTI.
- Annotate @Lends and prototype object literals in NTI.
- Move more code from TypeValidator to TypeMismatch.
- Add method instantiateGenericsWithUnknown to OTI.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=150074274
  • Loading branch information
dimvar authored and Tyler Breisacher committed Mar 14, 2017
1 parent 50981b9 commit d28e5e4
Show file tree
Hide file tree
Showing 18 changed files with 671 additions and 282 deletions.
3 changes: 2 additions & 1 deletion src/com/google/javascript/jscomp/CheckAccessControls.java
Expand Up @@ -1045,7 +1045,8 @@ private static ObjectTypeI dereference(TypeI type) {
*/ */
private static ObjectTypeI getSuperClassInstanceIfFinal(TypeI type) { private static ObjectTypeI getSuperClassInstanceIfFinal(TypeI type) {
if (type != null) { if (type != null) {
FunctionTypeI ctor = castToObject(type).getSuperClassConstructor(); ObjectTypeI obj = castToObject(type);
FunctionTypeI ctor = obj == null ? null : obj.getSuperClassConstructor();
JSDocInfo doc = ctor == null ? null : ctor.getJSDocInfo(); JSDocInfo doc = ctor == null ? null : ctor.getJSDocInfo();
if (doc != null && doc.isFinal()) { if (doc != null && doc.isFinal()) {
return ctor.getInstanceType(); return ctor.getInstanceType();
Expand Down
9 changes: 8 additions & 1 deletion src/com/google/javascript/jscomp/Compiler.java
Expand Up @@ -1445,7 +1445,14 @@ Iterable<TypeMismatch> getTypeMismatches() {


@Override @Override
Iterable<TypeMismatch> getImplicitInterfaceUses() { Iterable<TypeMismatch> getImplicitInterfaceUses() {
return getTypeValidator().getImplicitInterfaceUses(); switch (this.mostRecentTypechecker) {
case OTI:
return getTypeValidator().getImplicitInterfaceUses();
case NTI:
return getSymbolTable().getImplicitInterfaceUses();
default:
throw new RuntimeException("Can't ask for type mismatches before type checking.");
}
} }


@Override @Override
Expand Down
24 changes: 17 additions & 7 deletions src/com/google/javascript/jscomp/DisambiguateProperties.java
Expand Up @@ -522,16 +522,14 @@ private void handleObjectLit(NodeTraversal t, Node n) {


// We should never see a mix of numbers and strings. // We should never see a mix of numbers and strings.
String name = child.getString(); String name = child.getString();
TypeI type = getType(n); TypeI objlitType = getType(n);

Property prop = getProperty(name); Property prop = getProperty(name);
if (!prop.scheduleRenaming(child, if (!prop.scheduleRenaming(child, processProperty(t, prop, objlitType, null))) {
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this // TODO(user): It doesn't look like the user can do much in this
// case right now. // case right now.
if (propertiesToErrorFor.containsKey(name)) { if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(child, propertiesToErrorFor.get(name), compiler.report(JSError.make(child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name, String.valueOf(type), n.toString(), "")); Warnings.INVALIDATION, name, String.valueOf(objlitType), n.toString(), ""));
} }
} }
} }
Expand Down Expand Up @@ -825,7 +823,19 @@ private boolean isInvalidatingType(TypeI type) {
return true; return true;
} }
ObjectTypeI objType = type.toMaybeObjectType(); ObjectTypeI objType = type.toMaybeObjectType();
return objType != null && objType.isUnknownObject(); if (objType != null) {
FunctionTypeI ft = objType.toMaybeFunctionType();
// TODO(dimvar): types of most object literals are considered anonymous objects, and as such,
// a property of an object literal prevents all properties with the same name from being
// disambiguated. In OTI, this might be hard to change, but in NTI, it is easy to change
// isUnknownObject to return false for object literals. I deliberately followed the behavior
// of OTI to help with the migration, but can revisit in the future to improve
// disambiguation.
return objType.isUnknownObject()
// Invalidate constructors of already-invalidated types
|| (ft != null && ft.isConstructor() && isInvalidatingType(ft.getInstanceType()));
}
return false;
} }


/** /**
Expand Down Expand Up @@ -940,7 +950,7 @@ private ObjectTypeI getTypeWithProperty(String field, TypeI type) {
&& objType.getConstructor().isInterface()) { && objType.getConstructor().isInterface()) {
ObjectTypeI topInterface = objType.getTopDefiningInterface(field); ObjectTypeI topInterface = objType.getTopDefiningInterface(field);
if (topInterface != null && topInterface.getConstructor() != null) { if (topInterface != null && topInterface.getConstructor() != null) {
foundType = topInterface.getConstructor().getPrototypeProperty(); foundType = topInterface.getPrototypeObject();
} }
} else { } else {
while (objType != null && !Objects.equals(objType.getPrototypeObject(), objType)) { while (objType != null && !Objects.equals(objType.getPrototypeObject(), objType)) {
Expand Down
110 changes: 70 additions & 40 deletions src/com/google/javascript/jscomp/GlobalTypeInfo.java
Expand Up @@ -283,6 +283,7 @@ class GlobalTypeInfo implements CompilerPass, TypeIRegistry {
private NTIScope globalScope; private NTIScope globalScope;
private WarningReporter warnings; private WarningReporter warnings;
private final List<TypeMismatch> mismatches; private final List<TypeMismatch> mismatches;
private final List<TypeMismatch> implicitInterfaceUses;
private final JSTypeCreatorFromJSDoc typeParser; private final JSTypeCreatorFromJSDoc typeParser;
private final AbstractCompiler compiler; private final AbstractCompiler compiler;
private final CodingConvention convention; private final CodingConvention convention;
Expand Down Expand Up @@ -327,6 +328,7 @@ class GlobalTypeInfo implements CompilerPass, TypeIRegistry {


this.warnings = new WarningReporter(compiler); this.warnings = new WarningReporter(compiler);
this.mismatches = new ArrayList<>(); this.mismatches = new ArrayList<>();
this.implicitInterfaceUses = new ArrayList<>();
this.compiler = compiler; this.compiler = compiler;
this.unknownTypeNames = unknownTypeNames; this.unknownTypeNames = unknownTypeNames;
this.convention = compiler.getCodingConvention(); this.convention = compiler.getCodingConvention();
Expand Down Expand Up @@ -362,6 +364,10 @@ List<TypeMismatch> getMismatches() {
return this.mismatches; return this.mismatches;
} }


List<TypeMismatch> getImplicitInterfaceUses() {
return this.implicitInterfaceUses;
}

JSType getCastType(Node n) { JSType getCastType(Node n) {
JSType t = castTypes.get(n); JSType t = castTypes.get(n);
Preconditions.checkNotNull(t); Preconditions.checkNotNull(t);
Expand Down Expand Up @@ -504,6 +510,21 @@ public void process(Node externs, Node root) {
for (NTIScope s : scopes) { for (NTIScope s : scopes) {
s.finalizeScope(); s.finalizeScope();
} }

// Traverse the externs and annotate them with types.
// Only works for the top level, not inside function bodies.
NodeTraversal.traverseEs6(
this.compiler, externs, new NodeTraversal.AbstractShallowCallback(){
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isQualifiedName()) {
Declaration d = globalScope.getDeclaration(QualifiedName.fromNode(n), false);
JSType type = simpleInferDeclaration(d);
n.setTypeI(type);
}
}
});

Map<Node, String> unknownTypes = typeParser.getUnknownTypesMap(); Map<Node, String> unknownTypes = typeParser.getUnknownTypesMap();
for (Map.Entry<Node, String> unknownTypeEntry : unknownTypes.entrySet()) { for (Map.Entry<Node, String> unknownTypeEntry : unknownTypes.entrySet()) {
this.warnings.add(JSError.make(unknownTypeEntry.getKey(), this.warnings.add(JSError.make(unknownTypeEntry.getKey(),
Expand All @@ -526,6 +547,38 @@ public void process(Node externs, Node root) {
this.compiler.setExternProperties(ImmutableSet.copyOf(this.externPropertyNames)); this.compiler.setExternProperties(ImmutableSet.copyOf(this.externPropertyNames));
} }


private JSType simpleInferDeclaration(Declaration decl) {
if (decl == null) {
return null;
}
// Namespaces (literals, enums, constructors) get populated during ProcessScope,
// so it's generally NOT safe to convert them to jstypes until after ProcessScope is done.
// However, we've seen examples where it is useful to use the constructor type
// during inference, e.g., to get the type of the instance from it.
// We allow this use case but add a marker property to make sure that the constructor type
// itself doesn't leak into the result.
if (decl.getNominal() != null) {
FunctionType ctorFn = decl.getNominal().getConstructorFunction();
if (ctorFn == null) {
return null;
}
return commonTypes.fromFunctionType(ctorFn)
.withProperty(CONST_INFERENCE_MARKER, commonTypes.UNKNOWN);
}
if (decl.getTypeOfSimpleDecl() != null) {
return decl.getTypeOfSimpleDecl();
}
NTIScope funScope = (NTIScope) decl.getFunctionScope();
if (funScope != null) {
DeclaredFunctionType dft = funScope.getDeclaredFunctionType();
if (dft == null) {
return null;
}
return commonTypes.fromFunctionType(dft.toFunctionType());
}
return null;
}

private Collection<PropertyDef> getPropDefsFromInterface(NominalType nominalType, String pname) { private Collection<PropertyDef> getPropDefsFromInterface(NominalType nominalType, String pname) {
Preconditions.checkArgument(nominalType.isFinalized()); Preconditions.checkArgument(nominalType.isFinalized());
Preconditions.checkArgument(nominalType.isInterface() || nominalType.isBuiltinObject()); Preconditions.checkArgument(nominalType.isInterface() || nominalType.isBuiltinObject());
Expand Down Expand Up @@ -819,8 +872,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
visitEnum(nameNode); visitEnum(nameNode);
} else if (isAliasedNamespaceDefinition(nameNode)) { } else if (isAliasedNamespaceDefinition(nameNode)) {
visitAliasedNamespace(nameNode); visitAliasedNamespace(nameNode);
} else if (varName.equals(WINDOW_INSTANCE) } else if (varName.equals(WINDOW_INSTANCE) && nameNode.isFromExterns()) {
&& nameNode.isFromExterns()) {
visitWindowVar(nameNode); visitWindowVar(nameNode);
} else if (isCtorDefinedByCall(nameNode)) { } else if (isCtorDefinedByCall(nameNode)) {
visitNewCtorDefinedByCall(nameNode); visitNewCtorDefinedByCall(nameNode);
Expand Down Expand Up @@ -1220,7 +1272,11 @@ private void maybeRecordNominalType(
|| mayCreateFunctionNamespace(firstChild) || mayCreateFunctionNamespace(firstChild)
|| mayCreateWindowNamespace(firstChild)) { || mayCreateWindowNamespace(firstChild)) {
if (nameNode.isGetProp()) { if (nameNode.isGetProp()) {
defSite.getParent().getFirstChild().putBooleanProp(Node.ANALYZED_DURING_GTI, true); if (defSite.isFunction()) {
defSite.getParent().putBooleanProp(Node.ANALYZED_DURING_GTI, true);
} else {
defSite.getParent().getFirstChild().putBooleanProp(Node.ANALYZED_DURING_GTI, true);
}
} else if (currentScope.isTopLevel()) { } else if (currentScope.isTopLevel()) {
maybeRecordBuiltinType(qname, rawType); maybeRecordBuiltinType(qname, rawType);
} }
Expand All @@ -1234,8 +1290,7 @@ private void maybeRecordNominalType(
} }
} }


private void maybeRecordBuiltinType( private void maybeRecordBuiltinType(String name, RawNominalType rawType) {
String name, RawNominalType rawType) {
switch (name) { switch (name) {
case "Arguments": case "Arguments":
commonTypes.setArgumentsType(rawType); commonTypes.setArgumentsType(rawType);
Expand Down Expand Up @@ -1358,8 +1413,8 @@ void processLendsNode(Node objlit) {
if (currentScope.isNamespace(lendsQname)) { if (currentScope.isNamespace(lendsQname)) {
processLendsToNamespace(lendsQname, lendsName, objlit); processLendsToNamespace(lendsQname, lendsName, objlit);
} else { } else {
RawNominalType rawType = checkValidLendsToPrototypeAndGetClass( RawNominalType rawType =
lendsQname, lendsName, objlit); checkValidLendsToPrototypeAndGetClass(lendsQname, lendsName, objlit);
if (rawType != null) { if (rawType != null) {
for (Node prop : objlit.children()) { for (Node prop : objlit.children()) {
String pname = NodeUtil.getObjectLitKeyName(prop); String pname = NodeUtil.getObjectLitKeyName(prop);
Expand Down Expand Up @@ -2263,38 +2318,6 @@ private JSType simpleInferGetelemType(Node n) {
return null; return null;
} }


private JSType simpleInferDeclaration(Declaration decl) {
if (decl == null) {
return null;
}
// Namespaces (literals, enums, constructors) get populated during ProcessScope,
// so it's generally NOT safe to convert them to jstypes until after ProcessScope is done.
// However, we've seen examples where it is useful to use the constructor type
// during inference, e.g., to get the type of the instance from it.
// We allow this use case but add a marker property to make sure that the constructor type
// itself doesn't leak into the result.
if (decl.getNominal() != null) {
FunctionType ctorFn = decl.getNominal().getConstructorFunction();
if (ctorFn == null) {
return null;
}
return commonTypes.fromFunctionType(ctorFn)
.withProperty(CONST_INFERENCE_MARKER, commonTypes.UNKNOWN);
}
if (decl.getTypeOfSimpleDecl() != null) {
return decl.getTypeOfSimpleDecl();
}
NTIScope funScope = (NTIScope) decl.getFunctionScope();
if (funScope != null) {
DeclaredFunctionType dft = funScope.getDeclaredFunctionType();
if (dft == null) {
return null;
}
return commonTypes.fromFunctionType(dft.toFunctionType());
}
return null;
}

private JSType simpleInferAndOrType(Node n) { private JSType simpleInferAndOrType(Node n) {
Preconditions.checkState(n.isOr() || n.isAnd()); Preconditions.checkState(n.isOr() || n.isAnd());
JSType lhs = simpleInferExprTypeRecur(n.getFirstChild()); JSType lhs = simpleInferExprTypeRecur(n.getFirstChild());
Expand Down Expand Up @@ -2591,7 +2614,14 @@ private void mayAddPropToPrototype(
} }
} }
} else { } else {
rawType.addUndeclaredProtoProperty(pname, defSite); JSType inferredType = null;
if (initializer != null) {
inferredType = simpleInferExprType(initializer);
}
if (inferredType == null) {
inferredType = commonTypes.UNKNOWN;
}
rawType.addUndeclaredProtoProperty(pname, defSite, inferredType);
} }
} }


Expand Down
6 changes: 2 additions & 4 deletions src/com/google/javascript/jscomp/NTIScope.java
Expand Up @@ -425,8 +425,7 @@ Set<String> getExterns() {
// If a variable is declared many times in a scope, the last definition // If a variable is declared many times in a scope, the last definition
// overwrites the previous ones. For correctness, we rely on the fact that // overwrites the previous ones. For correctness, we rely on the fact that
// the var-check passes run before type checking. // the var-check passes run before type checking.
void addLocal(String name, JSType declType, void addLocal(String name, JSType declType, boolean isConstant, boolean isFromExterns) {
boolean isConstant, boolean isFromExterns) {
Preconditions.checkArgument(!name.contains(".")); Preconditions.checkArgument(!name.contains("."));
if (isConstant) { if (isConstant) {
constVars.add(name); constVars.add(name);
Expand Down Expand Up @@ -658,8 +657,7 @@ void resolveEnums(JSTypeCreatorFromJSDoc typeParser) {
} }


void finalizeScope() { void finalizeScope() {
Preconditions.checkNotNull( Preconditions.checkNotNull(this.declaredType, "No declared type for scope: %s", this.root);
this.declaredType, "No declared type for scope: %s", this.root);
unknownTypeNames = ImmutableSet.of(); unknownTypeNames = ImmutableSet.of();
// For now, we put types of namespaces directly into the locals. // For now, we put types of namespaces directly into the locals.
// Alternatively, we could move this into NewTypeInference.initEdgeEnvs // Alternatively, we could move this into NewTypeInference.initEdgeEnvs
Expand Down

0 comments on commit d28e5e4

Please sign in to comment.