com.google.javascript.rhino.jstype.FunctionType Maven / Gradle / Ivy
Show all versions of closure-compiler 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.javascript.rhino.jstype.JSTypeNative.OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.U2U_CONSTRUCTOR_TYPE;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.FunctionTypeI;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TypeI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This derived type provides extended information about a function, including
* its return type and argument types.
*
* Note: the parameters list is the LP node that is the parent of the
* actual NAME node containing the parsed argument list (annotated with
* JSDOC_TYPE_PROP's for the compile-time type of each argument.
*/
public class FunctionType extends PrototypeObjectType implements FunctionTypeI {
private static final long serialVersionUID = 1L;
private enum Kind {
ORDINARY,
CONSTRUCTOR,
INTERFACE
}
// relevant only for constructors
private enum PropAccess { ANY, STRUCT, DICT }
/**
* {@code [[Call]]} property.
*/
private ArrowType call;
/**
* The {@code prototype} property. This field is lazily initialized by
* {@code #getPrototype()}. The most important reason for lazily
* initializing this field is that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*
* If non-null, the type must be a PrototypeObjectType.
*/
private Property prototypeSlot;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* Whether the instances are structs, dicts, or unrestricted.
*/
private PropAccess propAccess;
/**
* The type of {@code this} in the scope of this function.
*/
private JSType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* if this is an interface, indicate whether or not it supports
* structural interface matching
*/
private boolean isStructuralInterface;
/**
* If true, the function type represents an abstract method or the constructor of an abstract
* class
*/
private final boolean isAbstract;
/**
* The interfaces directly implemented by this function (for constructors)
* It is only relevant for constructors. May not be {@code null}.
*/
private ImmutableList implementedInterfaces = ImmutableList.of();
/**
* The interfaces directly extended by this function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private ImmutableList extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List subTypes;
/** Creates an instance for a function that might be a constructor. */
FunctionType(
JSTypeRegistry registry,
String name,
Node source,
ArrowType arrowType,
JSType typeOfThis,
TemplateTypeMap templateTypeMap,
boolean isConstructor,
boolean nativeType,
boolean isAbstract) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType, templateTypeMap);
setPrettyPrint(true);
Preconditions.checkArgument(source == null || Token.FUNCTION == source.getToken());
Preconditions.checkNotNull(arrowType);
this.source = source;
if (isConstructor) {
this.kind = Kind.CONSTRUCTOR;
this.propAccess = PropAccess.ANY;
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.kind = Kind.ORDINARY;
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
this.isStructuralInterface = false;
this.isAbstract = isAbstract;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source,
TemplateTypeMap typeParameters) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
false, typeParameters);
setPrettyPrint(true);
Preconditions.checkArgument(source == null || Token.FUNCTION == source.getToken());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
this.isStructuralInterface = false;
this.isAbstract = false;
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source,
TemplateTypeMap typeParameters) {
return new FunctionType(registry, name, source, typeParameters);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely. It overrides
// getConstructor() appropriately when it's declared.
return this == registry.getNativeType(U2U_CONSTRUCTOR_TYPE);
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
/**
* When a class B inherits from A and A is annotated as a struct, then B
* automatically gets the annotation, even if B's constructor is not
* explicitly annotated.
*/
public boolean makesStructs() {
if (!hasInstanceType()) {
return false;
}
if (propAccess == PropAccess.STRUCT) {
return true;
}
FunctionType superc = getSuperClassConstructor();
if (superc != null && superc.makesStructs()) {
setStruct();
return true;
}
return false;
}
/**
* When a class B inherits from A and A is annotated as a dict, then B
* automatically gets the annotation, even if B's constructor is not
* explicitly annotated.
*/
public boolean makesDicts() {
if (!isConstructor()) {
return false;
}
if (propAccess == PropAccess.DICT) {
return true;
}
FunctionType superc = getSuperClassConstructor();
if (superc != null && superc.makesDicts()) {
setDict();
return true;
}
return false;
}
public void setStruct() {
propAccess = PropAccess.STRUCT;
}
public void setDict() {
propAccess = PropAccess.DICT;
}
@Override
public FunctionType toMaybeFunctionType() {
return this;
}
@Override
public boolean canBeCalled() {
return true;
}
public boolean hasImplementedInterfaces() {
if (!implementedInterfaces.isEmpty()){
return true;
}
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor != null) {
return superCtor.hasImplementedInterfaces();
}
return false;
}
public Iterable getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments that this function requires,
* or Integer.MAX_VALUE if this is a variable argument function.
*/
public int getMaxArguments() {
Node params = getParametersNode();
if (params != null) {
Node lastParam = params.getLastChild();
if (lastParam == null || !lastParam.isVarArgs()) {
return params.getChildCount();
}
}
return Integer.MAX_VALUE;
}
@Override
public JSType getReturnType() {
return call.returnType;
}
public boolean isReturnTypeInferred() {
return call.returnTypeInferred;
}
/** Gets the internal arrow type. For use by subclasses only. */
ArrowType getInternalArrowType() {
return call;
}
@Override
public Property getSlot(String name) {
if ("prototype".equals(name)) {
// Lazy initialization of the prototype field.
getPrototype();
return prototypeSlot;
} else {
return super.getSlot(name);
}
}
/**
* Includes the prototype iff someone has created it. We do not want
* to expose the prototype for ordinary functions.
*/
@Override
public Set getOwnPropertyNames() {
if (prototypeSlot == null) {
return super.getOwnPropertyNames();
} else {
ImmutableSet.Builder names = ImmutableSet.builder();
names.add("prototype");
names.addAll(super.getOwnPropertyNames());
return names.build();
}
}
/**
* Gets the {@code prototype} property of this function type. This is
* equivalent to {@code (ObjectType) getPropertyType("prototype")}.
*/
public ObjectType getPrototype() {
// lazy initialization of the prototype field
if (prototypeSlot == null) {
String refName = getReferenceName();
if (refName == null) {
// Someone is trying to access the prototype of a structural function.
// We don't want to give real properties to this prototype, because
// then it would propagate to all structural functions.
setPrototypeNoCheck(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE),
null);
} else {
setPrototype(
new PrototypeObjectType(
registry,
getReferenceName() + ".prototype",
registry.getNativeObjectType(OBJECT_TYPE),
isNativeObjectType(), null),
null);
}
}
return (ObjectType) prototypeSlot.getType();
}
/**
* Sets the prototype, creating the prototype object from the given
* base type.
* @param baseType The base type.
*/
public void setPrototypeBasedOn(ObjectType baseType) {
setPrototypeBasedOn(baseType, null);
}
void setPrototypeBasedOn(ObjectType baseType, Node propertyNode) {
// This is a bit weird. We need to successfully handle these
// two cases:
// Foo.prototype = new Bar();
// and
// Foo.prototype = {baz: 3};
// In the first case, we do not want new properties to get
// added to Bar. In the second case, we do want new properties
// to get added to the type of the anonymous object.
//
// We handle this by breaking it into two cases:
//
// In the first case, we create a new PrototypeObjectType and set
// its implicit prototype to the type being assigned. This ensures
// that Bar will not get any properties of Foo.prototype, but properties
// later assigned to Bar will get inherited properly.
//
// In the second case, we just use the anonymous object as the prototype.
if (baseType.hasReferenceName() ||
isNativeObjectType() ||
baseType.isFunctionPrototypeType()) {
if (prototypeSlot != null && hasInstanceType() && baseType.equals(getInstanceType())) {
// Bail out for cases like Foo.prototype = new Foo();
return;
}
baseType = new PrototypeObjectType(
registry, getReferenceName() + ".prototype", baseType);
}
setPrototype(baseType, propertyNode);
}
/**
* Extends the TemplateTypeMap of the function's this type, based on the
* specified type.
* @param type
*/
public void extendTemplateTypeMapBasedOn(ObjectType type) {
typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap());
}
/**
* Sets the prototype.
* @param prototype the prototype. If this value is {@code null} it will
* silently be discarded.
*/
boolean setPrototype(ObjectType prototype, Node propertyNode) {
if (prototype == null) {
return false;
}
// getInstanceType fails if the function is not a constructor
if (isConstructor() && prototype == getInstanceType()) {
return false;
}
return setPrototypeNoCheck(prototype, propertyNode);
}
/** Set the prototype without doing any sanity checks. */
private boolean setPrototypeNoCheck(ObjectType prototype, Node propertyNode) {
ObjectType oldPrototype = prototypeSlot == null
? null : (ObjectType) prototypeSlot.getType();
boolean replacedPrototype = oldPrototype != null;
this.prototypeSlot = new Property("prototype", prototype, true,
propertyNode == null ? source : propertyNode);
prototype.setOwnerFunction(this);
if (oldPrototype != null) {
// Disassociating the old prototype makes this easier to debug--
// we don't have to worry about two prototypes running around.
oldPrototype.setOwnerFunction(null);
}
if (isConstructor() || isInterface()) {
FunctionType superClass = getSuperClassConstructor();
if (superClass != null) {
superClass.addSubType(this);
}
if (isInterface()) {
for (ObjectType interfaceType : getExtendedInterfaces()) {
if (interfaceType.getConstructor() != null) {
interfaceType.getConstructor().addSubType(this);
}
}
}
}
if (replacedPrototype) {
clearCachedValues();
}
return true;
}
/**
* check whether or not this function type has implemented
* the given interface
* if this function is an interface, check whether or not
* this interface has extended the given interface
* @param interfaceType the interface type
* @return true if implemented
*/
public boolean explicitlyImplOrExtInterface(FunctionType interfaceType) {
Preconditions.checkArgument(interfaceType.isInterface());
for (ObjectType implementedInterface : getAllImplementedInterfaces()) {
FunctionType ctor = implementedInterface.getConstructor();
if (ctor != null && ctor.checkEquivalenceHelper(
interfaceType, EquivalenceMethod.IDENTITY)) {
return true;
}
}
for (ObjectType implementedInterface : getExtendedInterfaces()) {
FunctionType ctor = implementedInterface.getConstructor();
if (ctor != null && ctor.checkEquivalenceHelper(
interfaceType, EquivalenceMethod.IDENTITY)) {
return true;
} else if (ctor != null) {
return ctor.explicitlyImplOrExtInterface(interfaceType);
}
}
return false;
}
/**
* Returns all interfaces implemented by a class or its superclass and any
* superclasses for any of those interfaces. If this is called before all
* types are resolved, it may return an incomplete set.
*/
public Iterable getAllImplementedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set interfaces = new LinkedHashSet<>();
for (ObjectType type : getImplementedInterfaces()) {
addRelatedInterfaces(type, interfaces);
}
return interfaces;
}
private void addRelatedInterfaces(ObjectType instance, Set set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
if (!constructor.isInterface()) {
return;
}
if (!set.add(instance)) {
return;
}
for (ObjectType interfaceType : instance.getCtorExtendedInterfaces()) {
addRelatedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces implemented directly by a class or its superclass. */
public Iterable getImplementedInterfaces() {
FunctionType superCtor =
isConstructor() ? getSuperClassConstructor() : null;
if (superCtor == null) {
return implementedInterfaces;
}
ImmutableList.Builder builder = ImmutableList.builder();
builder.addAll(implementedInterfaces);
while (superCtor != null) {
builder.addAll(superCtor.implementedInterfaces);
superCtor = superCtor.getSuperClassConstructor();
}
return builder.build();
}
/** Returns interfaces directly implemented by the class. */
public Iterable getOwnImplementedInterfaces() {
return implementedInterfaces;
}
public void setImplementedInterfaces(List implementedInterfaces) {
if (isConstructor()) {
// Records this type for each implemented interface.
for (ObjectType type : implementedInterfaces) {
registry.registerTypeImplementingInterface(this, type);
typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap());
}
this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces);
} else {
throw new UnsupportedOperationException(
"An interface cannot implement other inferfaces");
}
}
private void addRelatedExtendedInterfaces(ObjectType instance,
Set set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
if (!set.add(instance)) {
return;
}
for (ObjectType interfaceType : constructor.getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces directly extended by an interface */
public Iterable getExtendedInterfaces() {
return extendedInterfaces;
}
/** Returns the number of interfaces directly extended by an interface */
public int getExtendedInterfacesCount() {
return extendedInterfaces.size();
}
public void setExtendedInterfaces(List extendedInterfaces) {
if (isInterface()) {
this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces);
for (ObjectType extendedInterface : this.extendedInterfaces) {
typeOfThis.extendTemplateTypeMap(
extendedInterface.getTemplateTypeMap());
}
} else {
throw new UnsupportedOperationException();
}
}
@Override
public JSType getPropertyType(String name) {
if (!hasOwnProperty(name)) {
// Define the "call", "apply", and "bind" functions lazily.
boolean isCall = "call".equals(name);
boolean isBind = "bind".equals(name);
if (isCall || isBind) {
defineDeclaredProperty(name, getCallOrBindSignature(isCall), source);
} else if ("apply".equals(name)) {
// Define the "apply" function lazily.
FunctionParamBuilder builder = new FunctionParamBuilder(registry);
// ECMA-262 says that apply's second argument must be an Array
// or an arguments object. We don't model the arguments object,
// so let's just be forgiving for now.
// TODO(nicksantos): Model the Arguments object.
builder.addOptionalParams(
registry.createNullableType(getTypeOfThis()),
registry.createNullableType(
registry.getNativeType(JSTypeNative.OBJECT_TYPE)));
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParamsNode(builder.build())
.withReturnType(getReturnType())
.withTemplateKeys(getTemplateTypeMap().getTemplateKeys())
.build(),
source);
}
}
return super.getPropertyType(name);
}
/**
* Get the return value of calling "bind" on this function
* with the specified number of arguments.
*
* If -1 is passed, then we will return a result that accepts
* any parameters.
*/
public FunctionType getBindReturnType(int argsToBind) {
FunctionBuilder builder = new FunctionBuilder(registry)
.withReturnType(getReturnType())
.withTemplateKeys(getTemplateTypeMap().getTemplateKeys());
if (argsToBind >= 0) {
Node origParams = getParametersNode();
if (origParams != null) {
Node params = origParams.cloneTree();
for (int i = 1; i < argsToBind && params.getFirstChild() != null; i++) {
if (params.getFirstChild().isVarArgs()) {
break;
}
params.removeFirstChild();
}
builder.withParamsNode(params);
}
}
return builder.build();
}
/**
* Notice that "call" and "bind" have the same argument signature,
* except that all the arguments of "bind" (except the first)
* are optional.
*/
private FunctionType getCallOrBindSignature(boolean isCall) {
boolean isBind = !isCall;
FunctionBuilder builder = new FunctionBuilder(registry)
.withReturnType(isCall ? getReturnType() : getBindReturnType(-1))
.withTemplateKeys(getTemplateTypeMap().getTemplateKeys());
Node origParams = getParametersNode();
if (origParams != null) {
Node params = origParams.cloneTree();
Node thisTypeNode = Node.newString(Token.NAME, "thisType");
thisTypeNode.setJSType(
registry.createOptionalNullableType(getTypeOfThis()));
params.addChildToFront(thisTypeNode);
if (isBind) {
// The arguments of bind() are unique in that they are all
// optional but not undefinable.
for (Node current = thisTypeNode.getNext();
current != null; current = current.getNext()) {
current.setOptionalArg(true);
}
} else if (isCall) {
// The first argument of call() is optional iff all the arguments
// are optional. It's sufficient to check the first argument.
Node firstArg = thisTypeNode.getNext();
if (firstArg == null
|| firstArg.isOptionalArg()
|| firstArg.isVarArgs()) {
thisTypeNode.setOptionalArg(true);
}
}
builder.withParamsNode(params);
}
return builder.build();
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, Node propertyNode) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (prototypeSlot != null &&
objType.isEquivalentTo(prototypeSlot.getType())) {
return true;
}
setPrototypeBasedOn(objType, propertyNode);
return true;
} else {
return false;
}
}
return super.defineProperty(name, type, inferred, propertyNode);
}
/**
* Computes the supremum or infimum of two functions.
* Because sup() and inf() share a lot of logic for functions, we use
* a single helper.
* @param leastSuper If true, compute the supremum of {@code this} with
* {@code that}. Otherwise, compute the infimum.
* @return The least supertype or greatest subtype.
*/
FunctionType supAndInfHelper(FunctionType that, boolean leastSuper) {
// NOTE(nicksantos): When we remove the unknown type, the function types
// form a lattice with the universal constructor at the top of the lattice,
// and the LEAST_FUNCTION_TYPE type at the bottom of the lattice.
//
// When we introduce the unknown type, it's much more difficult to make
// heads or tails of the partial ordering of types, because there's no
// clear hierarchy between the different components (parameter types and
// return types) in the ArrowType.
//
// Rather than make the situation more complicated by introducing new
// types (like unions of functions), we just fallback on the simpler
// approach of getting things right at the top and the bottom of the
// lattice.
//
// If there are unknown parameters or return types making things
// ambiguous, then sup(A, B) is always the top function type, and
// inf(A, B) is always the bottom function type.
Preconditions.checkNotNull(that);
if (isEquivalentTo(that)) {
return this;
}
// If these are ordinary functions, then merge them.
// Don't do this if any of the params/return
// values are unknown, because then there will be cycles in
// their local lattice and they will merge in weird ways.
if (isOrdinaryFunction() && that.isOrdinaryFunction() &&
!this.call.hasUnknownParamsOrReturn() &&
!that.call.hasUnknownParamsOrReturn()) {
// Check for the degenerate case, but double check
// that there's not a cycle.
boolean isSubtypeOfThat = isSubtype(that);
boolean isSubtypeOfThis = that.isSubtype(this);
if (isSubtypeOfThat && !isSubtypeOfThis) {
return leastSuper ? that : this;
} else if (isSubtypeOfThis && !isSubtypeOfThat) {
return leastSuper ? this : that;
}
// Merge the two functions component-wise.
FunctionType merged = tryMergeFunctionPiecewise(that, leastSuper);
if (merged != null) {
return merged;
}
}
// The function instance type is a special case
// that lives above the rest of the lattice.
JSType functionInstance = registry.getNativeType(
JSTypeNative.FUNCTION_INSTANCE_TYPE);
if (functionInstance.isEquivalentTo(that)) {
return leastSuper ? that : this;
} else if (functionInstance.isEquivalentTo(this)) {
return leastSuper ? this : that;
}
// In theory, we should be using the GREATEST_FUNCTION_TYPE as the
// greatest function. In practice, we don't because it's way too
// broad. The greatest function takes var_args None parameters, which
// means that all parameters register a type warning.
//
// Instead, we use the U2U ctor type, which has unknown type args.
FunctionType greatestFn =
registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE);
FunctionType leastFn =
registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE);
return leastSuper ? greatestFn : leastFn;
}
/**
* Try to get the sup/inf of two functions by looking at the
* piecewise components.
*/
private FunctionType tryMergeFunctionPiecewise(
FunctionType other, boolean leastSuper) {
Node newParamsNode = null;
if (call.hasEqualParameters(other.call, EquivalenceMethod.IDENTITY, EqCache.create())) {
newParamsNode = call.parameters;
} else {
// If the parameters are not equal, don't try to merge them.
// Someday, we should try to merge the individual params.
return null;
}
JSType newReturnType = leastSuper ?
call.returnType.getLeastSupertype(other.call.returnType) :
call.returnType.getGreatestSubtype(other.call.returnType);
JSType newTypeOfThis = null;
if (isEquivalent(typeOfThis, other.typeOfThis)) {
newTypeOfThis = typeOfThis;
} else {
JSType maybeNewTypeOfThis = leastSuper ?
typeOfThis.getLeastSupertype(other.typeOfThis) :
typeOfThis.getGreatestSubtype(other.typeOfThis);
newTypeOfThis = maybeNewTypeOfThis;
}
boolean newReturnTypeInferred =
call.returnTypeInferred || other.call.returnTypeInferred;
return new FunctionType(
registry,
null,
null,
new ArrowType(registry, newParamsNode, newReturnType, newReturnTypeInferred),
newTypeOfThis,
null,
false,
false,
false);
}
/**
* Given a constructor or an interface type, get its superclass constructor
* or {@code null} if none exists.
*/
@Override
public FunctionType getSuperClassConstructor() {
Preconditions.checkArgument(isConstructor() || isInterface());
ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return null;
}
return maybeSuperInstanceType.getConstructor();
}
/**
* Given an interface and a property, finds the top-most super interface
* that has the property defined (including this interface).
*/
public static ObjectType getTopDefiningInterface(ObjectType type,
String propertyName) {
ObjectType foundType = null;
if (type.hasProperty(propertyName)) {
foundType = type;
}
for (ObjectType interfaceType : type.getCtorExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
foundType = getTopDefiningInterface(interfaceType, propertyName);
}
}
return foundType;
}
/**
* Given a constructor or an interface type and a property, finds the
* top-most superclass that has the property defined (including this
* constructor).
*/
public ObjectType getTopMostDefiningType(String propertyName) {
Preconditions.checkState(isConstructor() || isInterface());
Preconditions.checkArgument(getInstanceType().hasProperty(propertyName));
FunctionType ctor = this;
if (isInterface()) {
return getTopDefiningInterface(getInstanceType(), propertyName);
}
ObjectType topInstanceType = null;
do {
topInstanceType = ctor.getInstanceType();
ctor = ctor.getSuperClassConstructor();
} while (ctor != null
&& ctor.getPrototype().hasProperty(propertyName));
return topInstanceType;
}
/**
* Two function types are equal if their signatures match. Since they don't
* have signatures, two interfaces are equal if their names match.
*/
boolean checkFunctionEquivalenceHelper(
FunctionType that, EquivalenceMethod eqMethod, EqCache eqCache) {
if (this == that) {
return true;
}
if (kind != that.kind) {
return false;
}
switch (kind) {
case CONSTRUCTOR:
return false; // constructors use identity semantics, which we already checked for above.
case INTERFACE:
return getReferenceName().equals(that.getReferenceName());
case ORDINARY:
return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod, eqCache)
&& call.checkArrowEquivalenceHelper(that.call, eqMethod, eqCache);
default:
throw new AssertionError();
}
}
@Override
public int hashCode() {
int hc = kind.hashCode();
switch (kind) {
case CONSTRUCTOR:
return 31 * hc + System.identityHashCode(this); // constructors use identity semantics
case INTERFACE:
return 31 * hc + getReferenceName().hashCode();
case ORDINARY:
hc = 31 * hc + typeOfThis.hashCode();
hc = 31 * hc + call.hashCode();
return hc;
default:
throw new AssertionError();
}
}
public boolean hasEqualCallType(FunctionType otherType) {
return this.call.checkArrowEquivalenceHelper(
otherType.call, EquivalenceMethod.IDENTITY, EqCache.create());
}
/**
* Informally, a function is represented by
* {@code function (params): returnType} where the {@code params} is a comma
* separated list of types, the first one being a special
* {@code this:T} if the function expects a known type for {@code this}.
*/
@Override
StringBuilder appendTo(StringBuilder sb, boolean forAnnotations) {
if (!isPrettyPrint() ||
this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return sb.append("Function");
}
setPrettyPrint(false);
sb.append("function (");
int paramNum = call.parameters.getChildCount();
boolean hasKnownTypeOfThis = !(typeOfThis instanceof UnknownType);
if (hasKnownTypeOfThis) {
if (isConstructor()) {
sb.append("new:");
} else {
sb.append("this:");
}
typeOfThis.appendTo(sb, forAnnotations);
}
if (paramNum > 0) {
if (hasKnownTypeOfThis) {
sb.append(", ");
}
Node p = call.parameters.getFirstChild();
appendArgString(sb, p, forAnnotations);
p = p.getNext();
while (p != null) {
sb.append(", ");
appendArgString(sb, p, forAnnotations);
p = p.getNext();
}
}
sb.append("): ");
call.returnType.appendAsNonNull(sb, forAnnotations);
setPrettyPrint(true);
return sb;
}
private void appendArgString(StringBuilder sb, Node p, boolean forAnnotations) {
if (p.isVarArgs()) {
appendVarArgsString(sb, p.getJSType(), forAnnotations);
} else if (p.isOptionalArg()) {
appendOptionalArgString(sb, p.getJSType(), forAnnotations);
} else {
p.getJSType().appendAsNonNull(sb, forAnnotations);
}
}
/** Gets the string representation of a var args param. */
private void appendVarArgsString(
StringBuilder sb, JSType paramType, boolean forAnnotations) {
if (paramType.isUnionType()) {
// Remove the optionality from the var arg.
paramType = paramType.toMaybeUnionType().getRestrictedUnion(
registry.getNativeType(JSTypeNative.VOID_TYPE));
}
sb.append("...");
paramType.appendAsNonNull(sb, forAnnotations);
}
/** Gets the string representation of an optional param. */
private void appendOptionalArgString(
StringBuilder sb, JSType paramType, boolean forAnnotations) {
if (paramType.isUnionType()) {
// Remove the optionality from the var arg.
paramType = paramType.toMaybeUnionType().getRestrictedUnion(
registry.getNativeType(JSTypeNative.VOID_TYPE));
}
paramType.appendAsNonNull(sb, forAnnotations).append("=");
}
/**
* A function is a subtype of another if their call methods are related via
* subtyping and {@code this} is a subtype of {@code that} with regard to
* the prototype chain.
*/
@Override
public boolean isSubtype(JSType that) {
return isSubtype(that, ImplCache.create(), SubtypingMode.NORMAL);
}
@Override
protected boolean isSubtype(JSType that,
ImplCache implicitImplCache, SubtypingMode subtypingMode) {
if (JSType.isSubtypeHelper(this, that, implicitImplCache, subtypingMode)) {
return true;
}
if (that.isFunctionType()) {
FunctionType other = that.toMaybeFunctionType();
if (other.isInterface()) {
// Any function can be assigned to an interface function.
return true;
}
if (isInterface()) {
// An interface function cannot be assigned to anything.
return false;
}
return treatThisTypesAsCovariant(other, implicitImplCache)
&& this.call.isSubtype(other.call, implicitImplCache, subtypingMode);
}
return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE)
.isSubtype(that, implicitImplCache, subtypingMode);
}
private boolean treatThisTypesAsCovariant(FunctionType other,
ImplCache implicitImplCache) {
// If functionA is a subtype of functionB, then their "this" types
// should be contravariant. However, this causes problems because
// of the way we enforce overrides. Because function(this:SubFoo)
// is not a subtype of function(this:Foo), our override check treats
// this as an error. Let's punt on all this for now.
// TODO(nicksantos): fix this.
boolean treatThisTypesAsCovariant =
// An interface 'this'-type is non-restrictive.
// In practical terms, if C implements I, and I has a method m,
// then any m doesn't necessarily have to C#m's 'this'
// type doesn't need to match I.
(other.typeOfThis.toObjectType() != null &&
other.typeOfThis.toObjectType().getConstructor() != null &&
other.typeOfThis.toObjectType().getConstructor().isInterface()) ||
// If one of the 'this' types is covariant of the other,
// then we'll treat them as covariant (see comment above).
other.typeOfThis.isSubtype(this.typeOfThis, implicitImplCache, SubtypingMode.NORMAL)
|| this.typeOfThis.isSubtype(other.typeOfThis, implicitImplCache, SubtypingMode.NORMAL);
return treatThisTypesAsCovariant;
}
@Override
public T visit(Visitor visitor) {
return visitor.caseFunctionType(this);
}
@Override T visit(RelationshipVisitor visitor, JSType that) {
return visitor.caseFunctionType(this, that);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
@Override
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType(), "Expected a constructor; got %s", this);
return typeOfThis.toObjectType();
}
/**
* Sets the instance type. This should only be used for special
* native types.
*/
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
@Override
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
@Override
public JSType getTypeOfThis() {
return typeOfThis.isEmptyType() ?
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
@Override
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
@Override
public void setSource(Node source) {
if (prototypeSlot != null) {
// NOTE(bashir): On one hand when source is null we want to drop any
// references to old nodes retained in prototypeSlot. On the other hand
// we cannot simply drop prototypeSlot, so we retain all information
// except the propertyNode for which we use an approximation! These
// details mostly matter in hot-swap passes.
if (source == null || prototypeSlot.getNode() == null) {
prototypeSlot = new Property(prototypeSlot.getName(),
prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source);
}
}
this.source = source;
}
void addSubTypeIfNotPresent(FunctionType subType) {
if (subTypes == null || !subTypes.contains(subType)) {
addSubType(subType);
}
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null) {
subTypes = new ArrayList<>();
}
subTypes.add(subType);
}
@Override
public void clearCachedValues() {
super.clearCachedValues();
if (subTypes != null) {
for (FunctionType subType : subTypes) {
subType.clearCachedValues();
}
}
if (!isNativeObjectType()) {
if (hasInstanceType()) {
getInstanceType().clearCachedValues();
}
if (prototypeSlot != null) {
((ObjectType) prototypeSlot.getType()).clearCachedValues();
}
}
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
@Override
public List getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototypeSlot != null || super.hasCachedValues();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticTypedScope scope) {
setResolvedTypeInternal(this);
call = (ArrowType) safeResolve(call, t, scope);
if (prototypeSlot != null) {
prototypeSlot.setType(
safeResolve(prototypeSlot.getType(), t, scope));
}
// Warning about typeOfThis if it doesn't resolve to an ObjectType
// is handled further upstream.
//
// TODO(nicksantos): Handle this correctly if we have a UnionType.
JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope);
if (maybeTypeOfThis != null) {
if (maybeTypeOfThis.isNullType() || maybeTypeOfThis.isVoidType()) {
typeOfThis = maybeTypeOfThis;
} else {
maybeTypeOfThis = ObjectType.cast(
maybeTypeOfThis.restrictByNotNullOrUndefined());
if (maybeTypeOfThis != null) {
typeOfThis = maybeTypeOfThis;
}
}
}
ImmutableList resolvedImplemented =
resolveTypeListHelper(implementedInterfaces, t, scope);
if (resolvedImplemented != null) {
implementedInterfaces = resolvedImplemented;
}
ImmutableList resolvedExtended =
resolveTypeListHelper(extendedInterfaces, t, scope);
if (resolvedExtended != null) {
extendedInterfaces = resolvedExtended;
}
if (subTypes != null) {
for (int i = 0; i < subTypes.size(); i++) {
subTypes.set(
i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope)));
}
}
return super.resolveInternal(t, scope);
}
/**
* Resolve each item in the list, and return a new list if any
* references changed. Otherwise, return null.
*/
private ImmutableList resolveTypeListHelper(
ImmutableList list,
ErrorReporter t,
StaticTypedScope scope) {
boolean changed = false;
ImmutableList.Builder resolvedList =
ImmutableList.builder();
for (ObjectType type : list) {
ObjectType resolved = (ObjectType) type.resolve(t, scope);
resolvedList.add(resolved);
changed |= (resolved != type);
}
return changed ? resolvedList.build() : null;
}
@Override
public String toDebugHashCodeString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return super.toDebugHashCodeString();
}
StringBuilder b = new StringBuilder(32);
b.append("function (");
int paramNum = call.parameters.getChildCount();
boolean hasKnownTypeOfThis = !typeOfThis.isUnknownType();
if (hasKnownTypeOfThis) {
b.append("this:");
b.append(getDebugHashCodeStringOf(typeOfThis));
}
if (paramNum > 0) {
if (hasKnownTypeOfThis) {
b.append(", ");
}
Node p = call.parameters.getFirstChild();
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
while (p != null) {
b.append(", ");
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
}
}
b.append(")");
b.append(": ");
b.append(getDebugHashCodeStringOf(call.returnType));
return b.toString();
}
private String getDebugHashCodeStringOf(JSType type) {
if (type == this) {
return "me";
} else {
return type.toDebugHashCodeString();
}
}
/** Create a new constructor with the parameters and return type stripped. */
public FunctionType forgetParameterAndReturnTypes() {
FunctionType result =
new FunctionType(
registry,
getReferenceName(),
source,
registry.createArrowType(null, null),
getInstanceType(),
null,
true,
false,
false);
result.setPrototypeBasedOn(getInstanceType());
return result;
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return getTemplateTypeMap().numUnfilledTemplateKeys() > 0
|| typeOfThis.hasAnyTemplateTypes()
|| call.hasAnyTemplateTypes();
}
@Override
public TypeI convertMethodToFunction() {
List paramTypes = new ArrayList<>();
paramTypes.add(getTypeOfThis());
for (Node param : getParameters()) {
paramTypes.add(param.getJSType());
}
return registry.createFunctionTypeWithInstanceType(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE),
getReturnType(),
paramTypes);
}
@Override
public boolean hasProperties() {
if (prototypeSlot != null) {
return true;
}
return !super.getOwnPropertyNames().isEmpty();
}
/**
* sets the current interface type to support
* structural interface matching (abbr. SMI)
* @param flag indicates whether or not it should support SMI
*/
public void setImplicitMatch(boolean flag) {
Preconditions.checkState(isInterface());
isStructuralInterface = flag;
}
@Override
public boolean isStructuralInterface() {
return isInterface() && isStructuralInterface;
}
public boolean isAbstract() {
return isAbstract;
}
/**
* get the map of properties to types covered in a function type
* @return a Map that maps the property's name to the property's type
*/
@Override
public Map getPropertyTypeMap() {
Map propTypeMap = new LinkedHashMap<>();
updatePropertyTypeMap(this, propTypeMap, new HashSet());
return propTypeMap;
}
// cache is added to prevent infinite recursion when retrieving
// the super type: see testInterfaceExtendsLoop in TypeCheckTest.java
private static void updatePropertyTypeMap(
FunctionType type, Map propTypeMap,
HashSet cache) {
if (type == null) { return; }
// retrieve all property types on the prototype of this class
ObjectType prototype = type.getPrototype();
if (prototype != null) {
Set propNames = prototype.getOwnPropertyNames();
for (String name : propNames) {
if (!propTypeMap.containsKey(name)) {
JSType propType = prototype.getPropertyType(name);
propTypeMap.put(name, propType);
}
}
}
// retrieve all property types from its super class
Iterable iterable = type.getExtendedInterfaces();
if (iterable != null) {
for (ObjectType interfaceType : iterable) {
FunctionType superConstructor = interfaceType.getConstructor();
if (superConstructor == null
|| cache.contains(superConstructor)) { continue; }
cache.add(superConstructor);
updatePropertyTypeMap(superConstructor, propTypeMap, cache);
cache.remove(superConstructor);
}
}
}
/**
* check if there is a loop in the type extends chain
* @return an array of all functions in the loop chain if
* a loop exists, otherwise returns null
*/
public List checkExtendsLoop() {
return checkExtendsLoop(new HashSet(),
new ArrayList());
}
public List checkExtendsLoop(HashSet cache,
List path) {
Iterable iterable = this.getExtendedInterfaces();
if (iterable != null) {
for (ObjectType interfaceType : iterable) {
FunctionType superConstructor = interfaceType.getConstructor();
if (superConstructor == null) { continue; }
if (cache.contains(superConstructor)) {
// after detecting a loop, prune and return the path, e.g.,:
// A -> B -> C -> D -> C, will be pruned into:
// c -> D -> C
path.add(superConstructor);
while (path.get(0) != superConstructor) {
path.remove(0);
}
return path;
}
cache.add(superConstructor);
path.add(superConstructor);
List result =
superConstructor.checkExtendsLoop(cache, path);
if (result != null) {
return result;
}
cache.remove(superConstructor);
path.remove(path.size() - 1);
}
}
return null;
}
@Override
public boolean acceptsArguments(List argumentTypes) {
// NOTE(aravindpg): This code is essentially lifted from TypeCheck::visitParameterList,
// but what small differences there are make it very painful to refactor out the shared code.
Iterator arguments = argumentTypes.iterator();
Iterator parameters = this.getParameters().iterator();
Node parameter = null;
TypeI argument = null;
while (arguments.hasNext()
&& (parameters.hasNext() || parameter != null && parameter.isVarArgs())) {
// If there are no parameters left in the list, then the while loop
// above implies that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
if (!argument.isSubtypeOf(parameter.getTypeI())) {
return false;
}
}
int numArgs = argumentTypes.size();
return this.getMinArguments() <= numArgs && numArgs <= this.getMaxArguments();
}
}