Skip to content

Commit

Permalink
[NTI] Record properties assigned to methods.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=170929193
  • Loading branch information
shicks authored and dimvar committed Oct 4, 2017
1 parent c9cad9d commit fec2ee3
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 13 deletions.
62 changes: 49 additions & 13 deletions src/com/google/javascript/jscomp/GlobalTypeInfoCollector.java
Expand Up @@ -1714,17 +1714,6 @@ private RawNominalType findInScope(String qname) {
return currentScope.getNominalType(QualifiedName.fromQualifiedString(qname));
}

/** Returns the type of the constructor for a raw type. Null-safe. */
private JSType getConstructor(RawNominalType rawType) {
if (rawType != null) {
FunctionType ctor = rawType.getConstructorFunction();
if (ctor != null) {
return getCommonTypes().fromFunctionType(ctor);
}
}
return null;
}

private void visitPropertyDeclaration(Node getProp) {
recordPropertyName(getProp.getLastChild());
// Property declaration on THIS; most commonly a class property
Expand Down Expand Up @@ -2109,6 +2098,14 @@ private void visitOtherPropertyDeclaration(Node getProp) {
}
JSType recvType = simpleInferExprType(recv);
if (recvType == null) {
// Might still be worth recording a property, e.g. on a function.
PropertyDef def = findPropertyDef(recv);
if (def != null) {
JSType type = simpleInferExprType(getProp.getNext());
if (type != null) {
def.addProperty(recv.getNext().getString(), type);
}
}
return;
}
recvType = recvType.removeType(getCommonTypes().NULL_OR_UNDEFINED);
Expand Down Expand Up @@ -2143,6 +2140,20 @@ private void visitOtherPropertyDeclaration(Node getProp) {
}
}

/** Given a qualified name node, find a corresponding PropertyDef in propertyDefs. */
PropertyDef findPropertyDef(Node n) {
if (!isPrototypeProperty(n)) {
return null;
}
RawNominalType ownerType =
currentScope.getNominalType(QualifiedName.fromNode(n.getFirstFirstChild()));
if (ownerType == null) {
return null;
}
String propertyName = n.getLastChild().getString();
return propertyDefs.get(ownerType, propertyName);
}

boolean mayWarnAboutNoInit(Node constExpr) {
if (constExpr.isFromExterns()) {
return false;
Expand Down Expand Up @@ -2710,7 +2721,8 @@ private void mayAddPropToPrototype(
propDeclType = ft.getReturnType();
}
}
propertyDefs.put(rawType, pname, new PropertyDef(defSite, methodType, methodScope));
PropertyDef def = new PropertyDef(defSite, methodType, methodScope);
propertyDefs.put(rawType, pname, def);

// Warn for abstract methods not in abstract classes
if (methodType != null && methodType.isAbstract() && !rawType.isAbstractClass()) {
Expand All @@ -2730,6 +2742,7 @@ private void mayAddPropToPrototype(
if (propDeclType == null) {
propDeclType = mayInferFromRhsIfConst(defSite);
}
def.setter = new PropertySetter(rawType, pname, propDeclType, isConst);
rawType.addProtoProperty(pname, defSite, propDeclType, isConst);
if (defSite.isGetProp()) { // Don't bother saving for @lends
defSite.putBooleanProp(Node.ANALYZED_DURING_GTI, true);
Expand Down Expand Up @@ -2916,6 +2929,7 @@ private static class PropertyDef {
final Node defSite; // The getProp/objectLitKey of the property definition
DeclaredFunctionType methodType; // null for non-method property decls
final NTIScope methodScope; // null for decls without function on the RHS
PropertySetter setter; // optional extra information for updating the rawtype

PropertyDef(
Node defSite, DeclaredFunctionType methodType, NTIScope methodScope) {
Expand All @@ -2931,8 +2945,10 @@ PropertyDef substituteNominalGenerics(NominalType nt) {
if (this.methodType == null) {
return this;
}
return new PropertyDef(
PropertyDef def = new PropertyDef(
this.defSite, this.methodType.substituteNominalGenerics(nt), this.methodScope);
def.setter = this.setter;
return def;
}

void updateMethodType(DeclaredFunctionType updatedType) {
Expand All @@ -2942,12 +2958,32 @@ void updateMethodType(DeclaredFunctionType updatedType) {
}
}

void addProperty(String name, JSType type) {
if (this.setter != null) {
setter.type = setter.type.withProperty(new QualifiedName(name), type);
setter.rawType.addProtoProperty(setter.name, defSite, setter.type, setter.isConstant);
}
}

@Override
public String toString() {
return "PropertyDef(" + defSite + ", " + methodType + ")";
}
}

private static class PropertySetter {
final RawNominalType rawType;
final String name;
JSType type;
final boolean isConstant;
PropertySetter(RawNominalType rawType, String name, JSType type, boolean isConstant) {
this.rawType = rawType;
this.name = name;
this.type = type;
this.isConstant = isConstant;
}
}

private Map<Node, String> getAnonFunNames() {
return this.globalTypeInfo.getAnonFunNames();
}
Expand Down
25 changes: 25 additions & 0 deletions test/com/google/javascript/jscomp/NewTypeInferenceTest.java
Expand Up @@ -21777,4 +21777,29 @@ public void testDontCrashOnUnusualExternsDefs() {
"Foo.prototype['myprop'];"),
"");
}

public void testPropertiesOnMethods() {
typeCheck(
LINE_JOINER.join(
"/** @constructor */ function Foo() {}",
"Foo.prototype.bar = function() {};",
"Foo.prototype.bar.baz = 42;",
"var /** string */ qux = Foo.prototype.bar.baz;"),
NewTypeInference.MISTYPED_ASSIGN_RHS);

typeCheck(
LINE_JOINER.join(
"/** @constructor */ function Foo() {}",
"Foo.prototype.bar = function() {};",
"Foo.prototype.bar.baz = 42;",
"var /** string */ qux = new Foo().bar.baz;"),
NewTypeInference.MISTYPED_ASSIGN_RHS);

typeCheck(
LINE_JOINER.join(
"/** @constructor */ function Foo() {}",
"Foo.prototype.bar = function() {};",
"Foo.prototype.bar.baz = 42;",
"var /** number */ qux = new Foo().bar.baz;"));
}
}

0 comments on commit fec2ee3

Please sign in to comment.