com.google.javascript.jscomp.newtypes.FunctionType Maven / Gradle / Ivy
Show all versions of closure-compiler-linter Show documentation
/*
* Copyright 2013 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.newtypes;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.javascript.rhino.TypeI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Additional type information for methods and stand-alone functions.
* Stored as a field in appropriate {@link ObjectType} instances.
*
* @author [email protected] (Ben Lickly)
* @author [email protected] (Dimitris Vardoulakis)
*/
public final class FunctionType {
private final JSTypes commonTypes;
private final ImmutableList requiredFormals;
private final ImmutableList optionalFormals;
private final JSType restFormals;
private final JSType returnType;
private final boolean isLoose;
private final boolean isAbstract;
private final ImmutableMap outerVarPreconditions;
// If this FunctionType is a constructor/interface, this field stores the
// type of the instance.
private final JSType nominalType;
// If this FunctionType is a prototype method, this field stores the
// type of the instance.
private final JSType receiverType;
// Non-empty iff this function has an @template annotation. Note that a function type can have
// type variables as formal parameters and still have empty typeParameters, eg, a method without
// @template defined on a generic class.
private final ImmutableList typeParameters;
private static final boolean DEBUGGING = false;
private FunctionType(
JSTypes commonTypes,
ImmutableList requiredFormals,
ImmutableList optionalFormals,
JSType restFormals,
JSType retType,
JSType nominalType,
JSType receiverType,
ImmutableMap outerVars,
ImmutableList typeParameters,
boolean isLoose,
boolean isAbstract) {
Preconditions.checkNotNull(commonTypes);
this.commonTypes = commonTypes;
this.requiredFormals = requiredFormals;
this.optionalFormals = optionalFormals;
this.restFormals = restFormals;
this.returnType = retType;
this.nominalType = nominalType;
this.receiverType = receiverType;
this.outerVarPreconditions = outerVars;
this.typeParameters = typeParameters;
this.isLoose = isLoose;
this.isAbstract = isAbstract;
checkValid();
}
// This constructor is only used to create TOP_FUNCTION and LOOSE_TOP_FUNCTION.
// We create only one TOP_FUNCTION and one LOOSE_TOP_FUNCTION, and check
// for "topness" using reference equality. Most fields of these two types are set to null,
// and are not allowed to be null for other function types. This is on purpose;
// we do not want to accidentally make a top function be equals() to some other
// function type. The return type is unknown for convenience.
private FunctionType(JSTypes commonTypes, boolean isLoose) {
Preconditions.checkNotNull(commonTypes);
this.commonTypes = commonTypes;
this.requiredFormals = null;
this.optionalFormals = null;
this.restFormals = null;
this.returnType = Preconditions.checkNotNull(this.commonTypes.UNKNOWN);
this.nominalType = null;
this.receiverType = null;
this.outerVarPreconditions = null;
this.typeParameters = ImmutableList.of();
this.isLoose = isLoose;
this.isAbstract = false;
}
/**
* Checks a number of preconditions on the function, ensuring that it has
* been properly initialized. This is called automatically by all public
* factory methods. Only TOP_FUNCTION and LOOSE_TOP_FUNCTION are not
* validated.
*/
@SuppressWarnings("ReferenceEquality")
private void checkValid() {
if (isTopFunction() || isQmarkFunction()) {
return;
}
Preconditions.checkNotNull(requiredFormals,
"null required formals for function: %s", this);
for (JSType formal : requiredFormals) {
Preconditions.checkNotNull(formal);
// A loose function has bottom formals in the bwd direction of NTI.
// See NTI#analyzeLooseCallNodeBwd.
Preconditions.checkState(isLoose || !formal.isBottom());
}
Preconditions.checkNotNull(optionalFormals,
"null optional formals for function: %s", this);
for (JSType formal : optionalFormals) {
Preconditions.checkNotNull(formal);
Preconditions.checkState(!formal.isBottom());
}
Preconditions.checkState(restFormals == null || !restFormals.isBottom());
Preconditions.checkNotNull(returnType);
}
/** Returns the JSTypes instance stored by this object. */
JSTypes getCommonTypes() {
return this.commonTypes;
}
/**
* Returns true if this function type is "loose". When an unannotated formal parameter of
* some function is itself used as a function, we infer a loose function type for it.
* For example: {@code function f(g) { return g(5) - 2; }}. Here, g is a loose number→number
* function. A loose function type may have required formal parameters that are bottom.
*/
public boolean isLoose() {
return isLoose;
}
/**
* Returns a loose version of this function type, for example, as the result of
* specializing or joining this function with a "loose top" or "question mark"
* function (in both of these cases, no formal parameters need to be combined).
*/
FunctionType withLoose() {
if (isLoose()) {
return this;
}
if (isTopFunction()) {
return this.commonTypes.LOOSE_TOP_FUNCTION;
}
return new FunctionType(
this.commonTypes,
requiredFormals, optionalFormals, restFormals, returnType, nominalType,
receiverType, outerVarPreconditions, typeParameters, true, isAbstract);
}
public boolean isAbstract() {
return isAbstract;
}
public boolean isConstructorOfAbstractClass() {
return isUniqueConstructor()
&& this.nominalType.getNominalTypeIfSingletonObj().isAbstractClass();
}
/**
* Builds a function type, adjusting its inputs to a "canonical" form if necessary, e.g.,
* replacing null arguments with empty collections. Optional parameters whose types are equal to
* the rest type are also removed.
*/
static FunctionType normalized(
JSTypes commonTypes,
List requiredFormals,
List optionalFormals,
JSType restFormals,
JSType retType,
JSType nominalType,
JSType receiverType,
Map outerVars,
ImmutableList typeParameters,
boolean isLoose,
boolean isAbstract) {
if (requiredFormals == null) {
requiredFormals = ImmutableList.of();
}
if (optionalFormals == null) {
optionalFormals = ImmutableList.of();
}
if (outerVars == null) {
outerVars = ImmutableMap.of();
}
if (typeParameters == null) {
typeParameters = ImmutableList.of();
}
if (restFormals != null) {
// Remove trailing optional params w/ type equal to restFormals
for (int i = optionalFormals.size() - 1; i >= 0; i--) {
if (restFormals.equals(optionalFormals.get(i))) {
optionalFormals.remove(i);
} else {
break;
}
}
}
return new FunctionType(
commonTypes,
ImmutableList.copyOf(requiredFormals),
ImmutableList.copyOf(optionalFormals),
restFormals, retType, nominalType, receiverType,
ImmutableMap.copyOf(outerVars),
typeParameters,
isLoose,
isAbstract);
}
/**
* Called in the beginning of type checking, when the JSTypes object is being initialized.
* Creates a few basic function types:
* TOP_FUNCTION, a supertype of all functions;
* LOOSE_TOP_FUNCTION, same as top but loose;
* QMARK_FUNCTION, the "any" function; a subtype and supertype of all function types; and
* BOTTOM_FUNCTION, a subtype of all functions.
*/
static Map createInitialFunctionTypes(JSTypes commonTypes) {
LinkedHashMap functions = new LinkedHashMap<>();
functions.put(
"QMARK_FUNCTION",
FunctionType.normalized(
commonTypes, null, null, commonTypes.UNKNOWN, commonTypes.UNKNOWN,
null, null, null, null, true, false));
functions.put(
"BOTTOM_FUNCTION",
FunctionType.normalized(
commonTypes, null, null, null, commonTypes.BOTTOM, null, null, null, null, false,
false));
functions.put("TOP_FUNCTION", new FunctionType(commonTypes, false));
functions.put("LOOSE_TOP_FUNCTION", new FunctionType(commonTypes, true));
return functions;
}
/**
* Returns true if this function is either version (loose or not) of the
* top function (which takes all bottoms and returns top).
*/
public boolean isTopFunction() {
return this == this.commonTypes.TOP_FUNCTION || this == this.commonTypes.LOOSE_TOP_FUNCTION;
}
/** Null-safe version of {@link JSType#getNominalTypeIfSingletonObj}. */
private static NominalType getNominalTypeIfSingletonObj(JSType t) {
return t == null ? null : t.getNominalTypeIfSingletonObj();
}
/**
* Returns true if this type is some sort of constructor, i.e. function(new: T)
* for any value of T (whether it be a class, interface, type variable, or union
* thereof). This is a looser version of {@link #isUniqueConstructor} and
* {@link #isInterfaceDefinition}.
*/
public boolean isSomeConstructorOrInterface() {
return this.nominalType != null;
}
/**
* Returns true if this is a constructor for a single class, i.e.
* function(new: Foo} where Foo is a concrete class.
*/
public boolean isUniqueConstructor() {
NominalType nt = getNominalTypeIfSingletonObj(this.nominalType);
return nt != null && nt.isClass();
}
/**
* Returns true if this is a constructor for a single interface, i.e.
* function(new: Foo} where Foo is an interface definition.
*/
public boolean isInterfaceDefinition() {
NominalType nt = getNominalTypeIfSingletonObj(this.nominalType);
return nt != null && nt.isInterface();
}
/**
* Returns the prototype object of the superclass, or null if there is
* no superclass.
*
* @throws IllegalStateException if this is not a unique constructor.
*/
public JSType getSuperPrototype() {
Preconditions.checkState(isUniqueConstructor());
NominalType nt = getNominalTypeIfSingletonObj(this.nominalType);
NominalType superClass = nt.getInstantiatedSuperclass();
return superClass == null ? null : superClass.getPrototypePropertyOfCtor();
}
/**
* Returns true if this function is the common QMARK_FUNCTION type
* (function(...?): ?), determined by reference equality.
*/
@SuppressWarnings("ReferenceEquality")
public boolean isQmarkFunction() {
return this == this.commonTypes.QMARK_FUNCTION;
}
/**
* Returns true if there exist any function values that can inhabit the given function type.
* (In our type system, this is true for all non-bottom function types.)
*/
@SuppressWarnings("ReferenceEquality")
static boolean isInhabitable(FunctionType f) {
return f == null || f != f.commonTypes.BOTTOM_FUNCTION;
}
public boolean hasRestFormals() {
return restFormals != null;
}
/**
* Returns the type of this function's rest parameter.
*
* @throws IllegalStateException if there is no rest parameter.
*/
public JSType getRestFormalsType() {
Preconditions.checkNotNull(restFormals);
return restFormals;
}
/**
* Returns the formal parameter in the given (0-indexed) position,
* or null if the position is past the end of the parameter list.
*
* @throws IllegalStateException if this is the top function (rather than returning bottom).
*/
public JSType getFormalType(int argpos) {
Preconditions.checkState(!isTopFunction());
int numReqFormals = requiredFormals.size();
if (argpos < numReqFormals) {
return requiredFormals.get(argpos);
} else if (argpos < numReqFormals + optionalFormals.size()) {
return optionalFormals.get(argpos - numReqFormals);
} else {
// Note: as requiredFormals and optionalFormals are both ImmutableLists,
// they can never return null. This is the only codepath that can return
// null, and only if there is no rest parameter.
return restFormals;
}
}
public JSType getReturnType() {
return returnType;
}
/**
* Returns the type of the closed-over variable of the given name,
* or null if the named variable is not in the closure.
*
* @throws IllegalStateException if this is the top function.
*/
public JSType getOuterVarPrecondition(String name) {
Preconditions.checkState(!isTopFunction());
return outerVarPreconditions.get(name);
}
/**
* Returns the minimum number of parameters accepted by this function.
*
* @throws IllegalStateException if this is the top function, which has
* effectively-infinite minimum arity.
*/
public int getMinArity() {
Preconditions.checkState(!isTopFunction());
return requiredFormals.size();
}
/**
* Returns the maximum number of parameters accepted by this function,
* or Integer.MAX_VALUE for a function with rest parameters (effectively
* infinite).
*
* @throws IllegalStateException if this is the top function.
*/
public int getMaxArity() {
Preconditions.checkArgument(!isTopFunction());
if (restFormals != null) {
return Integer.MAX_VALUE; // "Infinite" arity
} else {
return requiredFormals.size() + optionalFormals.size();
}
}
/**
* Returns the maximum number of parameters accepted by this function,
* not counting rest parameters.
*
* @throws IllegalStateException if this is the top function.
*/
public int getMaxArityWithoutRestFormals() {
return requiredFormals.size() + optionalFormals.size();
}
/** Returns true if the {@code i}th parameter is required. */
public boolean isRequiredArg(int i) {
return i < requiredFormals.size();
}
/** Returns true if the {@code i}th parameter is an optional parameter. */
public boolean isOptionalArg(int i) {
return i >= requiredFormals.size()
&& i < requiredFormals.size() + optionalFormals.size();
}
/**
* If this type is a constructor (e.g. function(new: Foo)), returns the
* type Foo. If the nominal type is generic, then type parameters are
* set to unknown. Returns null for non-constructors.
*/
public JSType getInstanceTypeOfCtor() {
if (!isGeneric()) {
return this.nominalType;
}
NominalType nominal = getNominalTypeIfSingletonObj(this.nominalType);
return nominal == null
? null
: nominal.instantiateGenerics(this.commonTypes.MAP_TO_UNKNOWN).getInstanceAsJSType();
}
/**
* Returns the 'this' type of the function, or the 'new' type for a
* constructor.
*/
public JSType getThisType() {
return this.receiverType != null ? this.receiverType : this.nominalType;
}
/**
* Say a method f is defined on a generic type {@code Foo}.
* When doing Foo.prototype.f.call (or also .apply), we transform the method type to a
* function F, which includes the type variables of Foo, in this case T.
*/
private FunctionTypeBuilder transformCallApplyHelper() {
FunctionTypeBuilder builder = new FunctionTypeBuilder(this.commonTypes);
if (this.receiverType == null) {
builder.addReqFormal(this.commonTypes.UNKNOWN);
return builder;
}
NominalType nt = this.receiverType.getNominalTypeIfSingletonObj();
if (nt != null && nt.isUninstantiatedGenericType()) {
builder.addTypeParameters(nt.getTypeParameters());
NominalType ntWithIdentity = nt.instantiateGenericsWithIdentity();
builder.addReqFormal(JSType.fromObjectType(ObjectType.fromNominalType(ntWithIdentity)));
} else {
builder.addReqFormal(this.receiverType);
}
return builder;
}
/**
* Returns a FunctionType representing the 'call' property of this
* function (i.e. the receiver type is prepended to the parameter list).
*/
public FunctionType transformByCallProperty() {
if (isTopFunction() || isQmarkFunction() || isLoose) {
return this.commonTypes.QMARK_FUNCTION;
}
FunctionTypeBuilder builder = transformCallApplyHelper();
for (JSType type : this.requiredFormals) {
builder.addReqFormal(type);
}
for (JSType type : this.optionalFormals) {
builder.addOptFormal(type);
}
builder.addRestFormals(this.restFormals);
builder.addRetType(this.returnType);
builder.appendTypeParameters(this.typeParameters);
builder.addAbstract(this.isAbstract);
return builder.buildFunction();
}
/**
* Returns a FunctionType representing the 'apply' property of this
* function. In most cases, only the receiver type is checked, since
* heterogeneous parameter types are not representable without tuples
* in the type system (or other special handling). If the only
* parameter is a rest parameter, then the array argument is typed
* correctly.
*/
public FunctionType transformByApplyProperty() {
if (isTopFunction() || isQmarkFunction() || isLoose) {
return this.commonTypes.QMARK_FUNCTION;
}
if (isGeneric()) {
return instantiateGenericsWithUnknown().transformByApplyProperty();
}
FunctionTypeBuilder builder = transformCallApplyHelper();
JSType arrayContents;
if (getMaxArityWithoutRestFormals() == 0 && hasRestFormals()) {
arrayContents = getRestFormalsType();
} else {
arrayContents = this.commonTypes.UNKNOWN;
}
JSType varargsArray = this.commonTypes.getIArrayLikeInstance(arrayContents);
builder.addOptFormal(JSType.join(this.commonTypes.NULL, varargsArray));
builder.addRetType(this.returnType);
builder.addAbstract(this.isAbstract);
return builder.buildFunction();
}
/**
* Returns this function type as a {@link DeclaredFunctionType}. While
* DeclaredFunctionType allows incomplete types (i.e. null formals or
* returns), this function always returns a complete type. This should
* only be called from GlobalTypeInfo.
*
* @throws IllegalStateException if this function is loose.
*/
public DeclaredFunctionType toDeclaredFunctionType() {
if (isQmarkFunction()) {
return DeclaredFunctionType.qmarkFunctionDeclaration(this.commonTypes);
}
Preconditions.checkState(!isLoose(), "Loose function: %s", this);
FunctionTypeBuilder builder = new FunctionTypeBuilder(this.commonTypes);
if (isGeneric()) {
builder.addTypeParameters(this.typeParameters);
}
for (JSType type : this.requiredFormals) {
builder.addReqFormal(type);
}
for (JSType type : this.optionalFormals) {
builder.addOptFormal(type);
}
builder.addRestFormals(this.restFormals);
builder.addRetType(this.returnType);
builder.addNominalType(this.nominalType);
builder.addReceiverType(this.receiverType);
builder.addAbstract(this.isAbstract);
return builder.buildDeclaration();
}
/**
* Null-safe version of {@link JSType#meet}, treating nulls as top.
* Returns null if both inputs are null, or if the result would be
* bottom.
*/
private static JSType nullAcceptingMeet(JSType t1, JSType t2) {
if (t1 == null) {
return t2;
}
if (t2 == null) {
return t1;
}
JSType tmp = JSType.meet(t1, t2);
return tmp.isBottom() ? null : tmp;
}
/**
* Merges a loose function type with another function. This is strictly
* neither a join nor a meet, but instead joins both the formals and
* the return. This behavior is appropriate since loose functions are
* the result of inferring facts about an unannotated function type,
* so seeing it accept or return a particular type only allows adding
* types to the union of types alread seen in that position.
*/
// TODO(dimvar): we need to clean up the combination of loose functions with
// new: and/or this: types. Eg, this.nominalType doesn't appear at all.
private static FunctionType looseMerge(FunctionType f1, FunctionType f2) {
Preconditions.checkArgument(f1.isLoose() || f2.isLoose());
FunctionTypeBuilder builder = new FunctionTypeBuilder(f1.commonTypes);
int minRequiredArity = Math.min(f1.getMinArity(), f2.getMinArity());
for (int i = 0; i < minRequiredArity; i++) {
builder.addReqFormal(JSType.nullAcceptingJoin(
f1.getFormalType(i), f2.getFormalType(i)));
}
int maxTotalArity = Math.max(
f1.requiredFormals.size() + f1.optionalFormals.size(),
f2.requiredFormals.size() + f2.optionalFormals.size());
for (int i = minRequiredArity; i < maxTotalArity; i++) {
JSType t = JSType.nullAcceptingJoin(f1.getFormalType(i), f2.getFormalType(i));
if (t != null && t.isBottom()) {
// We will add the optional formal of the loose function in the fwd
// direction, when we have better type information.
break;
}
builder.addOptFormal(t);
}
// Loose types never have varargs, because there is no way for that
// information to make it to a function summary
return builder.addRetType(
JSType.nullAcceptingJoin(f1.returnType, f2.returnType))
.addLoose().buildFunction();
}
/**
* Returns true if this function is a valid override of other.
* Specifically, this requires that this function be a subtype of other.
*/
public boolean isValidOverride(FunctionType other) {
// Note: SubtypeCache.create() is cheap, since the data structure is persistent.
// The cache is used to handle cycles in types' dependencies.
return isSubtypeOfHelper(other, false, SubtypeCache.create(), null);
}
// We want to warn about argument mismatch, so we don't consider a function
// with N required arguments to have restFormals of type TOP.
// But we allow joins (eg after an IF) to change arity, eg,
// number->number ∨ number,number->number = number,number->number
/** Returns true if this function is a subtype of {@code other}. */
boolean isSubtypeOf(FunctionType other, SubtypeCache subSuperMap) {
return isSubtypeOfHelper(other, true, subSuperMap, null);
}
/**
* Fills boxedInfo with the reason why f1 is not a subtype of f2,
* for the purpose of building informative error messages.
*/
static void whyNotSubtypeOf(FunctionType f1, FunctionType f2,
SubtypeCache subSuperMap, MismatchInfo[] boxedInfo) {
Preconditions.checkArgument(boxedInfo.length == 1);
f1.isSubtypeOfHelper(f2, true, subSuperMap, boxedInfo);
}
/**
* Returns true if this function's formal parameter list is exactly
* {@code ...?}. This notation has a special meaning: it is NOT a
* variable-arity function with arguments of ? type; it means that
* we should skip type-checking the arguments. We can therefore use
* it to represent, for example, a constructor of Foos with whatever
* arguments.
*/
private boolean acceptsAnyArguments() {
return this.requiredFormals.isEmpty() && this.optionalFormals.isEmpty()
&& this.restFormals != null && this.restFormals.isUnknown();
}
/**
* Recursively checks that this is a subtype of other: this's parameter
* types are supertypes of other's corresponding parameters (contravariant),
* and this's return type is a subtype of other's return type (covariant).
* Additionally, any 'this' type must be contravariant and any 'new' type
* must be covariant. A cache is used to resolve cycles, and details
* about some mismatched types are written to boxedInfo[0].
*/
private boolean isSubtypeOfHelper(FunctionType other, boolean checkThisType,
SubtypeCache subSuperMap, MismatchInfo[] boxedInfo) {
if (other.isTopFunction() ||
other.isQmarkFunction() || this.isQmarkFunction()) {
return true;
}
if (isTopFunction()) {
return false;
}
// NOTE(dimvar): We never happen to call isSubtypeOf for loose functions.
// If some analyzed program changes this, the preconditions check will tell
// us so we can handle looseness correctly.
Preconditions.checkState(!isLoose() && !other.isLoose());
if (this.isGeneric()) {
if (this.equals(other)) {
return true;
}
// NOTE(dimvar): This is a bug. The code that triggers this should be rare
// and the fix is not trivial, so for now we decided to not fix.
// See unit tests in NewTypeInferenceTest#testGenericsSubtyping
return instantiateGenericsWithUnknown()
.isSubtypeOfHelper(other, checkThisType, subSuperMap, boxedInfo);
}
if (!other.acceptsAnyArguments()) {
// The subtype must have an equal or smaller number of required formals
if (requiredFormals.size() > other.requiredFormals.size()) {
return false;
}
int otherMaxTotalArity =
other.requiredFormals.size() + other.optionalFormals.size();
for (int i = 0; i < otherMaxTotalArity; i++) {
// contravariance in the arguments
JSType thisFormal = getFormalType(i);
JSType otherFormal = other.getFormalType(i);
if (thisFormal != null
&& !thisFormal.isUnknown() && !otherFormal.isUnknown()
&& !otherFormal.isSubtypeOf(thisFormal, subSuperMap)) {
if (boxedInfo != null) {
boxedInfo[0] =
MismatchInfo.makeArgTypeMismatch(i, otherFormal, thisFormal);
}
return false;
}
}
if (other.restFormals != null) {
int thisMaxTotalArity =
this.requiredFormals.size() + this.optionalFormals.size();
if (this.restFormals != null) {
thisMaxTotalArity++;
}
for (int i = otherMaxTotalArity; i < thisMaxTotalArity; i++) {
JSType thisFormal = getFormalType(i);
JSType otherFormal = other.getFormalType(i);
if (thisFormal != null
&& !thisFormal.isUnknown() && !otherFormal.isUnknown()
&& !otherFormal.isSubtypeOf(thisFormal, subSuperMap)) {
return false;
}
}
}
}
// covariance for the new: type
if ((this.nominalType == null && other.nominalType != null)
|| (this.nominalType != null && other.nominalType != null
&& !this.nominalType.isSubtypeOf(other.nominalType, subSuperMap))) {
return false;
}
if (checkThisType) {
// A function without @this can be a subtype of a function with @this.
if (!this.commonTypes.allowMethodsAsFunctions
&& this.receiverType != null && other.receiverType == null) {
return false;
}
if (this.receiverType != null && other.receiverType != null
// Contravariance for the receiver type
&& !other.receiverType.isSubtypeOf(this.receiverType, subSuperMap)) {
return false;
}
}
// covariance in the return type
boolean areRetTypesSubtypes = this.returnType.isUnknown()
|| other.returnType.isUnknown()
|| this.returnType.isSubtypeOf(other.returnType, subSuperMap);
if (boxedInfo != null) {
boxedInfo[0] =
MismatchInfo.makeRetTypeMismatch(other.returnType, this.returnType);
}
return areRetTypesSubtypes;
}
/**
* Returns the join of two nominal types. Optimized to avoid using
* {@link JSType#join} if possible to prevent creating new types.
* Null arguments (and returns) are treated as top. This is called
* by {@link #join} and {@link #meet}.
*/
private static JSType joinNominalTypes(JSType nt1, JSType nt2) {
if (nt1 == null || nt2 == null) {
return null;
}
NominalType n1 = getNominalTypeIfSingletonObj(nt1);
NominalType n2 = getNominalTypeIfSingletonObj(nt2);
if (n1 != null && n2 != null) {
NominalType tmp = NominalType.join(n1, n2);
if (tmp != null) {
return tmp.getInstanceAsJSType();
}
}
// One of the nominal types is non-standard; can't avoid the join
return JSType.join(nt1, nt2);
}
/**
* Returns the meet of two nominal types. Optimized to avoid using
* {@link JSType#meet} if possible to prevent creating new types.
* Null arguments (and returns) are treated as top. This is called
* by {@link #join} and {@link #meet}.
*/
private static JSType meetNominalTypes(JSType nt1, JSType nt2) {
if (nt1 == null) {
return nt2;
}
if (nt2 == null) {
return nt1;
}
NominalType n1 = getNominalTypeIfSingletonObj(nt1);
NominalType n2 = getNominalTypeIfSingletonObj(nt2);
if (n1 != null && n2 != null) {
NominalType tmp = NominalType.pickSubclass(n1, n2);
return tmp == null ? null : tmp.getInstanceAsJSType();
}
// One of the nominal types is non-standard; can't avoid the meet
return JSType.meet(nt1, nt2);
}
/**
* Returns the join (union) of two function types. The return type is
* the join of the input return types. The formal parameters are the
* meets of the input functions' parameters (where required ∧ optional
* is required). Generic functions will lose precision if they are
* not in a direct subclass relationship. If any parameter meets to
* bottom, then BOTTOM_FUNCTION will be returned. Nominal ("new")
* types are joined (they are in "output" position), while receiver
* ("this") types are intersected (they are in "input" position).
*/
static FunctionType join(FunctionType f1, FunctionType f2) {
if (f1 == null) {
return f2;
} else if (f2 == null || f1.equals(f2)) {
return f1;
} else if (f1.isQmarkFunction() || f2.isQmarkFunction()) {
return f1.commonTypes.QMARK_FUNCTION;
} else if (f1.isTopFunction() || f2.isTopFunction()) {
return f1.commonTypes.TOP_FUNCTION;
}
if (f1.isLoose() || f2.isLoose()) {
return looseMerge(f1, f2);
}
if (f1.isGeneric() && f2.isSubtypeOf(f1, SubtypeCache.create())) {
return f1;
} else if (f2.isGeneric() && f1.isSubtypeOf(f2, SubtypeCache.create())) {
return f2;
}
// We lose precision for generic funs that are not in a subtype relation.
if (f1.isGeneric()) {
f1 = f1.instantiateGenericsWithUnknown();
}
if (f2.isGeneric()) {
f2 = f2.instantiateGenericsWithUnknown();
}
JSTypes commonTypes = f1.commonTypes;
FunctionTypeBuilder builder = new FunctionTypeBuilder(commonTypes);
int maxRequiredArity = Math.max(
f1.requiredFormals.size(), f2.requiredFormals.size());
for (int i = 0; i < maxRequiredArity; i++) {
JSType reqFormal = nullAcceptingMeet(f1.getFormalType(i), f2.getFormalType(i));
if (reqFormal == null) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addReqFormal(reqFormal);
}
int maxTotalArity = Math.max(
f1.requiredFormals.size() + f1.optionalFormals.size(),
f2.requiredFormals.size() + f2.optionalFormals.size());
for (int i = maxRequiredArity; i < maxTotalArity; i++) {
JSType optFormal = nullAcceptingMeet(f1.getFormalType(i), f2.getFormalType(i));
if (optFormal == null) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addOptFormal(optFormal);
}
if (f1.restFormals != null && f2.restFormals != null) {
JSType newRestFormals = nullAcceptingMeet(f1.restFormals, f2.restFormals);
if (newRestFormals == null) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addRestFormals(newRestFormals);
}
builder.addRetType(JSType.join(f1.returnType, f2.returnType));
builder.addNominalType(joinNominalTypes(f1.nominalType, f2.nominalType));
builder.addReceiverType(meetNominalTypes(f1.receiverType, f2.receiverType));
return builder.buildFunction();
}
/**
* Specializes this function with the {@code other} function type. It's often
* a no-op, because when this type is not loose, no specialization is required.
*/
FunctionType specialize(FunctionType other) {
if (other == null
|| other.isQmarkFunction() || other.isTopFunction() || equals(other)
|| !isLoose()) {
return this;
}
return isTopFunction() || isQmarkFunction()
? other.withLoose() : looseMerge(this, other);
}
/**
* Returns the meet (intersect) of two function types. The return
* type is the meet of the input returns. The formal parameters
* are the joins of the input functions' parameters (where required
* ∨ optional is optional). Generic functions will lose precision
* if they are not in a direct subclass relationship. If any
* parameter or return results in bottom, then BOTTOM_FUNCTION will
* be returned. Nominal ("new") types are intersected, while
* receiver ("this") types are joined.
*/
static FunctionType meet(FunctionType f1, FunctionType f2) {
if (f1 == null || f2 == null) {
return null;
} else if (f2.isTopFunction() || f1.equals(f2)) {
return f1;
} else if (f1.isTopFunction()) {
return f2;
}
if (f1.isLoose() || f2.isLoose()) {
return looseMerge(f1, f2);
}
if (f1.isGeneric() && f1.isSubtypeOf(f2, SubtypeCache.create())) {
return f1;
} else if (f2.isGeneric() && f2.isSubtypeOf(f1, SubtypeCache.create())) {
return f2;
}
// We lose precision for generic funs that are not in a subtype relation.
if (f1.isGeneric()) {
f1 = f1.instantiateGenericsWithUnknown();
}
if (f2.isGeneric()) {
f2 = f2.instantiateGenericsWithUnknown();
}
JSTypes commonTypes = f1.commonTypes;
FunctionTypeBuilder builder = new FunctionTypeBuilder(commonTypes);
int minRequiredArity = Math.min(
f1.requiredFormals.size(), f2.requiredFormals.size());
for (int i = 0; i < minRequiredArity; i++) {
builder.addReqFormal(
JSType.nullAcceptingJoin(f1.getFormalType(i), f2.getFormalType(i)));
}
int maxTotalArity = Math.max(
f1.requiredFormals.size() + f1.optionalFormals.size(),
f2.requiredFormals.size() + f2.optionalFormals.size());
for (int i = minRequiredArity; i < maxTotalArity; i++) {
JSType optFormalType =
JSType.nullAcceptingJoin(f1.getFormalType(i), f2.getFormalType(i));
if (optFormalType.isBottom()) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addOptFormal(optFormalType);
}
if (f1.restFormals != null || f2.restFormals != null) {
JSType restFormalsType =
JSType.nullAcceptingJoin(f1.restFormals, f2.restFormals);
if (restFormalsType.isBottom()) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addRestFormals(restFormalsType);
}
JSType retType = JSType.meet(f1.returnType, f2.returnType);
if (retType.isBottom()) {
return commonTypes.BOTTOM_FUNCTION;
}
builder.addRetType(retType);
// NOTE(dimvar): these two are not correct. We should be picking the
// greatest lower bound of the types if they are incomparable.
// Eg, this case arises when an interface extends multiple interfaces.
// OTOH, it may be enough to detect that during GTI, and not implement the
// more expensive methods (in NominalType or ObjectType).
builder.addNominalType(meetNominalTypes(f1.nominalType, f2.nominalType));
builder.addReceiverType(joinNominalTypes(f1.receiverType, f2.receiverType));
return builder.buildFunction();
}
/**
* Returns true if this function or {@code f2} is a possibly subtype of the
* other. This requires that all required parameters and the return type
* share a common subtype.
*
* We may consider true subtyping for deferred checks when the formal
* parameter has a loose function type.
*
* @throws IllegalStateException if neither function is loose.
*/
// TODO(sdh): make this method static to emphasize parameter symmetry.
boolean isLooseSubtypeOf(FunctionType f2) {
Preconditions.checkState(this.isLoose() || f2.isLoose());
if (this.isTopFunction() || f2.isTopFunction()) {
return true;
}
int minRequiredArity =
Math.min(this.requiredFormals.size(), f2.requiredFormals.size());
for (int i = 0; i < minRequiredArity; i++) {
if (!JSType.haveCommonSubtype(this.getFormalType(i), f2.getFormalType(i))) {
return false;
}
}
return JSType.haveCommonSubtype(this.getReturnType(), f2.getReturnType());
}
/**
* Returns true if this function has any non-instantiated type parameters.
* (Note: if this type is the result of a generic function type that has been
* instantiated, the type parameters have already been substituted away.)
*/
public boolean isGeneric() {
return !this.typeParameters.isEmpty();
}
/**
* Returns all the non-instantiated type parameter names.
* (Note: if this type is the result of a generic function type that has been
* instantiated, the type parameters have already been substituted away.)
*/
public List getTypeParameters() {
return typeParameters;
}
/**
* Returns a list of parameter types. Any rest parameter type is
* added once at the end of the list.
*/
List getParameterTypes() {
int howmanyTypes = getMaxArityWithoutRestFormals() + (hasRestFormals() ? 1 : 0);
ArrayList types = new ArrayList<>(howmanyTypes);
types.addAll(this.requiredFormals);
types.addAll(this.optionalFormals);
if (hasRestFormals()) {
types.add(this.restFormals);
}
return types;
}
/**
* Unifies this function type, which may contain free type variables, with other,
* a concrete subtype, modifying the supplied typeMultimap to add any new template
* variable type bindings. Returns true if the unification succeeded.
*
* Note that "generic" is not the same as "contains free type variables":
* the former is a type with a "for all" quantifier, while the latter is a
* component nested within a generic type. For example, in the function
* (forall T. (T → T) → T), the outer function is generic, while the
* inner (T → T) is a non-generic function with a free type variable. This
* function is called to unify the inner type in order to determine a concrete
* instantiation for the outer type.
*
* @throws IllegalStateException if this function is generic, is TOP_FUNCTION,
* or is LOOSE_TOP_FUNCTION with any closed-over variables.
*/
@SuppressWarnings("ReferenceEquality")
boolean unifyWithSubtype(FunctionType other, List typeParameters,
Multimap typeMultimap, SubtypeCache subSuperMap) {
Preconditions.checkState(this.typeParameters.isEmpty(),
"Non-empty type parameters %s", this.typeParameters);
Preconditions.checkState(this == this.commonTypes.LOOSE_TOP_FUNCTION
|| this.outerVarPreconditions.isEmpty());
Preconditions.checkState(this != this.commonTypes.TOP_FUNCTION);
if (this == this.commonTypes.LOOSE_TOP_FUNCTION || other.isTopFunction() || other.isLoose()) {
return true;
}
if (other.isGeneric()) {
other = other.instantiateGenericsWithUnknown();
}
if (!acceptsAnyArguments()) {
if (other.requiredFormals.size() > this.requiredFormals.size()) {
return false;
}
int maxNonInfiniteArity = getMaxArityWithoutRestFormals();
for (int i = 0; i < maxNonInfiniteArity; i++) {
JSType thisFormal = getFormalType(i);
JSType otherFormal = other.getFormalType(i);
// NOTE(dimvar): The correct handling here would be to implement
// unifyWithSupertype for JSType, ObjectType, etc, to handle the
// contravariance here.
// But it's probably an overkill to do, so instead we just do a subtype
// check if unification fails. Same for restFormals and receiverType.
// Altenatively, maybe the unifyWith function could handle both subtype
// and supertype, and we'd catch type errors as invalid-argument-type
// after unification. (Not sure this is correct, I'd have to try it.)
if (otherFormal != null
&& !thisFormal.unifyWithSubtype(
otherFormal, typeParameters, typeMultimap, subSuperMap)
&& !thisFormal.isSubtypeOf(otherFormal, SubtypeCache.create())) {
return false;
}
}
if (this.restFormals != null) {
JSType otherRestFormals = other.getFormalType(maxNonInfiniteArity);
if (otherRestFormals != null
&& !this.restFormals.unifyWithSubtype(
otherRestFormals, typeParameters, typeMultimap, subSuperMap)
&& !this.restFormals.isSubtypeOf(
otherRestFormals, SubtypeCache.create())) {
return false;
}
}
}
if ((nominalType == null && other.nominalType != null)
|| (nominalType != null && other.nominalType == null)) {
return false;
}
if (nominalType != null
&& !nominalType.unifyWithSubtype(
other.nominalType, typeParameters, typeMultimap, subSuperMap)) {
return false;
}
// If one of the two functions doesn't use THIS in the body, we can still
// unify.
if (this.receiverType != null && other.receiverType != null
&& !this.receiverType.unifyWithSubtype(
other.receiverType, typeParameters, typeMultimap, subSuperMap)
&& !this.receiverType.isSubtypeOf(
other.receiverType, SubtypeCache.create())) {
return false;
}
return this.returnType.unifyWithSubtype(
other.returnType, typeParameters, typeMultimap, subSuperMap);
}
private FunctionType instantiateGenericsWithUnknown() {
if (!isGeneric()) {
return this;
}
return instantiateGenerics(this.commonTypes.MAP_TO_UNKNOWN);
}
/**
* Unify the two types symmetrically, given that we have already instantiated
* the type variables of interest in {@code f1} and {@code f2}, treating
* JSType.UNKNOWN as a "hole" to be filled.
* @return The unified type, or null if unification fails
*/
static FunctionType unifyUnknowns(FunctionType f1, FunctionType f2) {
Preconditions.checkState(f1 != null || f2 != null);
if (f1 == null || f2 == null) {
return null;
}
if (!f1.typeParameters.isEmpty()) {
f1 = f1.instantiateGenericsWithUnknown();
}
if (!f2.typeParameters.isEmpty()) {
f2 = f2.instantiateGenericsWithUnknown();
}
Preconditions.checkState(!f1.isLoose() && !f2.isLoose());
if (f1.equals(f2)) {
return f1;
}
ImmutableList formals1 = f1.requiredFormals;
ImmutableList formals2 = f2.requiredFormals;
if (formals1.size() != formals2.size()) {
return null;
}
FunctionTypeBuilder builder = new FunctionTypeBuilder(f1.commonTypes);
int numReqFormals = formals1.size();
for (int i = 0; i < numReqFormals; i++) {
JSType t = JSType.unifyUnknowns(formals1.get(i), formals2.get(i));
if (t == null) {
return null;
}
builder.addReqFormal(t);
}
formals1 = f1.optionalFormals;
formals2 = f2.optionalFormals;
if (formals1.size() != formals2.size()) {
return null;
}
int numOptFormals = formals1.size();
for (int i = 0; i < numOptFormals; i++) {
JSType t = JSType.unifyUnknowns(formals1.get(i), formals2.get(i));
if (t == null) {
return null;
}
builder.addOptFormal(t);
}
if ((f1.restFormals == null && f2.restFormals != null)
|| (f1.restFormals != null && f2.restFormals == null)) {
return null;
}
if (f1.restFormals != null) {
JSType t = JSType.unifyUnknowns(f1.restFormals, f2.restFormals);
if (t == null) {
return null;
}
builder.addRestFormals(t);
}
JSType t = JSType.unifyUnknowns(f1.returnType, f2.returnType);
if (t == null) {
return null;
}
builder.addRetType(t);
// Don't unify unknowns in nominal types; it's going to be rare.
if (!Objects.equals(f1.nominalType, f2.nominalType)) {
return null;
}
builder.addNominalType(f1.nominalType);
if (!Objects.equals(f1.receiverType, f2.receiverType)) {
return null;
}
builder.addReceiverType(f1.receiverType);
return builder.buildFunction();
}
/**
* Returns a version of nt with generics substituted from typeMap. This is a more efficient
* alternative to {@link JSType#substituteGenerics} in the case of singleton objects,
* since it can avoid creating new types.
*
* TODO(sdh): is there a reason not to build this optimization directly
* into JSType#substituteGenerics?
*/
private static JSType substGenericsInNomType(JSType nt, Map typeMap) {
if (nt == null) {
return null;
}
NominalType tmp = nt.getNominalTypeIfSingletonObj();
if (tmp == null) {
return nt.substituteGenerics(typeMap);
}
if (!tmp.isGeneric()) {
return tmp.getInstanceAsJSType();
}
if (typeMap.isEmpty()) {
return nt;
}
return JSType.fromObjectType(ObjectType.fromNominalType(
tmp.instantiateGenerics(typeMap)));
}
/**
* Returns a FunctionType with free type variables substituted using typeMap.
* There must be no overlap between this function's generic type parameters
* and the types in the map (i.e. this is not instantiating a generic
* function: that's done in {@link #substituteParametericGenerics}).
* @throws IllegalStateException if typeMap's keys overlap with type parameters.
*/
private FunctionType substituteNominalGenerics(Map typeMap) {
if (typeMap.isEmpty() || this.isTopFunction()) {
return this;
}
if (!this.commonTypes.MAP_TO_UNKNOWN.equals(typeMap)) {
// Before we switched to unique generated names for type variables, a method's type variables
// could shadow type variables defined on the class. Check that this no longer happens.
for (String typeParam : this.typeParameters) {
Preconditions.checkState(!typeMap.containsKey(typeParam));
}
}
FunctionTypeBuilder builder = new FunctionTypeBuilder(this.commonTypes);
for (JSType reqFormal : this.requiredFormals) {
builder.addReqFormal(reqFormal.substituteGenerics(typeMap));
}
for (JSType optFormal : this.optionalFormals) {
builder.addOptFormal(optFormal.substituteGenerics(typeMap));
}
if (this.restFormals != null) {
builder.addRestFormals(restFormals.substituteGenerics(typeMap));
}
builder.addRetType(this.returnType.substituteGenerics(typeMap));
if (isLoose()) {
builder.addLoose();
}
builder.addNominalType(substGenericsInNomType(this.nominalType, typeMap));
builder.addReceiverType(substGenericsInNomType(this.receiverType, typeMap));
// TODO(blickly): Do we need instantiation here?
for (String var : this.outerVarPreconditions.keySet()) {
builder.addOuterVarPrecondition(var, this.outerVarPreconditions.get(var));
}
builder.addTypeParameters(this.typeParameters);
return builder.buildFunction();
}
/** {@see #instantiateGenerics} */
private FunctionType substituteParametricGenerics(Map typeMap) {
if (typeMap.isEmpty()) {
return this;
}
FunctionTypeBuilder builder = new FunctionTypeBuilder(this.commonTypes);
for (JSType reqFormal : this.requiredFormals) {
builder.addReqFormal(reqFormal.substituteGenerics(typeMap));
}
for (JSType optFormal : this.optionalFormals) {
builder.addOptFormal(optFormal.substituteGenerics(typeMap));
}
if (this.restFormals != null) {
builder.addRestFormals(restFormals.substituteGenerics(typeMap));
}
builder.addRetType(this.returnType.substituteGenerics(typeMap));
if (isLoose()) {
builder.addLoose();
}
builder.addNominalType(substGenericsInNomType(this.nominalType, typeMap));
if (this.receiverType != null) {
// NOTE(dimvar):
// We have no way of knowing if receiverType comes from an @this
// annotation, or because this type represents a method.
// In case 1, we would want to substitute in receiverType, in case 2 we
// don't, it's a different scope for the type variables.
// To properly track this we would need two separate fields instead of
// just receiverType.
// Instead, the IF test is a heuristic that works in most cases.
// In the else branch, we are substituting incorrectly when receiverType
// comes from a method declaration, but I have not been able to find a
// test that exposes the bug.
NominalType recvType = getNominalTypeIfSingletonObj(this.receiverType);
if (recvType != null && recvType.isUninstantiatedGenericType()) {
// Receiver came from enclosing class: don't substitute generics.
// TODO(sdh): consider eliminating this branch now that generics are uniquified.
builder.addReceiverType(this.receiverType);
} else {
// Receiver was explicitly specified as @this: substitute.
builder.addReceiverType(substGenericsInNomType(this.receiverType, typeMap));
}
}
// TODO(blickly): Do we need instatiation here?
for (String var : outerVarPreconditions.keySet()) {
builder.addOuterVarPrecondition(var, outerVarPreconditions.get(var));
}
return builder.buildFunction();
}
/**
* FunctionType#substituteGenerics is called while instantiating prototype
* methods of generic nominal types.
*/
FunctionType substituteGenerics(Map concreteTypes) {
if (!isGeneric() || this.commonTypes.MAP_TO_UNKNOWN.equals(concreteTypes)) {
return substituteNominalGenerics(concreteTypes);
}
ImmutableMap.Builder builder = ImmutableMap.builder();
for (Map.Entry concreteTypeEntry : concreteTypes.entrySet()) {
if (!typeParameters.contains(concreteTypeEntry.getKey())) {
builder.put(concreteTypeEntry);
}
}
return substituteNominalGenerics(builder.build());
}
/**
* Returns a FunctionType with generic type parameters instantiated as
* concrete types using typeMap. This is an orthogonal operation to
* {@link #substituteNominalGenerics}, which operates on the free type
* variables found in formal parameters and returns.
*/
public FunctionType instantiateGenerics(Map typeMap) {
Preconditions.checkState(isGeneric());
return substituteParametricGenerics(typeMap);
}
/**
* Given concrete types for the arguments, unify with the formals to create
* a type map, and then instantiate this function as usual, by calling
* {@link #substituteParametricGenerics}.
* @throws IllegalStateException if this is not a generic function.
*/
public FunctionType instantiateGenericsFromArgumentTypes(JSType recvtype, List argTypes) {
Preconditions.checkState(isGeneric());
if (argTypes.size() < getMinArity() || argTypes.size() > getMaxArity()) {
return null;
}
Multimap typeMultimap = LinkedHashMultimap.create();
if (recvtype != null
&& !getThisType()
.unifyWithSubtype(recvtype, typeParameters, typeMultimap, SubtypeCache.create())) {
return null;
}
for (int i = 0, size = argTypes.size(); i < size; i++) {
JSType argType = argTypes.get(i);
if (argType.isBottom()) {
continue;
}
if (!this.getFormalType(i).unifyWithSubtype(
argType, typeParameters, typeMultimap, SubtypeCache.create())) {
return null;
}
}
ImmutableMap.Builder builder = ImmutableMap.builder();
for (String typeParam : typeParameters) {
Collection types = typeMultimap.get(typeParam);
if (types.size() > 1) {
return null;
} else if (types.isEmpty()) {
builder.put(typeParam, this.commonTypes.UNKNOWN);
} else {
builder.put(typeParam, Iterables.getOnlyElement(types));
}
}
return substituteParametricGenerics(builder.build());
}
/** Returns a new FunctionType with the receiverType promoted to the first argument type. */
FunctionType devirtualize() {
return new FunctionType(
commonTypes,
ImmutableList.builder().add(receiverType).addAll(requiredFormals).build(),
optionalFormals,
restFormals,
returnType,
nominalType,
null,
outerVarPreconditions,
typeParameters,
isLoose,
isAbstract);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
Preconditions.checkArgument(obj instanceof FunctionType, "obj is: %s", obj);
FunctionType f2 = (FunctionType) obj;
return Objects.equals(this.requiredFormals, f2.requiredFormals)
&& Objects.equals(this.optionalFormals, f2.optionalFormals)
&& Objects.equals(this.restFormals, f2.restFormals)
&& Objects.equals(this.returnType, f2.returnType)
&& Objects.equals(this.nominalType, f2.nominalType)
&& Objects.equals(this.receiverType, f2.receiverType);
}
@Override
public int hashCode() {
return Objects.hash(requiredFormals, optionalFormals, restFormals,
returnType, nominalType, receiverType);
}
@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}
/**
* Returns a transformed collection of type parameter names, mapped back
* to original (pre-uniquified) names.
*/
private static Collection getPrettyTypeParams(List typeParams) {
return Collections2.transform(
typeParams,
new Function() {
@Override
public String apply(String typeParam) {
return UniqueNameGenerator.getOriginalName(typeParam);
}
});
}
@SuppressWarnings("ReferenceEquality")
public StringBuilder appendTo(StringBuilder builder) {
if (this == this.commonTypes.LOOSE_TOP_FUNCTION) {
return builder.append("LOOSE_TOP_FUNCTION");
} else if (this == this.commonTypes.TOP_FUNCTION) {
return builder.append("TOP_FUNCTION");
} else if (isQmarkFunction()) {
return builder.append("Function");
}
if (!this.typeParameters.isEmpty()) {
builder.append("<");
builder.append(Joiner.on(",").join(getPrettyTypeParams(this.typeParameters)));
builder.append(">");
}
builder.append("function(");
if (nominalType != null) {
builder.append("new:");
builder.append(nominalType);
builder.append(',');
} else if (receiverType != null) {
builder.append("this:");
builder.append(receiverType);
builder.append(',');
}
for (int i = 0; i < requiredFormals.size(); ++i) {
requiredFormals.get(i).appendTo(builder);
builder.append(',');
}
for (int i = 0; i < optionalFormals.size(); ++i) {
optionalFormals.get(i).appendTo(builder);
builder.append("=,");
}
if (restFormals != null) {
builder.append("...");
restFormals.appendTo(builder);
}
// Delete the trailing comma, if present
if (builder.charAt(builder.length() - 1) == ',') {
builder.deleteCharAt(builder.length() - 1);
}
builder.append(')');
if (returnType != null) {
builder.append(':');
returnType.appendTo(builder);
}
if (isLoose()) {
builder.append(" (loose)");
}
if (DEBUGGING && !outerVarPreconditions.isEmpty()) {
builder.append("\tFV: {");
boolean firstIteration = true;
for (Map.Entry entry : outerVarPreconditions.entrySet()) {
if (!firstIteration) {
builder.append(',');
}
builder.append(entry.getKey());
builder.append('=');
entry.getValue().appendTo(builder);
}
builder.append('}');
}
return builder;
}
}