com.google.javascript.rhino.jstype.JSType Maven / Gradle / Ivy
Show all versions of closure-compiler-unshaded Show documentation
/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.javascript.rhino.jstype.JSTypeIterations.allTypesMatch;
import static com.google.javascript.rhino.jstype.JSTypeIterations.anyTypeMatches;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.errorprone.annotations.ForOverride;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.jstype.ObjectType.PropertyOptionality;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* Represents JavaScript value types.
*
* Types are split into two separate families: value types and object types.
*
*
A special {@link UnknownType} exists to represent a wildcard type on which no information can
* be gathered. In particular, it can assign to everyone, is a subtype of everyone (and everyone is
* a subtype of it).
*
*
If you remove the {@link UnknownType}, the set of types in the type system forms a lattice
* with the {@link #isSubtype} relation defining the partial order of types. All types are united at
* the top of the lattice by the {@link AllType} and at the bottom by the {@link NoType}.
*
*
*
*/
public abstract class JSType implements Serializable {
private static final long serialVersionUID = 1L;
@SuppressWarnings("ReferenceEquality")
static final boolean areIdentical(JSType a, JSType b) {
return a == b;
}
private boolean resolved = false;
private JSType resolveResult = null;
protected TemplateTypeMap templateTypeMap;
private boolean hashCodeInProgress = false;
private boolean inTemplatedCheckVisit = false;
private static final CanCastToVisitor CAN_CAST_TO_VISITOR =
new CanCastToVisitor();
private static final ImmutableSet BIVARIANT_TYPES =
ImmutableSet.of("Object", "IArrayLike", "Array");
// NB: IThenable and all subtypes are also covariant; however, they are handled differently.
private static final ImmutableSet COVARIANT_TYPES =
ImmutableSet.of("Iterable", "Iterator", "Generator", "AsyncIterator", "AsyncIterable");
final JSTypeRegistry registry;
JSType(JSTypeRegistry registry) {
this(registry, null);
}
JSType(JSTypeRegistry registry, TemplateTypeMap templateTypeMap) {
this.registry = registry;
this.templateTypeMap =
(templateTypeMap == null) ? registry.getEmptyTemplateTypeMap() : templateTypeMap;
}
/**
* Utility method for less verbose code.
*/
JSType getNativeType(JSTypeNative typeId) {
return registry.getNativeType(typeId);
}
/**
* Gets the docInfo for this type. By default, documentation cannot be
* attached to arbitrary types. This must be overridden for
* programmer-defined types.
*/
public JSDocInfo getJSDocInfo() {
return null;
}
/**
* Returns a user meaningful label for the JSType instance. For example,
* Functions and Enums will return their declaration name (if they have one).
* Some types will not have a meaningful display name. Calls to
* hasDisplayName() will return true IFF getDisplayName() will return null
* or a zero length string.
*
* @return the display name of the type, or null if one is not available
*/
public String getDisplayName() {
return null;
}
/**
* @return true if the JSType has a user meaningful label.
*/
public boolean hasDisplayName() {
String displayName = getDisplayName();
return displayName != null && !displayName.isEmpty();
}
/** A tristate value returned from canPropertyBeDefined. */
public enum HasPropertyKind {
ABSENT, // The property is not known to be part of this type
KNOWN_PRESENT, // The properties is known to be defined on a type or its super types
MAYBE_PRESENT; // The property is loosely associated with a type, typically one of its subtypes
public static HasPropertyKind of(boolean has) {
return has ? KNOWN_PRESENT : ABSENT;
}
}
/**
* Checks whether the property is present on the object.
* @param pname The property name.
*/
public HasPropertyKind getPropertyKind(String pname) {
return getPropertyKind(pname, true);
}
/**
* Checks whether the property is present on the object.
* @param pname The property name.
* @param autobox Whether to check for the presents on an autoboxed type
*/
public HasPropertyKind getPropertyKind(String pname, boolean autobox) {
return HasPropertyKind.ABSENT;
}
/**
* Checks whether the property is present on the object.
* @param pname The property name.
*/
public final boolean hasProperty(String pname) {
return !getPropertyKind(pname, false).equals(HasPropertyKind.ABSENT);
}
public boolean isNoType() {
return false;
}
public boolean isNoResolvedType() {
return false;
}
public final boolean isUnresolved() {
return isNoResolvedType();
}
public final boolean isUnresolvedOrResolvedUnknown() {
return isNoResolvedType() || (isNamedType() && isUnknownType());
}
public boolean isNoObjectType() {
return false;
}
public final boolean isEmptyType() {
return isNoType()
|| isNoObjectType()
|| isNoResolvedType()
|| areIdentical(this, registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE));
}
public boolean isNumberObjectType() {
return false;
}
public boolean isNumberValueType() {
return false;
}
/** Whether this is the prototype of a function. */
// TODO(sdh): consider renaming this to isPrototypeObject.
public boolean isFunctionPrototypeType() {
return false;
}
public boolean isStringObjectType() {
return false;
}
public boolean isSymbolObjectType() {
return false;
}
boolean isTheObjectType() {
return false;
}
public boolean isStringValueType() {
return false;
}
public boolean isSymbolValueType() {
return false;
}
/**
* Tests whether the type is a string (value or Object).
* @return this <: (String, string)
*/
public final boolean isString() {
return isSubtypeOf(getNativeType(JSTypeNative.STRING_TYPE))
|| isSubtypeOf(getNativeType(JSTypeNative.STRING_OBJECT_TYPE));
}
/**
* Tests whether the type is a number (value or Object).
* @return this <: (Number, number)
*/
public final boolean isNumber() {
return isSubtypeOf(getNativeType(JSTypeNative.NUMBER_TYPE))
|| isSubtypeOf(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE));
}
public final boolean isSymbol() {
return isSubtypeOf(getNativeType(JSTypeNative.SYMBOL_TYPE))
|| isSubtypeOf(getNativeType(JSTypeNative.SYMBOL_OBJECT_TYPE));
}
public boolean isArrayType() {
return false;
}
public boolean isBooleanObjectType() {
return false;
}
public boolean isBooleanValueType() {
return false;
}
public boolean isRegexpType() {
return false;
}
public boolean isDateType() {
return false;
}
public boolean isNullType() {
return false;
}
public boolean isVoidType() {
return false;
}
public boolean isAllType() {
return false;
}
public boolean isUnknownType() {
return false;
}
public final boolean isSomeUnknownType() {
// OTI's notion of isUnknownType already accounts for looseness (see override in ObjectType).
return isUnknownType();
}
public boolean isCheckedUnknownType() {
return false;
}
public final boolean isUnionType() {
return toMaybeUnionType() != null;
}
public final boolean isRawTypeOfTemplatizedType() {
return this.getTemplateParamCount() > 0 && !this.isTemplatizedType();
}
/**
* Returns true iff {@code this} can be a {@code struct}.
* UnionType overrides the method, assume {@code this} is not a union here.
*/
public boolean isStruct() {
if (isObject()) {
ObjectType objType = toObjectType();
FunctionType ctor = objType.getConstructor();
// This test is true for object literals
if (ctor == null) {
JSDocInfo info = objType.getJSDocInfo();
return info != null && info.makesStructs();
} else {
return ctor.makesStructs();
}
}
return false;
}
/**
* Returns true iff {@code this} can be a {@code dict}.
* UnionType overrides the method, assume {@code this} is not a union here.
*/
public boolean isDict() {
if (isObject()) {
ObjectType objType = toObjectType();
FunctionType ctor = objType.getConstructor();
// This test is true for object literals
if (ctor == null) {
JSDocInfo info = objType.getJSDocInfo();
return info != null && info.makesDicts();
} else {
return ctor.makesDicts();
}
}
return false;
}
/**
* This function searchers for a type `target` in the reference chain of ProxyObjectTypes
*/
public final boolean containsReferenceAncestor(JSType target) {
ContainsUpperBoundSuperTypeVisitor visitor = new ContainsUpperBoundSuperTypeVisitor(target);
return this.visit(visitor) == ContainsUpperBoundSuperTypeVisitor.Result.PRESENT;
}
public final boolean isLiteralObject() {
if (this instanceof PrototypeObjectType) {
return ((PrototypeObjectType) this).isAnonymous();
}
return false;
}
public JSType getGreatestSubtypeWithProperty(String propName) {
return this.registry.getGreatestSubtypeWithProperty(this, propName);
}
/**
* Downcasts this to a UnionType, or returns null if this is not a UnionType.
*
* Named in honor of Haskell's Maybe type constructor.
*/
public UnionType toMaybeUnionType() {
return null;
}
/** Returns true if this is a global this type. */
public final boolean isGlobalThisType() {
return areIdentical(this, registry.getNativeType(JSTypeNative.GLOBAL_THIS));
}
/** Returns true if toMaybeFunctionType returns a non-null FunctionType. */
public final boolean isFunctionType() {
return toMaybeFunctionType() != null;
}
/**
* Downcasts this to a FunctionType, or returns null if this is not a function.
*
* For the purposes of this function, we define a MaybeFunctionType as any type in the
* sub-lattice { x | LEAST_FUNCTION_TYPE <= x <= GREATEST_FUNCTION_TYPE } This definition
* excludes bottom types like NoType and NoObjectType.
*
*
This definition is somewhat arbitrary and axiomatic, but this is the definition that makes
* the most sense for the most callers.
*/
@SuppressWarnings("AmbiguousMethodReference")
public FunctionType toMaybeFunctionType() {
return null;
}
/** Returns this object cast to FunctionType or throws an exception if it isn't a FunctionType. */
public FunctionType assertFunctionType() {
FunctionType result = checkNotNull(toMaybeFunctionType(), "not a FunctionType: %s", this);
return result;
}
/** Returns this object cast to ObjectType or throws an exception if it isn't an ObjectType. */
public ObjectType assertObjectType() {
ObjectType result = checkNotNull(toMaybeObjectType(), "Not an ObjectType: %s", this);
return result;
}
/** Null-safe version of toMaybeFunctionType(). */
@SuppressWarnings("AmbiguousMethodReference")
public static FunctionType toMaybeFunctionType(JSType type) {
return type == null ? null : type.toMaybeFunctionType();
}
public final boolean isEnumElementType() {
return toMaybeEnumElementType() != null;
}
public final JSType getEnumeratedTypeOfEnumElement() {
EnumElementType e = toMaybeEnumElementType();
return e == null ? null : e.getPrimitiveType();
}
/**
* Downcasts this to an EnumElementType, or returns null if this is not an EnumElementType.
*/
public EnumElementType toMaybeEnumElementType() {
return null;
}
// TODO(sdh): Consider changing this to isEnumObjectType(), though this would be inconsistent with
// the EnumType class and toMaybeEnumType(), so we would need to consider changing them, too.
public boolean isEnumType() {
return toMaybeEnumType() != null;
}
/**
* Downcasts this to an EnumType, or returns null if this is not an EnumType.
*/
public EnumType toMaybeEnumType() {
return null;
}
public boolean isNamedType() {
return toMaybeNamedType() != null;
}
public NamedType toMaybeNamedType() {
return null;
}
public boolean isRecordType() {
return toMaybeRecordType() != null;
}
public boolean isStructuralInterface() {
return false;
}
public boolean isStructuralType() {
return false;
}
/**
* Downcasts this to a RecordType, or returns null if this is not
* a RecordType.
*/
public RecordType toMaybeRecordType() {
return null;
}
public final boolean isTemplatizedType() {
return toMaybeTemplatizedType() != null;
}
/**
* Downcasts this to a TemplatizedType, or returns null if this is not
* a function.
*/
public TemplatizedType toMaybeTemplatizedType() {
return null;
}
public final boolean isTemplateType() {
return toMaybeTemplateType() != null;
}
public final boolean isTypeVariable() {
return isTemplateType();
}
/**
* Downcasts this to a TemplateType, or returns null if this is not
* a function.
*/
public TemplateType toMaybeTemplateType() {
return null;
}
public boolean hasAnyTemplateTypes() {
if (!this.inTemplatedCheckVisit) {
this.inTemplatedCheckVisit = true;
boolean result = hasAnyTemplateTypesInternal();
this.inTemplatedCheckVisit = false;
return result;
} else {
// prevent infinite recursion, this is "not yet".
return false;
}
}
boolean hasAnyTemplateTypesInternal() {
return getTemplateTypeMap().hasAnyTemplateTypesInternal();
}
/**
* Returns the template type map associated with this type.
*/
public TemplateTypeMap getTemplateTypeMap() {
return templateTypeMap;
}
/**
* Return, in order, the sequence of type parameters declared for this type.
*
*
In general, this value corresponds to an element for every `@template` declaration on the
* type definition. It does not include template parameters from superclasses or superinterfaces.
*/
public final ImmutableList getTypeParameters() {
TemplateTypeMap map = getTemplateTypeMap();
return map.getTemplateKeys().subList(map.size() - getTemplateParamCount(), map.size());
}
/**
* Return the number of template parameters declared for this type.
*
* In general, this value corresponds to the number of `@template` declarations on the type
* definition. It does not include template parameters from superclasses or superinterfaces.
*/
public int getTemplateParamCount() {
return 0;
}
/**
* Prepends the template type map associated with this type, merging in the keys and values of the
* specified map.
*/
public void mergeSupertypeTemplateTypes(ObjectType other) {
if (other.isRawTypeOfTemplatizedType()) {
// Before a type can be prepended it needs to be fully specialized. This can happen, for
// example, when type arguments are not specified in an `@extends` annotation.
other = registry.createTemplatizedType(other, ImmutableList.of());
}
templateTypeMap = other.getTemplateTypeMap().copyWithExtension(this.getTemplateTypeMap());
}
/**
* Tests whether this type is an {@code Object}, or any subtype thereof.
* @return this <: Object
*/
public boolean isObject() {
return false;
}
/**
* Tests whether this type is an {@code Object}, or any subtype thereof.
*
* @return this <: Object
*/
public final boolean isObjectType() {
return isObject();
}
/**
* Whether this type is a {@link FunctionType} that is a constructor or a
* named type that points to such a type.
*/
public boolean isConstructor() {
return false;
}
/**
* Whether this type is a nominal type (a named instance object or
* a named enum).
*/
public boolean isNominalType() {
return false;
}
/**
* Whether this type is the original constructor of a nominal type.
* Does not include structural constructors.
*/
public final boolean isNominalConstructor() {
if (isConstructor() || isInterface()) {
FunctionType fn = toMaybeFunctionType();
if (fn == null) {
return false;
}
// Programmer-defined constructors will have a link
// back to the original function in the source tree.
// Structural constructors will not.
if (fn.getSource() != null) {
return true;
}
// Native constructors are always nominal.
return fn.isNativeObjectType();
}
return false;
}
public boolean isNativeObjectType() {
return false;
}
/**
* Whether this type is an Instance object of some constructor.
* Does not necessarily mean this is an {@link InstanceObjectType}.
*/
public boolean isInstanceType() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an interface or a named
* type that points to such a type.
*/
public boolean isInterface() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an ordinary function (i.e. not a
* constructor, nominal interface, or record interface), or a named type that points to such a
* type.
*/
public boolean isOrdinaryFunction() {
return false;
}
@Override
public boolean equals(@Nullable Object jsType) {
return (jsType instanceof JSType) && isEquivalentTo((JSType) jsType);
}
/** Checks if two types are equivalent. */
public final boolean isEquivalentTo(@Nullable JSType that) {
return isEquivalentTo(that, false);
}
public final boolean isEquivalentTo(@Nullable JSType that, boolean isStructural) {
EqCache eqCache = isStructural ? EqCache.create() : EqCache.createWithoutStructuralTyping();
return checkEquivalenceHelper(that, EquivalenceMethod.IDENTITY, eqCache);
}
public static final boolean isEquivalent(@Nullable JSType typeA, @Nullable JSType typeB) {
return (typeA == null) ? (typeB == null) : typeA.isEquivalentTo(typeB);
}
/**
* Whether this type is meaningfully different from {@code that} type for the purposes of data
* flow analysis.
*
*
This is a trickier check than pure equality, because it has to properly handle unknown
* types. See {@code EquivalenceMethod} for more info.
*/
public final boolean differsFrom(JSType that) {
return !checkEquivalenceHelper(that, EquivalenceMethod.DATA_FLOW, EqCache.create());
}
boolean checkEquivalenceHelper(
final @Nullable JSType that, EquivalenceMethod eqMethod, EqCache eqCache) {
if (that == null) {
return false;
} else if (areIdentical(this, that)) {
return true;
}
if (this.isNoResolvedType() && that.isNoResolvedType()) {
if (this.isNamedType() && that.isNamedType()) {
return Objects.equals(
this.toMaybeNamedType().getReferenceName(), //
that.toMaybeNamedType().getReferenceName());
} else {
return true;
}
}
boolean thisUnknown = isUnknownType();
boolean thatUnknown = that.isUnknownType();
if (thisUnknown || thatUnknown) {
if (eqMethod == EquivalenceMethod.INVARIANT) {
// If we're checking for invariance, the unknown type is invariant
// with everyone.
return true;
} else if (eqMethod == EquivalenceMethod.DATA_FLOW) {
// If we're checking data flow, then two types are the same if they're
// both unknown.
return thisUnknown && thatUnknown;
} else if (thisUnknown && thatUnknown &&
(isNominalType() ^ that.isNominalType())) {
// If they're both unknown, but one is a nominal type and the other
// is not, then we should fail out immediately. This ensures that
// we won't unbox the unknowns further down.
return false;
}
}
if (isUnionType() && that.isUnionType()) {
return toMaybeUnionType().checkUnionEquivalenceHelper(
that.toMaybeUnionType(), eqMethod, eqCache);
}
if (isFunctionType() && that.isFunctionType()) {
return toMaybeFunctionType().checkFunctionEquivalenceHelper(
that.toMaybeFunctionType(), eqMethod, eqCache);
}
if (!getTemplateTypeMap().checkEquivalenceHelper(
that.getTemplateTypeMap(), eqMethod, eqCache, SubtypingMode.NORMAL)) {
return false;
}
// Whether or not we use structural typing to compare object types is based on:
// 1. if eqCache.isStructuralTyping() is true, we always use structural typing
// 2. if we are comparing to anonymous record types (e.g. `{a: 3}`), always use structural types
// Ideally we would also use structural typing to compare anything declared @record, but
// that is harder to do with our current representation of types.
if (eqCache.shouldMatchStructurally(this, that)) {
return toMaybeObjectType()
.checkStructuralEquivalenceHelper(that.toMaybeObjectType(), eqMethod, eqCache);
}
if (isNominalType() && that.isNominalType()) {
JSType thisUnwrapped = unwrapNamedTypeAndTemplatizedType(this.toObjectType());
JSType thatUnwrapped = unwrapNamedTypeAndTemplatizedType(that.toObjectType());
if (isResolved() && that.isResolved()) {
return areIdentical(thisUnwrapped, thatUnwrapped);
}
// TODO(b/140763807): this is not valid across scopes pre-resolution.
@Nullable
String nameOfThis =
thisUnwrapped.toObjectType() != null
? thisUnwrapped.toObjectType().getReferenceName()
: null;
@Nullable
String nameOfThat =
thatUnwrapped.toObjectType() != null
? thatUnwrapped.toObjectType().getReferenceName()
: null;
if ((nameOfThis == null) && (nameOfThat == null)) {
// These are two anonymous types that were masquerading as nominal, so don't compare names.
} else {
return Objects.equals(nameOfThis, nameOfThat);
}
}
if (isTemplateType() && that.isTemplateType()) {
// TemplateType are they same only if they are object identical,
// which we check at the start of this function.
return false;
}
// Unbox other proxies.
if (this instanceof ProxyObjectType) {
return ((ProxyObjectType) this)
.getReferencedTypeInternal().checkEquivalenceHelper(
that, eqMethod, eqCache);
}
if (that instanceof ProxyObjectType) {
return checkEquivalenceHelper(
((ProxyObjectType) that).getReferencedTypeInternal(),
eqMethod, eqCache);
}
// Relies on the fact that for the base {@link JSType}, only one
// instance of each sub-type will ever be created in a given registry, so
// there is no need to verify members. If the object pointers are not
// identical, then the type member must be different.
return false;
}
private static JSType unwrapNamedTypeAndTemplatizedType(ObjectType objType) {
if (!objType.isResolved() || (!objType.isNamedType() && !objType.isTemplatizedType())) {
// Don't unwrap TemplateTypes, as they should use identity semantics even if their bounds
// are compatible. On the other hand, different TemplatizedType instances may be equal if
// their TemplateTypeMaps are compatible (which was checked for earlier).
return objType;
}
ObjectType internal =
objType.isNamedType()
? objType.toMaybeNamedType().getReferencedObjTypeInternal()
: objType.toMaybeTemplatizedType().getReferencedObjTypeInternal();
return (internal != null && internal.isNominalType())
? unwrapNamedTypeAndTemplatizedType(internal)
: internal;
}
/**
* Calculates a hash of the object as per {@link Object#hashCode()}.
*
*
This method is unsafe for multi-threaded use. The implementation mutates instance
* state to prevent recursion and therefore expects sole access.
*/
@Override
public final int hashCode() {
if (hashCodeInProgress) {
return -1; // Recursive base-case.
}
this.hashCodeInProgress = true;
int hashCode = recursionUnsafeHashCode();
this.hashCodeInProgress = false;
return hashCode;
}
/**
* Calculates {@code #hashCode()} with the assumption that it will never be called recursively.
*
*
To work correctly this method should only called under the following conditions:
*
*
* - by a subclass of {@link JSType};
*
- within the body of an override;
*
- when delegating to a superclass implementation.
*
*/
abstract int recursionUnsafeHashCode();
/**
* This predicate is used to test whether a given type can appear in a numeric context, such as an
* operand of a multiply operator.
*/
public boolean matchesNumberContext() {
return false;
}
/**
* This predicate is used to test whether a given type can appear in a
* {@code String} context, such as an operand of a string concat (+) operator.
*
* All types have at least the potential for converting to {@code String}.
* When we add externally defined types, such as a browser OM, we may choose
* to add types that do not automatically convert to {@code String}.
*/
public boolean matchesStringContext() {
return false;
}
/**
* This predicate is used to test whether a given type can appear in a
* {@code symbol} context such as property access.
*/
public boolean matchesSymbolContext() {
return false;
}
/**
* This predicate is used to test whether a given type can appear in an
* {@code Object} context, such as the expression in a with statement.
*
* Most types we will encounter, except notably {@code null}, have at least
* the potential for converting to {@code Object}. Host defined objects can
* get peculiar.
*/
public boolean matchesObjectContext() {
return false;
}
/**
* Coerces this type to an Object type, then gets the type of the property whose name is given.
*
* Unlike {@link ObjectType#getPropertyType}, returns null if the property is not found.
*
* @return The property's type. {@code null} if the current type cannot have properties, or if the
* type is not found.
*/
@Nullable
public final JSType findPropertyType(String propertyName) {
@Nullable JSType propertyType = findPropertyTypeWithoutConsideringTemplateTypes(propertyName);
if (propertyType == null) {
return null;
}
// Do templatized type replacing logic here, and make this method final, to prevent a subclass
// from forgetting to replace template types
if (getTemplateTypeMap().isEmpty() || !propertyType.hasAnyTemplateTypes()) {
return propertyType;
}
TemplateTypeMap typeMap = getTemplateTypeMap();
TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap);
return propertyType.visit(replacer);
}
/**
* Looks up a property on this type, but without properly replacing any templates in the result.
*
*
Subclasses can override this if they need more complicated logic for property lookup than
* just autoboxing to an object.
*
*
This is only for use by {@code findPropertyType(JSType)}. Call that method instead if you
* need to lookup a property on a random JSType
*/
@ForOverride
@Nullable
protected JSType findPropertyTypeWithoutConsideringTemplateTypes(String propertyName) {
ObjectType autoboxObjType = ObjectType.cast(autoboxesTo());
if (autoboxObjType != null) {
return autoboxObjType.findPropertyType(propertyName);
}
return null;
}
/**
* This predicate is used to test whether a given type can be used as the
* 'function' in a function call.
*
* @return {@code true} if this type might be callable.
*/
public boolean canBeCalled() {
return false;
}
/**
* Tests whether values of {@code this} type can be safely assigned
* to values of {@code that} type.
*
* The default implementation verifies that {@code this} is a subtype
* of {@code that}.
*/
public final boolean canCastTo(JSType that) {
return this.visit(CAN_CAST_TO_VISITOR, that);
}
/**
* Turn a scalar type to the corresponding object type.
*
* @return the auto-boxed type or {@code null} if this type is not a scalar.
*/
public JSType autoboxesTo() {
return null;
}
public boolean isBoxableScalar() {
return autoboxesTo() != null;
}
// TODO(johnlenz): this method is only used for testing, consider removing this.
/**
* Turn an object type to its corresponding scalar type.
*
* @return the unboxed type or {@code null} if this type does not unbox.
*/
public JSType unboxesTo() {
return null;
}
/**
* Casts this to an ObjectType, or returns null if this is not an ObjectType.
* If this is a scalar type, it will *not* be converted to an object type.
* If you want to simulate JS autoboxing or dereferencing, you should use
* autoboxesTo() or dereference().
*/
public ObjectType toObjectType() {
return this instanceof ObjectType ? (ObjectType) this : null;
}
/**
* Dereferences a type for property access.
*
* Filters null/undefined and autoboxes the resulting type.
* Never returns null.
*/
public JSType autobox() {
JSType restricted = restrictByNotNullOrUndefined();
JSType autobox = restricted.autoboxesTo();
return autobox == null ? restricted : autobox;
}
/**
* Dereferences a type for property access.
*
* Filters null/undefined, autoboxes the resulting type, and returns it
* iff it's an object. If not an object, returns null.
*/
@Nullable
public final ObjectType dereference() {
return autobox().toObjectType();
}
/**
* Tests whether {@code this} and {@code that} are meaningfully
* comparable. By meaningfully, we mean compatible types that do not lead
* to step 22 of the definition of the Abstract Equality Comparison
* Algorithm (11.9.3, page 55–56) of the ECMA-262 specification.
*/
public final boolean canTestForEqualityWith(JSType that) {
return testForEquality(that).equals(TernaryValue.UNKNOWN);
}
/**
* Compares {@code this} and {@code that}.
* @return
* - {@link TernaryValue#TRUE} if the comparison of values of
* {@code this} type and {@code that} always succeed (such as
* {@code undefined} compared to {@code null})
* - {@link TernaryValue#FALSE} if the comparison of values of
* {@code this} type and {@code that} always fails (such as
* {@code undefined} compared to {@code number})
* - {@link TernaryValue#UNKNOWN} if the comparison can succeed or
* fail depending on the concrete values
*
*/
public TernaryValue testForEquality(JSType that) {
return testForEqualityHelper(this, that);
}
final TernaryValue testForEqualityHelper(JSType aType, JSType bType) {
if (bType.isAllType() || bType.isUnknownType() ||
bType.isNoResolvedType() ||
aType.isAllType() || aType.isUnknownType() ||
aType.isNoResolvedType()) {
return TernaryValue.UNKNOWN;
}
boolean aIsEmpty = aType.isEmptyType();
boolean bIsEmpty = bType.isEmptyType();
if (aIsEmpty || bIsEmpty) {
if (aIsEmpty && bIsEmpty) {
return TernaryValue.TRUE;
} else {
return TernaryValue.UNKNOWN;
}
}
if (aType.isFunctionType() || bType.isFunctionType()) {
JSType otherType = aType.isFunctionType() ? bType : aType;
// TODO(johnlenz): tighten function type comparisons in general.
if (otherType.isSymbol()) {
return TernaryValue.FALSE;
}
// In theory, functions are comparable to anything except
// null/undefined. For example, on FF3:
// function() {} == 'function () {\n}'
// In practice, how a function serializes to a string is
// implementation-dependent, so it does not really make sense to test
// for equality with a string.
JSType greatestSubtype =
otherType.getGreatestSubtype(getNativeType(JSTypeNative.OBJECT_TYPE));
if (greatestSubtype.isNoType() || greatestSubtype.isNoObjectType()) {
return TernaryValue.FALSE;
} else {
return TernaryValue.UNKNOWN;
}
}
if (bType.isEnumElementType() || bType.isUnionType()) {
return bType.testForEquality(aType);
}
// If this is a "Symbol" or that is "symbol" or "Symbol"
if (aType.isSymbol()) {
return bType.canCastTo(getNativeType(JSTypeNative.SYMBOL_TYPE))
|| bType.canCastTo(getNativeType(JSTypeNative.SYMBOL_OBJECT_TYPE))
? TernaryValue.UNKNOWN
: TernaryValue.FALSE;
}
if (bType.isSymbol()) {
return aType.canCastTo(getNativeType(JSTypeNative.SYMBOL_TYPE))
|| aType.canCastTo(getNativeType(JSTypeNative.SYMBOL_OBJECT_TYPE))
? TernaryValue.UNKNOWN
: TernaryValue.FALSE;
}
return null;
}
/**
* Tests whether {@code this} and {@code that} are meaningfully
* comparable using shallow comparison. By meaningfully, we mean compatible
* types that are not rejected by step 1 of the definition of the Strict
* Equality Comparison Algorithm (11.9.6, page 56–57) of the
* ECMA-262 specification.
*/
public final boolean canTestForShallowEqualityWith(JSType that) {
if (isEmptyType() || that.isEmptyType()) {
return isSubtypeOf(that) || that.isSubtypeOf(this);
}
JSType inf = getGreatestSubtype(that);
return !inf.isEmptyType()
||
// Our getGreatestSubtype relation on functions is pretty bad.
// Let's just say it's always ok to compare two functions.
// Once the TODO in FunctionType is fixed, we should be able to
// remove this.
areIdentical(inf, registry.getNativeType(JSTypeNative.LEAST_FUNCTION_TYPE));
}
/**
* Tests whether this type is nullable.
*/
public boolean isNullable() {
return false;
}
/**
* Tests whether this type is voidable.
*/
public boolean isVoidable() {
return false;
}
/**
* Tests whether this type explicitly allows undefined, as opposed to ? or *. This is required for
* a property to be optional.
*/
public boolean isExplicitlyVoidable() {
return false;
}
/**
* Gets the least supertype of this that's not a union.
*/
public JSType collapseUnion() {
return this;
}
/**
* 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.
*
*
Examples:
*
*
* number ∨ *
= {@code *}
* number ∨ Object
= {@code (number, Object)}
* Number ∨ Object
= {@code Object}
*
*
* @return this ∨ that
*/
@SuppressWarnings("AmbiguousMethodReference")
public JSType getLeastSupertype(JSType that) {
if (areIdentical(this, that)) {
return this;
}
that = filterNoResolvedType(that);
if (that.isUnionType()) {
// Union types have their own implementation of getLeastSupertype.
return that.toMaybeUnionType().getLeastSupertype(this);
}
return getLeastSupertype(this, that);
}
/**
* A generic implementation meant to be used as a helper for common getLeastSupertype
* implementations.
*/
@SuppressWarnings("AmbiguousMethodReference")
static JSType getLeastSupertype(JSType thisType, JSType thatType) {
boolean areEquivalent = thisType.isEquivalentTo(thatType);
return areEquivalent ? thisType :
filterNoResolvedType(
thisType.registry.createUnionType(thisType, thatType));
}
/**
* Gets the greatest subtype of {@code this} and {@code that}. The greatest subtype is the meet
* (∧) or infimum of both types in the type lattice.
*
* Examples
*
*
* Number ∧ Any
= {@code Any}
* number ∧ Object
= {@code Any}
* Number ∧ Object
= {@code Number}
*
*
* @return this ∨ that
*/
@SuppressWarnings("AmbiguousMethodReference")
public JSType getGreatestSubtype(JSType that) {
return getGreatestSubtype(this, that);
}
/**
* A generic implementation meant to be used as a helper for common getGreatestSubtype
* implementations.
*/
@SuppressWarnings("AmbiguousMethodReference")
static JSType getGreatestSubtype(JSType thisType, JSType thatType) {
if (thisType.isFunctionType() && thatType.isFunctionType()) {
// The FunctionType sub-lattice is not well-defined. i.e., the
// proposition
// A < B => sup(A, B) == B
// does not hold because of unknown parameters and return types.
// See the comment in supAndInfHelper for more info on this.
return thisType.toMaybeFunctionType().supAndInfHelper(
thatType.toMaybeFunctionType(), false);
} else if (thisType.isEquivalentTo(thatType)) {
return thisType;
} else if (thisType.isUnknownType() || thatType.isUnknownType()) {
// The greatest subtype with any unknown type is the universal
// unknown type, unless the two types are equal.
return thisType.isEquivalentTo(thatType) ? thisType :
thisType.getNativeType(JSTypeNative.UNKNOWN_TYPE);
} else if (thisType.isUnionType()) {
return UnionType.getGreatestSubtype(thisType.toMaybeUnionType(), thatType);
} else if (thatType.isUnionType()) {
return UnionType.getGreatestSubtype(thatType.toMaybeUnionType(), thisType);
} else if (thisType.isTemplatizedType()) {
return thisType.toMaybeTemplatizedType().getGreatestSubtypeHelper(
thatType);
} else if (thatType.isTemplatizedType()) {
return thatType.toMaybeTemplatizedType().getGreatestSubtypeHelper(
thisType);
} else if (thisType.isSubtypeOf(thatType)) {
return filterNoResolvedType(thisType);
} else if (thatType.isSubtypeOf(thisType)) {
return filterNoResolvedType(thatType);
} else if (thisType.isRecordType()) {
return thisType.toMaybeRecordType().getGreatestSubtypeHelper(thatType);
} else if (thatType.isRecordType()) {
return thatType.toMaybeRecordType().getGreatestSubtypeHelper(thisType);
}
if (thisType.isEnumElementType()) {
JSType inf = EnumElementType.getGreatestSubtype(thisType.toMaybeEnumElementType(), thatType);
if (inf != null) {
return inf;
}
} else if (thatType.isEnumElementType()) {
JSType inf = EnumElementType.getGreatestSubtype(thatType.toMaybeEnumElementType(), thisType);
if (inf != null) {
return inf;
}
}
if (thisType.isObject() && thatType.isObject()) {
return thisType.getNativeType(JSTypeNative.NO_OBJECT_TYPE);
}
return thisType.getNativeType(JSTypeNative.NO_TYPE);
}
/**
* When computing infima, we may get a situation like
* inf(Type1, Type2)
* where both types are unresolved, so they're technically
* subtypes of one another.
*
* If this happens, filter them down to NoResolvedType.
*/
static JSType filterNoResolvedType(JSType type) {
if (type.isNoResolvedType()) {
// inf(UnresolvedType1, UnresolvedType2) needs to resolve
// to the base unresolved type, so that the relation is symmetric.
return type.getNativeType(JSTypeNative.NO_RESOLVED_TYPE);
} else if (type.isUnionType()) {
UnionType unionType = type.toMaybeUnionType();
boolean needsFiltering = false;
ImmutableList alternatesList = unionType.getAlternates();
for (int i = 0; i < alternatesList.size(); i++) {
JSType alt = alternatesList.get(i);
if (alt.isNoResolvedType()) {
needsFiltering = true;
break;
}
}
if (needsFiltering) {
UnionType.Builder builder =
UnionType.builder(type.registry)
.addAlternate(type.getNativeType(JSTypeNative.NO_RESOLVED_TYPE));
for (int i = 0; i < alternatesList.size(); i++) {
JSType alt = alternatesList.get(i);
if (!alt.isNoResolvedType()) {
builder.addAlternate(alt);
}
}
return builder.build();
}
}
return type;
}
/**
* Computes the restricted type of this type knowing that the
* {@code ToBoolean} predicate has a specific value. For more information
* about the {@code ToBoolean} predicate, see
* {@link #getPossibleToBooleanOutcomes}.
*
* @param outcome the value of the {@code ToBoolean} predicate
*
* @return the restricted type, or the Any Type if the underlying type could
* not have yielded this ToBoolean value
*
* TODO(user): Move this method to the SemanticRAI and use the visit
* method of types to get the restricted type.
*/
public JSType getRestrictedTypeGivenToBooleanOutcome(boolean outcome) {
if (outcome && areIdentical(this, getNativeType(JSTypeNative.UNKNOWN_TYPE))) {
return getNativeType(JSTypeNative.CHECKED_UNKNOWN_TYPE);
}
BooleanLiteralSet literals = getPossibleToBooleanOutcomes();
if (literals.contains(outcome)) {
return this;
} else {
return getNativeType(JSTypeNative.NO_TYPE);
}
}
/**
* Computes the set of possible outcomes of the {@code ToBoolean} predicate
* for this type. The {@code ToBoolean} predicate is defined by the ECMA-262
* standard, 3rd edition. Its behavior for simple types can be
* summarized by the following table:
*
* ToBoolean results by input type
* type result
* {@code undefined} {false}
* {@code null} {false}
* {@code boolean} {true, false}
* {@code number} {true, false}
* {@code string} {true, false}
* {@code Object} {true}
*
* @return the set of boolean literals for this type
*/
public abstract BooleanLiteralSet getPossibleToBooleanOutcomes();
/**
* 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();
}
/**
* 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();
}
/**
* Computes the subset of {@code this} and {@code that} types under shallow
* equality.
*
* @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 getTypesUnderShallowEquality(JSType that) {
JSType commonType = getGreatestSubtype(that);
return new TypePair(commonType, commonType);
}
/**
* 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);
}
}
public Iterable getUnionMembers() {
return isUnionType() ? this.toMaybeUnionType().getAlternates() : null;
}
/**
* If this is a union type, returns a union type that does not include
* the null or undefined type.
*/
public JSType restrictByNotNullOrUndefined() {
return this;
}
/** If this is a union type, returns a union type that does not include the undefined type. */
public JSType restrictByNotUndefined() {
return this;
}
/**
* the logic of this method is similar to isSubtype, except that it does not perform structural
* interface matching
*
* This function is added for disambiguate properties, and is deprecated for the other use
* cases.
*/
public boolean isSubtypeWithoutStructuralTyping(JSType supertype) {
return isSubtype(supertype, ImplCache.createWithoutStructuralTyping(), SubtypingMode.NORMAL);
}
/**
* In files translated from Java, we typecheck null and undefined loosely.
*/
public static enum SubtypingMode {
NORMAL,
IGNORE_NULL_UNDEFINED
}
/**
* Checks whether {@code this} is a subtype of {@code that}.
*
*
Note this function also returns true if this type structurally matches the protocol define
* by that type (if that type is an interface function type)
*
*
Subtyping rules:
*
*
* - (unknown) — every type is a subtype of the Unknown type.
*
- (no) — the No type is a subtype of every type.
*
- (no-object) — the NoObject type is a subtype of every object type (i.e. subtypes of
* the Object type).
*
- (ref) — a type is a subtype of itself.
*
- (union-l) — A union type is a subtype of a type U if all the union type's
* constituents are a subtype of U. Formally
* (T1, …, Tn) <: U
if and only
* Tk <: U
for all k ∈ 1..n
.
* - (union-r) — A type U is a subtype of a union type if it is a subtype of one of the
* union type's constituents. Formally
* U <: (T1, …, Tn)
if and only if
* U <: Tk
for some index {@code k}.
* - (objects) — an Object
O1
is a subtype of an object
* O2
if it has more properties than O2
and all
* common properties are pairwise subtypes.
*
*
* @return this <: that
*/
public boolean isSubtype(JSType supertype) {
return isSubtypeHelper(this, supertype, ImplCache.create(), SubtypingMode.NORMAL);
}
public boolean isSubtype(JSType supertype, SubtypingMode mode) {
return isSubtype(supertype, ImplCache.create(), mode);
}
/**
* checking isSubtype with structural interface matching
*
* @param implicitImplCache a cache that records the checked or currently checking type pairs, for
* example, if previous checking found that constructor C is a subtype of interface I, then in
* the cache, table key {@code } maps to IMPLEMENT status.
*/
protected boolean isSubtype(
JSType supertype, ImplCache implicitImplCache, SubtypingMode subtypingMode) {
return isSubtypeHelper(this, supertype, implicitImplCache, subtypingMode);
}
/** if implicitImplCache is null, there is no structural interface matching */
static boolean isSubtypeHelper(
JSType subtype, JSType supertype, ImplCache implicitImplCache, SubtypingMode subtypingMode) {
checkNotNull(subtype);
// Axiomatic cases.
if (supertype.isUnknownType()
|| supertype.isAllType()
|| subtype.isUnknownType()
|| subtype.isNoType()) {
return true;
}
// Special consideration for `null` and `undefined` in J2CL.
if (subtypingMode == SubtypingMode.IGNORE_NULL_UNDEFINED
&& (subtype.isNullType() || subtype.isVoidType())) {
return true;
}
// Reflexive case.
if (subtype.isEquivalentTo(supertype, implicitImplCache.isStructuralTyping())) {
return true;
}
/**
* Unwrap proxy types.
*
* Only named types are unwrapped because other subclasses of `ProxyObjectType` should not be
* considered proxies; they have additional behaviour. We intentiaionlly call the instance
* `isSubtype` method to call into any class specific subtyping behaviour that isn't present in
* this method.
*/
if (subtype.isNamedType()) {
return subtype
.toMaybeNamedType()
.getReferencedType()
.isSubtype(supertype, implicitImplCache, subtypingMode);
} else if (supertype.isNamedType()) {
return subtype.isSubtype(
supertype.toMaybeNamedType().getReferencedType(), implicitImplCache, subtypingMode);
}
// Union decomposition.
if (subtype.isUnionType()) {
// All alternates must be subtypes.
return allTypesMatch(
(sub) -> sub.isSubtype(supertype, implicitImplCache, subtypingMode),
subtype.toMaybeUnionType());
} else if (supertype.isUnionType()) {
// Some alternate must be a supertype.
return anyTypeMatches(
(sup) -> subtype.isSubtype(sup, implicitImplCache, subtypingMode),
supertype.toMaybeUnionType());
}
if (!subtype.isObjectType() || !supertype.isObjectType()) {
return false; // All cases for non object types have been covered by this point.
}
return isObjectSubtypeHelper(
subtype.assertObjectType(), supertype.assertObjectType(), implicitImplCache, subtypingMode);
}
private static boolean isObjectSubtypeHelper(
ObjectType subtype,
ObjectType supertype,
ImplCache implicitImplCache,
SubtypingMode subtypingMode) {
// TemplateTypeMaps. This check only returns false if the TemplateTypeMaps
// are not equivalent.
TemplateTypeMap subtypeParams = subtype.getTemplateTypeMap();
TemplateTypeMap supertypeParams = supertype.getTemplateTypeMap();
boolean templateMatch = true;
if (isBivariantType(supertype)) {
// Array and Object are exempt from template type invariance; their
// template types maps are bivariant. That is they are considered
// a match if either ObjectElementKey values are subtypes of the
// other.
TemplateType key = subtype.registry.getObjectElementKey();
JSType thisElement = subtypeParams.getResolvedTemplateType(key);
JSType thatElement = supertypeParams.getResolvedTemplateType(key);
templateMatch = thisElement.isSubtype(thatElement, implicitImplCache, subtypingMode)
|| thatElement.isSubtype(thisElement, implicitImplCache, subtypingMode);
} else {
TemplateType covariantKey = getTemplateKeyIfCovariantType(supertype);
if (covariantKey != null) {
JSType thisElement = subtypeParams.getResolvedTemplateType(covariantKey);
JSType thatElement = supertypeParams.getResolvedTemplateType(covariantKey);
templateMatch = thisElement.isSubtype(thatElement, implicitImplCache, subtypingMode);
} else {
templateMatch =
subtypeParams.checkEquivalenceHelper(
supertypeParams, EquivalenceMethod.INVARIANT, subtypingMode);
}
}
if (!templateMatch) {
return false;
}
// If the super type is a structural type, then we can't safely remove a templatized type
// (since it might affect the types of the properties)
PropertyOptionality propOptionality = null;
if (implicitImplCache.shouldMatchStructurally(subtype, supertype)) {
propOptionality = PropertyOptionality.VOIDABLE_PROPS_ARE_OPTIONAL;
} else if (supertype.isRecordType()) {
propOptionality = PropertyOptionality.ALL_PROPS_ARE_REQUIRED;
}
if (propOptionality != null) {
return ObjectType.isStructuralSubtypeHelper(
subtype, supertype, implicitImplCache, subtypingMode, propOptionality);
}
// Interfaces
// Find all the interfaces implemented by this class and compare each one
// to the interface instance.
FunctionType subtypeCtor = subtype.getConstructor();
FunctionType supertypeCtor = supertype.getConstructor();
if (subtypeCtor != null && subtypeCtor.isInterface()) {
for (ObjectType subtypeInterface : subtype.getCtorExtendedInterfaces()) {
// TODO(b/142155372): When fixed, call `isObjectSubtypeHelper` here directly.
if (subtypeInterface.isSubtype(supertype, implicitImplCache, subtypingMode)) {
return true;
}
}
} else if (supertypeCtor != null && supertypeCtor.isInterface()) {
for (ObjectType subtypeInterface : subtype.getCtorImplementedInterfaces()) {
// TODO(b/142155372): When fixed, call `isObjectSubtypeHelper` here directly.
if (subtypeInterface.isSubtype(supertype, implicitImplCache, subtypingMode)) {
return true;
}
}
}
return supertype.isImplicitPrototypeOf(subtype);
}
/**
* Determines if the supplied type should be checked as a bivariant templatized type rather the
* standard invariant templatized type rules.
*/
static boolean isBivariantType(JSType type) {
ObjectType unwrapped = getObjectTypeIfNative(type);
return unwrapped != null && BIVARIANT_TYPES.contains(unwrapped.getReferenceName());
}
/**
* Determines if the specified type should be checked as covariant rather than the standard
* invariant type. If so, returns the template type to check covariantly.
*/
@Nullable
static TemplateType getTemplateKeyIfCovariantType(JSType type) {
if (type.isTemplatizedType()) {
// Unlike other covariant/bivariant types, even non-native subtypes of IThenable are
// covariant, so IThenable is special-cased here.
TemplatizedType ttype = type.toMaybeTemplatizedType();
if (ttype.getTemplateTypeMap().hasTemplateKey(ttype.registry.getIThenableTemplate())) {
return ttype.registry.getIThenableTemplate();
}
}
ObjectType unwrapped = getObjectTypeIfNative(type);
if (unwrapped != null && COVARIANT_TYPES.contains(unwrapped.getReferenceName())) {
// Note: this code only works because the currently covariant types only have a single
// @template. A different solution would be needed to generalize variance for arbitrary types.
return Iterables.getOnlyElement(
unwrapped.getConstructor().getTemplateTypeMap().getTemplateKeys());
}
return null;
}
@Nullable
private static ObjectType getObjectTypeIfNative(JSType type) {
ObjectType objType = type.toObjectType();
ObjectType unwrapped = ObjectType.deeplyUnwrap(objType);
return unwrapped != null && unwrapped.isNativeObjectType() ? unwrapped : null;
}
/**
* Visit this type with the given visitor.
* @see com.google.javascript.rhino.jstype.Visitor
* @return the value returned by the visitor
*/
public abstract T visit(Visitor visitor);
/**
* Visit the types with the given visitor.
* @see com.google.javascript.rhino.jstype.RelationshipVisitor
* @return the value returned by the visitor
*/
abstract T visit(RelationshipVisitor visitor, JSType that);
/**
* Resolve this type in the given scope.
*
* The returned value must be equal to {@code this}, as defined by
* {@link #isEquivalentTo}. It may or may not be the same object. This method
* may modify the internal state of {@code this}, as long as it does
* so in a way that preserves Object equality.
*
* For efficiency, we should only resolve a type once per compilation job.
* For incremental compilations, one compilation job may need the
* artifacts from a previous generation, so we will eventually need
* a generational flag instead of a boolean one.
*/
public final JSType resolve(ErrorReporter reporter) {
if (resolved) {
if (resolveResult == null) {
// If there is a circular definition, keep the NamedType around. This is not ideal,
// but is still a better type than unknown.
return this;
}
return resolveResult;
}
resolved = true;
resolveResult = resolveInternal(reporter);
resolveResult.setResolvedTypeInternal(resolveResult);
return resolveResult;
}
/**
* @see #resolve
*/
abstract JSType resolveInternal(ErrorReporter reporter);
void setResolvedTypeInternal(JSType type) {
resolveResult = type;
resolved = true;
}
/**
* Returns whether the type has undergone resolution.
*
* A value of {@code true} does not indicate that resolution was successful, only that
* it was attempted and has finished.
*/
public final boolean isResolved() {
return resolved;
}
/** Returns whether the type has undergone resolution and resolved to a "useful" type. */
public final boolean isSuccessfullyResolved() {
return isResolved() && !isNoResolvedType();
}
/** Returns whether the type has undergone resolution and resolved to a "useless" type. */
public final boolean isUnsuccessfullyResolved() {
return isResolved() && isNoResolvedType();
}
/**
* A null-safe resolve.
* @see #resolve
*/
static final JSType safeResolve(
JSType type, ErrorReporter reporter) {
return type == null ? null : type.resolve(reporter);
}
/**
* Certain types have constraints on them at resolution-time.
* For example, a type in an {@code @extends} annotation must be an
* object. Clients should inject a validator that emits a warning
* if the type does not validate, and return false.
*/
public boolean setValidator(Predicate validator) {
return validator.apply(this);
}
/**
* a data structure that represents a pair of types
*/
public static class TypePair {
public final JSType typeA;
public final JSType typeB;
public TypePair(JSType typeA, JSType typeB) {
this.typeA = typeA;
this.typeB = typeB;
}
}
/**
* A string representation of this type, suitable for printing
* in warnings.
*/
@Override
public String toString() {
return appendTo(new StringBuilder(), false).toString();
}
/**
* A hash code function for diagnosing complicated issues
* around type-identity.
*/
public String toDebugHashCodeString() {
return "{" + hashCode() + "}";
}
// Don't call from this package; use appendAsNonNull instead.
public final String toAnnotationString(Nullability nullability) {
return nullability == Nullability.EXPLICIT
? appendAsNonNull(new StringBuilder(), true).toString()
: appendTo(new StringBuilder(), true).toString();
}
final StringBuilder appendAsNonNull(StringBuilder sb, boolean forAnnotations) {
if (forAnnotations
&& isObject()
&& !isUnknownType()
&& !isTemplateType()
&& !isRecordType()
&& !isFunctionType()
&& !isUnionType()
&& !isLiteralObject()) {
sb.append("!");
}
return appendTo(sb, forAnnotations);
}
abstract StringBuilder appendTo(StringBuilder sb, boolean forAnnotations);
/**
* Modify this type so that it matches the specified type.
*
* This is useful for reverse type-inference, where we want to
* infer that an object literal matches its constraint (much like
* how the java compiler does reverse-inference to figure out generics).
* @param constraint
*/
public void matchConstraint(JSType constraint) {}
public boolean isSubtypeOf(JSType other) {
return isSubtype(other);
}
public ObjectType toMaybeObjectType() {
return toObjectType();
}
/**
* describe the status of checking that a function
* implicitly implements an interface.
*
* it also be used to describe the status of checking
* that a record type structurally matches another
* record type
*
* A function implicitly implements an interface if
* the function does not use @implements to declare
* that it implements the interface, but its class
* structure complies with the protocol defined
* by the interface
*/
static enum MatchStatus {
/**
* indicate that a function implicitly
* implements an interface (i.e., the function
* structurally complies with the protocol
* defined in interface)
*
* or a record type matches another record type
*/
MATCH(true),
/**
* indicate that a function does not implicitly
* implements an interface (i.e., the function
* does not structurally comply with the protocol
* defined in interface)
*
* or a record type does not match another
* record type
*/
NOT_MATCH(false),
/**
* indicate that the interface and function
* relationship is under processing
*/
PROCESSING(true);
MatchStatus(boolean isSubtype) {
this.isSubtype = isSubtype;
}
private final boolean isSubtype;
boolean subtypeValue() {
return this.isSubtype;
}
static MatchStatus valueOf(boolean match) {
return match ? MATCH : NOT_MATCH;
}
}
/**
* base cache data structure
*/
private abstract static class MatchCache {
private final boolean isStructuralTyping;
MatchCache(boolean isStructuralTyping) {
this.isStructuralTyping = isStructuralTyping;
}
boolean isStructuralTyping() {
return isStructuralTyping;
}
}
/**
* cache used by equivalence check logic
*/
static class EqCache extends MatchCache {
private HashMap matchCache;
static EqCache create() {
return new EqCache(true);
}
static EqCache createWithoutStructuralTyping() {
return new EqCache(false);
}
private EqCache(boolean isStructuralTyping) {
super(isStructuralTyping);
}
void updateCache(JSType left, JSType right, MatchStatus isMatch) {
updateCacheAnyType(left, right, isMatch); // Defer Key instantiation.
}
void updateCache(TemplateTypeMap left, TemplateTypeMap right, MatchStatus isMatch) {
updateCacheAnyType(left, right, isMatch); // Defer Key instantiation.
}
private void updateCacheAnyType(Object left, Object right, MatchStatus isMatch) {
if (left == right) {
// Recall that we never bother reading keys with equal elements.
return;
}
getMatchCache().put(new Key(left, right), isMatch);
}
@Nullable
MatchStatus checkCache(JSType left, JSType right) {
return checkCacheAnyType(left, right); // Defer Key instantiation.
}
@Nullable
MatchStatus checkCache(TemplateTypeMap left, TemplateTypeMap right) {
return checkCacheAnyType(left, right); // Defer Key instantiation.
}
private MatchStatus checkCacheAnyType(Object left, Object right) {
if (left == right) {
return MatchStatus.MATCH;
}
return getMatchCache().putIfAbsent(new Key(left, right), MatchStatus.PROCESSING);
}
private HashMap getMatchCache() {
if (matchCache == null) {
matchCache = new HashMap<>();
}
return matchCache;
}
boolean shouldMatchStructurally(JSType left, JSType right) {
return isStructuralTyping()
? left.isStructuralType() && right.isStructuralType()
: left.isRecordType() && right.isRecordType();
}
private static final class Key {
private final Object left;
private final Object right;
private final int hashCode; // Cache this calculation because it is made often.
@Override
public int hashCode() {
return hashCode;
}
@Override
@SuppressWarnings({
"ShortCircuitBoolean",
"ReferenceEquality",
"EqualsBrokenForNull",
"EqualsUnsafeCast"
})
public boolean equals(Object other) {
// Calling this with `null` or not a `Key` should cause a crash.
Key that = (Key) other;
if (this == other) {
return true;
}
// Recall that `Key` implements identity equality on `left` and `right`.
//
// Recall that `left` and `right` are not ordered.
//
// Use non-short circuiting operators to eliminate branches. Equality checks are
// side-effect-free and less expensive than branches.
return ((this.left == that.left) & (this.right == that.right))
| ((this.left == that.right) & (this.right == that.left));
}
Key(Object left, Object right) {
this.left = left;
this.right = right;
// XOR the component hashcodes because:
// - It's a symmetric operator, so we don't have to worry about order.
// - It's assumed the inputs are already uniformly distributed and unrelated.
// - `left` and `right` should never be identical.
// Recall that `Key` implements identity equality on `left` and `right`.
this.hashCode = System.identityHashCode(left) ^ System.identityHashCode(right);
}
}
}
/**
* cache used by check sub-type logic
*/
static class ImplCache extends MatchCache {
private HashMap matchCache;
private EqCache eqCache;
static ImplCache create() {
return new ImplCache(true);
}
static ImplCache createWithoutStructuralTyping() {
return new ImplCache(false);
}
private ImplCache(boolean isStructuralTyping) {
super(isStructuralTyping);
}
boolean updateCache(JSType subType, JSType superType, MatchStatus isMatch) {
matchCache.put(new Key(subType, superType), isMatch);
return isMatch.subtypeValue();
}
MatchStatus checkCache(JSType subType, JSType superType) {
if (matchCache == null) {
this.matchCache = new HashMap<>();
}
// check the cache
return matchCache.putIfAbsent(new Key(subType, superType), MatchStatus.PROCESSING);
}
boolean shouldMatchStructurally(JSType subType, JSType superType) {
return isStructuralTyping() && subType.isObject() && superType.isStructuralType();
}
private boolean equal(JSType left, JSType right) {
if (eqCache == null) {
this.eqCache = new EqCache(isStructuralTyping());
}
return left.checkEquivalenceHelper(right, EquivalenceMethod.IDENTITY, eqCache);
}
private final class Key {
final JSType left;
final JSType right;
final int hashCode; // Cache this calculation because it is made often.
@Override
public int hashCode() {
return hashCode;
}
@Override
@SuppressWarnings({"ReferenceEquality", "EqualsBrokenForNull", "EqualsUnsafeCast"})
public boolean equals(Object other) {
// Calling this with `null` or not a `Key` should never happen, so it's fine to crash.
Key that = (Key) other;
if (this.left == that.left && this.right == that.right) {
return true;
}
// The vast majority of cases will have already returned by now, since equality isn't even
// checked unless the hash code matches, and in most cases there's only one instance of any
// equivalent JSType floating around. The remainder only occurs for cyclic (or otherwise
// complicated) data structures where equivalent types are being synthesized by recursive
// application of type parameters, or (even more rarely) for hash collisions. Identity is
// insufficient in the case of recursive parameterized types because new equivalent copies
// of the type are generated on-the-fly to compute the types of various properties.
return equal(this.left, that.left) && equal(this.right, that.right);
}
Key(JSType left, JSType right) {
this.left = left;
this.right = right;
// NOTE: order matters here, since we're expressing an asymmetric relationship.
this.hashCode = 31 * left.hashCode() + right.hashCode();
}
}
}
/**
* Specifies how to express nullability of reference types in annotation strings and error
* messages. Note that this only applies to the outer-most type. Nullability of generic type
* arguments is always explicit.
*/
public enum Nullability {
/**
* Include an explicit '!' for non-nullable reference types. This is suitable for use
* in most type contexts (particularly 'type', 'param', and 'return' annotations).
*/
EXPLICIT,
/**
* Omit the explicit '!' from the outermost non-nullable reference type. This is suitable for
* use in cases where a single reference type is expected (e.g. 'extends' and 'implements').
*/
IMPLICIT,
}
/**
* Returns the template type argument in this type's map corresponding to the supertype's template
* parameter, or the UNKNOWN_TYPE if the supertype template key is not present.
*
* Note: this only supports arguments that have a singleton list of template keys, and will
* throw an exception for arguments with zero or multiple or template keys.
*/
public JSType getInstantiatedTypeArgument(JSType supertype) {
TemplateType templateType =
Iterables.getOnlyElement(supertype.getTemplateTypeMap().getTemplateKeys());
return getTemplateTypeMap().getResolvedTemplateType(templateType);
}
/**
* Returns a JSType representation of this type suitable for running optimizations.
* This may have certain features that are only useful at check-time omitted.
*/
JSType simplifyForOptimizations() {
return this;
}
}