Skip to content

Commit

Permalink
[NEW TYPE INFERENCE] Make unification more accepting for subtypes.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=92768133
  • Loading branch information
dimvar committed May 5, 2015
1 parent 8db235e commit d536a7d
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/com/google/javascript/jscomp/newtypes/FunctionType.java
Expand Up @@ -424,14 +424,14 @@ public boolean isSubtypeOf(FunctionType other) {
if (nominalType == null && other.nominalType != null
|| nominalType != null && other.nominalType == null
|| nominalType != null && other.nominalType != null
&& !nominalType.isSubclassOf(other.nominalType)) {
&& !nominalType.isSubtypeOf(other.nominalType)) {
return false;
}

// covariance for the this: type
if (receiverType != null && other.receiverType == null
|| receiverType != null && other.receiverType != null
&& !receiverType.isSubclassOf(other.receiverType)) {
&& !receiverType.isSubtypeOf(other.receiverType)) {
return false;
}

Expand Down
17 changes: 10 additions & 7 deletions src/com/google/javascript/jscomp/newtypes/JSType.java
Expand Up @@ -460,6 +460,8 @@ static JSType nullAcceptingJoin(JSType t1, JSType t2) {

// When joining w/ TOP or UNKNOWN, avoid setting more fields on them, eg, obj.
public static JSType join(JSType lhs, JSType rhs) {
Preconditions.checkNotNull(lhs);
Preconditions.checkNotNull(rhs);
if (lhs.isTop() || rhs.isTop()) {
return TOP;
} else if (lhs.isUnknown() || rhs.isUnknown()) {
Expand Down Expand Up @@ -518,6 +520,7 @@ public JSType substituteGenericsWithUnknown() {
private static void updateTypemap(
Multimap<String, JSType> typeMultimap,
String typeParam, JSType type) {
Preconditions.checkNotNull(type);
Set<JSType> typesToRemove = new LinkedHashSet<>();
for (JSType other : typeMultimap.get(typeParam)) {
JSType unified = unifyUnknowns(type, other);
Expand Down Expand Up @@ -546,6 +549,8 @@ private static int promoteBoolean(int mask) {
* JSType.UNKNOWN as a "hole" to be filled.
* @return The unified type, or null if unification fails */
static JSType unifyUnknowns(JSType t1, JSType t2) {
Preconditions.checkNotNull(t1);
Preconditions.checkNotNull(t2);
if (t1.isUnknown()) {
return t2;
} else if (t2.isUnknown()) {
Expand Down Expand Up @@ -609,6 +614,7 @@ static JSType unifyUnknowns(JSType t1, JSType t2) {
*/
public boolean unifyWithSubtype(JSType other, List<String> typeParameters,
Multimap<String, JSType> typeMultimap) {
Preconditions.checkNotNull(other);
if (this.isUnknown() || this.isTop()) {
return true;
} else if (getMask() == TYPEVAR_MASK
Expand All @@ -623,20 +629,17 @@ public boolean unifyWithSubtype(JSType other, List<String> typeParameters,
}

Set<EnumType> ununifiedEnums = ImmutableSet.of();
if (getEnums().isEmpty()) {
ununifiedEnums = other.getEnums();
} else if (other.getEnums().isEmpty()) {
if (!other.getEnums().isEmpty()) {
ununifiedEnums = new LinkedHashSet<>();
for (EnumType e : other.getEnums()) {
if (!getEnums().contains(e)) {
if (!fromEnum(e).isSubtypeOf(this)) {
ununifiedEnums.add(e);
}
}
}

Set<ObjectType> ununifiedObjs = new LinkedHashSet<>(other.getObjs());
// Each obj in this must unify w/ exactly one obj in other.
// However, we don't check that two different objects of this don't unify
// We don't check that two different objects of this don't unify
// with the same other type.
// Fancy cases are unfortunately iteration-order dependent, eg,
// Foo<number>|Foo<string> may or may not unify with Foo<T>|Foo<string>
Expand All @@ -653,7 +656,7 @@ public boolean unifyWithSubtype(JSType other, List<String> typeParameters,
if (thisTypevar == null || !typeParameters.contains(thisTypevar)) {
return ununifiedObjs.isEmpty() && ununifiedEnums.isEmpty()
&& (otherTypevar == null || otherTypevar.equals(thisTypevar))
&& getMask() == (getMask() | other.getMask());
&& getMask() == (getMask() | (other.getMask() & ~ENUM_MASK));
} else {
// this is (T | ...)
int templateMask = BOTTOM_MASK;
Expand Down
66 changes: 52 additions & 14 deletions src/com/google/javascript/jscomp/newtypes/NominalType.java
Expand Up @@ -248,7 +248,7 @@ static JSType getConstructorObject(FunctionType ctorFn) {
return ctorFn.nominalType.rawType.getConstructorObject(ctorFn);
}

boolean isSubclassOf(NominalType other) {
boolean isSubtypeOf(NominalType other) {
RawNominalType otherRawType = other.rawType;

// interface <: class
Expand All @@ -262,7 +262,7 @@ boolean isSubclassOf(NominalType other) {
return false;
}
for (NominalType i : rawType.interfaces) {
if (i.instantiateGenerics(typeMap).isSubclassOf(other)) {
if (i.instantiateGenerics(typeMap).isSubtypeOf(other)) {
return true;
}
}
Expand All @@ -277,7 +277,7 @@ boolean isSubclassOf(NominalType other) {
return false;
} else {
for (NominalType i : rawType.interfaces) {
if (i.instantiateGenerics(typeMap).isSubclassOf(other)) {
if (i.instantiateGenerics(typeMap).isSubtypeOf(other)) {
return true;
}
}
Expand All @@ -292,7 +292,7 @@ boolean isSubclassOf(NominalType other) {
return false;
} else {
return rawType.superClass.instantiateGenerics(typeMap)
.isSubclassOf(other);
.isSubtypeOf(other);
}
}

Expand Down Expand Up @@ -339,10 +339,10 @@ static NominalType pickSuperclass(NominalType c1, NominalType c2) {
if (c1 == null || c2 == null) {
return null;
}
if (c1.isSubclassOf(c2)) {
if (c1.isSubtypeOf(c2)) {
return c2;
}
return c2.isSubclassOf(c1) ? c1 : null;
return c2.isSubtypeOf(c1) ? c1 : null;
}

// A special-case of meet
Expand All @@ -352,15 +352,16 @@ static NominalType pickSubclass(NominalType c1, NominalType c2) {
} else if (c2 == null) {
return c1;
}
if (c1.isSubclassOf(c2)) {
if (c1.isSubtypeOf(c2)) {
return c1;
}
return c2.isSubclassOf(c1) ? c2 : null;
return c2.isSubtypeOf(c1) ? c2 : null;
}

boolean unifyWithSubtype(NominalType other, List<String> typeParameters,
Multimap<String, JSType> typeMultimap) {
if (this.rawType != other.rawType) {
other = other.findMatchingAncestorWith(this);
if (other == null) {
return false;
}
if (!isGeneric()) {
Expand All @@ -376,23 +377,50 @@ boolean unifyWithSubtype(NominalType other, List<String> typeParameters,
return true;
}
boolean hasUnified = true;
for (String typeParam : rawType.typeParameters) {
hasUnified = hasUnified
&& typeMap.get(typeParam).unifyWithSubtype(
other.typeMap.get(typeParam), typeParameters, typeMultimap);
for (String typeParam : this.rawType.typeParameters) {
JSType fromOtherMap = other.typeMap.get(typeParam);
Preconditions.checkNotNull(fromOtherMap,
"Type variable %s not found in map %s", typeParam, other.typeMap);
hasUnified = hasUnified && this.typeMap.get(typeParam)
.unifyWithSubtype(fromOtherMap, typeParameters, typeMultimap);
}
return hasUnified && isInvariantWith(typeMultimap, other);
}

// Returns a type with the same raw type as other, but possibly different type maps.
private NominalType findMatchingAncestorWith(NominalType other) {
RawNominalType thisRaw = this.rawType;
if (thisRaw == other.rawType) {
return this;
}
if (isInterface() && other.isClass()) {
return null;
}
if (other.isClass()) {
if (thisRaw.superClass == null) {
return null;
}
return getInstantiatedSuperclass().findMatchingAncestorWith(other);
}
for (NominalType interf : getInstantiatedInterfaces()) {
NominalType nt = interf.findMatchingAncestorWith(other);
if (nt != null) {
return nt;
}
}
return null;
}

private boolean isInvariantWith(Multimap<String, JSType> typeMultimap, NominalType other) {
Preconditions.checkState(isGeneric());
Preconditions.checkState(this.rawType == other.rawType);
Map<String, JSType> newTypeMap = new LinkedHashMap<>();
for (String typeVar : typeMultimap.keySet()) {
Collection<JSType> c = typeMultimap.get(typeVar);
if (c.size() != 1) {
return false;
}
newTypeMap.put(typeVar, Iterables.getOnlyElement(c));
newTypeMap.put(typeVar, Preconditions.checkNotNull(Iterables.getOnlyElement(c)));
}
NominalType instantiated = instantiateGenerics(newTypeMap);
return Objects.equals(instantiated.typeMap, other.typeMap);
Expand Down Expand Up @@ -440,6 +468,11 @@ public static class RawNominalType extends Namespace {
private PersistentMap<String, Property> protoProps = PersistentMap.create();
// The "static" properties of the constructor are stored in the namespace.
boolean isFinalized = false;
// Consider a generic type A<T> which inherits from a generic type B<T>.
// All instantiated A classes, such as A<number>, A<string>, etc,
// have the same superclass and interfaces fields, because they have the
// same raw type. You need to instantiate these fields to get the correct
// type maps, eg, see NominalType#isSubtypeOf.
private NominalType superClass = null;
private ImmutableSet<NominalType> interfaces = null;
private final boolean isInterface;
Expand Down Expand Up @@ -529,6 +562,11 @@ public boolean isDict() {
return objectKind.isDict();
}

private boolean isRawSubtypeOf(RawNominalType other) {
return other.isClass() ? isClass() && hasAncestorClass(other)
: hasAncestorInterface(other);
}

ImmutableList<String> getTypeParameters() {
return typeParameters;
}
Expand Down
4 changes: 2 additions & 2 deletions src/com/google/javascript/jscomp/newtypes/ObjectType.java
Expand Up @@ -458,7 +458,7 @@ boolean isSubtypeOf(boolean keepLoosenessOfThis, ObjectType obj2) {

if ((this.nominalType == null && obj2.nominalType != null)
|| this.nominalType != null && obj2.nominalType != null &&
!this.nominalType.isSubclassOf(obj2.nominalType)) {
!this.nominalType.isSubtypeOf(obj2.nominalType)) {
return false;
}

Expand Down Expand Up @@ -671,7 +671,7 @@ private static boolean areRelatedClasses(NominalType c1, NominalType c2) {
if (c1 == null || c2 == null) {
return true;
}
return c1.isSubclassOf(c2) || c2.isSubclassOf(c1);
return c1.isSubtypeOf(c2) || c2.isSubtypeOf(c1);
}

// TODO(dimvar): handle greatest lower bound of interface types.
Expand Down

0 comments on commit d536a7d

Please sign in to comment.