private boolean hasAlternate(JSType type, EquivalenceMethod eqMethod, EqCache eqCache) { List<JSType> alternatesRetainingStructuralSubtypes = getAlternatesWithoutStructuralTyping(); for (int i = 0; i < alternatesRetainingStructuralSubtypes.size(); i++) { JSType alternate = alternatesRetainingStructuralSubtypes.get(i); if (alternate.checkEquivalenceHelper(type, eqMethod, eqCache)) { return true; } } return false; }
/** * Creates a union. * @return A UnionType if it has two or more alternates, the * only alternate if it has one and otherwise {@code NO_TYPE}. */ public JSType build() { if (result == null) { result = reduceAlternatesWithoutUnion(); if (result == null) { result = new UnionType(registry, ImmutableList.copyOf(getAlternates())); } } return result; } }
boolean canCastToUnion(JSType thisType, UnionType unionType) { for (JSType type : unionType.getAlternates()) { if (thisType.visit(this, type)) { return true; } } return false; }
@Override public boolean isStruct() { return anyMatch(JSType::isStruct, getAlternates()); }
/** * Two union types are equal if, after flattening nested union types, * they have the same number of alternates and all alternates are equal. */ boolean checkUnionEquivalenceHelper( UnionType that, EquivalenceMethod eqMethod, EqCache eqCache) { Collection<JSType> thatAlternates = that.getAlternatesWithoutStructuralTyping(); if (eqMethod == EquivalenceMethod.IDENTITY && getAlternatesWithoutStructuralTyping().size() != thatAlternates.size()) { return false; } for (JSType alternate : thatAlternates) { if (!hasAlternate(alternate, eqMethod, eqCache)) { return false; } } return true; }
@Override public Boolean caseUnionType(UnionType thisType, JSType thatType) { boolean visited = false; for (JSType type : thisType.getAlternates()) { if (type.isVoidType() || type.isNullType()) { // Don't allow if the only match between the types is null or void, // otherwise any nullable type would be castable to any other nullable // type and we don't want that. } else { visited = true; if (type.visit(this, thatType)) { return true; } } } // Special case the "null|undefined" union and allow it to be cast // to any cast to any type containing allowing either null|undefined. if (!visited) { JSType NULL_TYPE = thisType.getNativeType(JSTypeNative.NULL_TYPE); JSType VOID_TYPE = thisType.getNativeType(JSTypeNative.VOID_TYPE); return NULL_TYPE.visit(this, thatType) || VOID_TYPE.visit(this, thatType); } return false; }
@Override public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); }
JSType meet(JSType that) { UnionTypeBuilder builder = UnionTypeBuilder.create(registry); for (int i = 0; i < alternatesRetainingStructuralSubtypes.size(); i++) { JSType alternate = alternatesRetainingStructuralSubtypes.get(i); if (alternate.isSubtypeOf(that)) { builder.addAlternate(alternate); } } if (that.isUnionType()) { List<JSType> thoseAlternatesWithoutStucturalTyping = that.toMaybeUnionType().alternatesRetainingStructuralSubtypes; for (int i = 0; i < thoseAlternatesWithoutStucturalTyping.size(); i++) { JSType otherAlternate = thoseAlternatesWithoutStucturalTyping.get(i); if (otherAlternate.isSubtypeOf(this)) { builder.addAlternate(otherAlternate); } } } else if (that.isSubtypeOf(this)) { builder.addAlternate(that); } JSType result = builder.build(); if (!result.isNoType()) { return result; } else if (this.isObject() && (that.isObject() && !that.isNoType())) { return getNativeType(JSTypeNative.NO_OBJECT_TYPE); } else { return getNativeType(JSTypeNative.NO_TYPE); } }
/** * Gets the least supertype of {@code this} and {@code that}. * The least supertype is the join (∨) or supremum of both types in the * type lattice.<p> * Examples: * <ul> * <li><code>number ∨ *</code> = {@code *}</li> * <li><code>number ∨ Object</code> = {@code (number, Object)}</li> * <li><code>Number ∨ Object</code> = {@code Object}</li> * </ul> * @return <code>this ∨ that</code> */ public JSType getLeastSupertype(JSType that) { if (that.isUnionType()) { // Union types have their own implementation of getLeastSupertype. return that.toMaybeUnionType().getLeastSupertype(this); } return getLeastSupertype(this, that); }
/** * Computes the subset of {@code this} and {@code that} types under * shallow inequality. * * @return A pair containing the restricted type of {@code this} as the first * component and the restricted type of {@code that} as the second * element. The returned pair is never {@code null} even though its * components may be {@code null} */ public TypePair getTypesUnderShallowInequality(JSType that) { // union types if (that.isUnionType()) { TypePair p = that.toMaybeUnionType().getTypesUnderShallowInequality(this); return new TypePair(p.typeB, p.typeA); } // Other types. // There are only two types whose shallow inequality is deterministically // true -- null and undefined. We can just enumerate them. if ((isNullType() && that.isNullType()) || (isVoidType() && that.isVoidType())) { return new TypePair(null, null); } else { return new TypePair(this, that); } }
/** * Computes the subset of {@code this} and {@code that} types if equality * is observed. If a value {@code v1} of type {@code null} is equal to a value * {@code v2} of type {@code (undefined,number)}, we can infer that the * type of {@code v1} is {@code null} and the type of {@code v2} is * {@code undefined}. * * @return a pair containing the restricted type of {@code this} as the first * component and the restricted type of {@code that} as the second * element. The returned pair is never {@code null} even though its * components may be {@code null} */ public TypePair getTypesUnderEquality(JSType that) { // unions types if (that.isUnionType()) { TypePair p = that.toMaybeUnionType().getTypesUnderEquality(this); return new TypePair(p.typeB, p.typeA); } // other types switch (testForEquality(that)) { case FALSE: return new TypePair(null, null); case TRUE: case UNKNOWN: return new TypePair(this, that); } // switch case is exhaustive throw new IllegalStateException(); }
@Override public JSType collapseUnion() { JSType currentValue = null; ObjectType currentCommonSuper = null; for (int i = 0; i < alternatesRetainingStructuralSubtypes.size(); i++) { JSType a = alternatesRetainingStructuralSubtypes.get(i); if (a.isUnknownType()) { return getNativeType(JSTypeNative.UNKNOWN_TYPE); } ObjectType obj = a.toObjectType(); if (obj == null) { if (currentValue == null && currentCommonSuper == null) { // If obj is not an object, then it must be a value. currentValue = a; } else { // Multiple values and objects will always collapse to the ALL_TYPE. return getNativeType(JSTypeNative.ALL_TYPE); } } else if (currentValue != null) { // Values and objects will always collapse to the ALL_TYPE. return getNativeType(JSTypeNative.ALL_TYPE); } else if (currentCommonSuper == null) { currentCommonSuper = obj; } else { currentCommonSuper = registry.findCommonSuperObject(currentCommonSuper, obj); } } return currentCommonSuper; }
/** * Computes the subset of {@code this} and {@code that} types if inequality * is observed. If a value {@code v1} of type {@code number} is not equal to a * value {@code v2} of type {@code (undefined,number)}, we can infer that the * type of {@code v1} is {@code number} and the type of {@code v2} is * {@code number} as well. * * @return a pair containing the restricted type of {@code this} as the first * component and the restricted type of {@code that} as the second * element. The returned pair is never {@code null} even though its * components may be {@code null} */ public TypePair getTypesUnderInequality(JSType that) { // unions types if (that.isUnionType()) { TypePair p = that.toMaybeUnionType().getTypesUnderInequality(this); return new TypePair(p.typeB, p.typeA); } // other types switch (testForEquality(that)) { case TRUE: JSType noType = getNativeType(JSTypeNative.NO_TYPE); return new TypePair(noType, noType); case FALSE: case UNKNOWN: return new TypePair(this, that); } // switch case is exhaustive throw new IllegalStateException(); }
return toMaybeUnionType().checkUnionEquivalenceHelper( that.toMaybeUnionType(), eqMethod, eqCache);
/** * Two union types are equal if, after flattening nested union types, they have the same number of * alternatesCollapsingStructuralSubtypes and all alternatesCollapsingStructuralSubtypes are * equal. */ boolean checkUnionEquivalenceHelper(UnionType that, EquivalenceMethod eqMethod, EqCache eqCache) { List<JSType> thatAlternates = that.getAlternatesWithoutStructuralTyping(); if (eqMethod == EquivalenceMethod.IDENTITY && getAlternatesWithoutStructuralTyping().size() != thatAlternates.size()) { return false; } for (int i = 0; i < thatAlternates.size(); i++) { JSType thatAlternate = thatAlternates.get(i); if (!hasAlternate(thatAlternate, eqMethod, eqCache)) { return false; } } return true; }
@Override public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); }
@Override public Boolean caseUnionType(UnionType thisType, JSType thatType) { boolean visited = false; for (JSType type : thisType.getAlternates()) { if (type.isVoidType() || type.isNullType()) { // Don't allow if the only match between the types is null or void, // otherwise any nullable type would be castable to any other nullable // type and we don't want that. } else { visited = true; if (type.visit(this, thatType)) { return true; } } } // Special case the "null|undefined" union and allow it to be cast // to any cast to any type containing allowing either null|undefined. if (!visited) { JSType NULL_TYPE = thisType.getNativeType(JSTypeNative.NULL_TYPE); JSType VOID_TYPE = thisType.getNativeType(JSTypeNative.VOID_TYPE); return NULL_TYPE.visit(this, thatType) || VOID_TYPE.visit(this, thatType); } return false; }
JSType meet(JSType that) { UnionTypeBuilder builder = new UnionTypeBuilder(registry); for (JSType alternate : alternatesWithoutStucturalTyping) { if (alternate.isSubtype(that)) { builder.addAlternate(alternate); } } if (that.isUnionType()) { for (JSType otherAlternate : that.toMaybeUnionType().alternatesWithoutStucturalTyping) { if (otherAlternate.isSubtype(this)) { builder.addAlternate(otherAlternate); } } } else if (that.isSubtype(this)) { builder.addAlternate(that); } JSType result = builder.build(); if (!result.isNoType()) { return result; } else if (this.isObject() && (that.isObject() && !that.isNoType())) { return getNativeType(JSTypeNative.NO_OBJECT_TYPE); } else { return getNativeType(JSTypeNative.NO_TYPE); } }
@Override public boolean isDict() { return anyMatch(JSType::isDict, getAlternates()); }
@Override public JSType getLeastSupertype(JSType that) { if (!that.isUnknownType() && !that.isUnionType()) { for (JSType alternate : alternatesWithoutStucturalTyping) { if (!alternate.isUnknownType() && that.isSubtype(alternate)) { return this; } } } return getLeastSupertype(this, that); }