com.google.javascript.rhino.jstype.ArrowType Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
Closure Compiler is a JavaScript optimizing compiler. It parses your
JavaScript, analyzes it, removes dead code and rewrites and minimizes
what's left. It also checks syntax, variable references, and types, and
warns about common JavaScript pitfalls. It is used in many of Google's
JavaScript apps, including Gmail, Google Web Search, Google Maps, and
Google Docs.
/*
*
* ***** 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.UNKNOWN_TYPE;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.Node;
import java.util.Objects;
/**
* The arrow type models a "bare" function type: from some parameter types to
* a return type. JavaScript functions include more things like properties, type
* of THIS, etc, and are modeled by {@link FunctionType}.
*/
final class ArrowType extends JSType {
private static final long serialVersionUID = 1L;
final Node parameters;
JSType returnType;
// Whether the return type is inferred.
final boolean returnTypeInferred;
ArrowType(JSTypeRegistry registry, Node parameters, JSType returnType) {
this(registry, parameters, returnType, false);
}
ArrowType(JSTypeRegistry registry, Node parameters,
JSType returnType, boolean returnTypeInferred) {
super(registry);
this.parameters = parameters == null ?
registry.createParametersWithVarArgs(getNativeType(UNKNOWN_TYPE)) :
parameters;
this.returnType = returnType == null ?
getNativeType(UNKNOWN_TYPE) : returnType;
this.returnTypeInferred = returnTypeInferred;
}
@Override
public boolean isSubtype(JSType that) {
return isSubtype(that, ImplCache.create(), SubtypingMode.NORMAL);
}
@Override
protected boolean isSubtype(JSType other,
ImplCache implicitImplCache, SubtypingMode subtypingMode) {
if (!(other instanceof ArrowType)) {
return false;
}
ArrowType that = (ArrowType) other;
// This is described in Draft 2 of the ES4 spec,
// Section 3.4.7: Subtyping Function Types.
// this.returnType <: that.returnType (covariant)
if (!this.returnType.isSubtype(that.returnType, implicitImplCache, subtypingMode)) {
return false;
}
// that.paramType[i] <: this.paramType[i] (contravariant)
//
// If this.paramType[i] is required,
// then that.paramType[i] is required.
//
// In theory, the "required-ness" should work in the other direction as
// well. In other words, if we have
//
// function f(number, number) {}
// function g(number) {}
//
// Then f *should* not be a subtype of g, and g *should* not be
// a subtype of f. But in practice, we do not implement it this way.
// We want to support the use case where you can pass g where f is
// expected, and pretend that g ignores the second argument.
// That way, you can have a single "no-op" function, and you don't have
// to create a new no-op function for every possible type signature.
//
// So, in this case, g < f, but f !< g
Node thisParam = parameters.getFirstChild();
Node thatParam = that.parameters.getFirstChild();
while (thisParam != null && thatParam != null) {
JSType thisParamType = thisParam.getJSType();
JSType thatParamType = thatParam.getJSType();
if (thisParamType != null) {
if (thatParamType == null ||
!thatParamType.isSubtype(thisParamType, implicitImplCache, subtypingMode)) {
return false;
}
}
boolean thisIsVarArgs = thisParam.isVarArgs();
boolean thatIsVarArgs = thatParam.isVarArgs();
boolean thisIsOptional = thisIsVarArgs || thisParam.isOptionalArg();
boolean thatIsOptional = thatIsVarArgs || thatParam.isOptionalArg();
// "that" can't be a supertype, because it's missing a required argument.
if (!thisIsOptional && thatIsOptional) {
// NOTE(nicksantos): In our type system, we use {function(...?)} and
// {function(...NoType)} to to indicate that arity should not be
// checked. Strictly speaking, this is not a correct formulation,
// because now a sub-function can required arguments that are var_args
// in the super-function. So we special-case this.
boolean isTopFunction =
thatIsVarArgs &&
(thatParamType == null ||
thatParamType.isUnknownType() ||
thatParamType.isNoType());
if (!isTopFunction) {
return false;
}
}
// don't advance if we have variable arguments
if (!thisIsVarArgs) {
thisParam = thisParam.getNext();
}
if (!thatIsVarArgs) {
thatParam = thatParam.getNext();
}
// both var_args indicates the end
if (thisIsVarArgs && thatIsVarArgs) {
thisParam = null;
thatParam = null;
}
}
// "that" can't be a supertype, because it's missing a required argument.
return thisParam == null || thisParam.isOptionalArg() || thisParam.isVarArgs()
|| thatParam != null;
}
/**
* @return True if our parameter spec is equal to {@code that}'s parameter
* spec.
*/
boolean hasEqualParameters(ArrowType that, EquivalenceMethod eqMethod, EqCache eqCache) {
Node thisParam = parameters.getFirstChild();
Node otherParam = that.parameters.getFirstChild();
while (thisParam != null && otherParam != null) {
JSType thisParamType = thisParam.getJSType();
JSType otherParamType = otherParam.getJSType();
if (thisParamType != null) {
// Both parameter lists give a type for this param, it should be equal
if (otherParamType != null &&
!thisParamType.checkEquivalenceHelper(otherParamType, eqMethod, eqCache)) {
return false;
}
} else {
if (otherParamType != null) {
return false;
}
}
// Check var_args/optionality
if (thisParam.isOptionalArg() != otherParam.isOptionalArg()) {
return false;
}
if (thisParam.isVarArgs() != otherParam.isVarArgs()) {
return false;
}
thisParam = thisParam.getNext();
otherParam = otherParam.getNext();
}
// One of the parameters is null, so the types are only equal if both
// parameter lists are null (they are equal).
return thisParam == otherParam;
}
boolean checkArrowEquivalenceHelper(
ArrowType that, EquivalenceMethod eqMethod, EqCache eqCache) {
// Please keep this method in sync with the hashCode() method below.
if (!returnType.checkEquivalenceHelper(
that.returnType, eqMethod, eqCache)) {
return false;
}
return hasEqualParameters(that, eqMethod, eqCache);
}
@Override
public int hashCode() {
int hashCode = Objects.hashCode(returnType);
if (parameters != null) {
Node param = parameters.getFirstChild();
while (param != null) {
hashCode = hashCode * 31 + Objects.hashCode(param.getJSType());
param = param.getNext();
}
}
return hashCode;
}
@Override
public JSType getLeastSupertype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public JSType getGreatestSubtype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public TernaryValue testForEquality(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public T visit(Visitor visitor) {
throw new UnsupportedOperationException();
}
@Override T visit(RelationshipVisitor visitor, JSType that) {
throw new UnsupportedOperationException();
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.TRUE;
}
@Override
JSType resolveInternal(ErrorReporter reporter, StaticTypedScope scope) {
returnType = safeResolve(returnType, reporter, scope);
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
paramNode.setJSType(paramNode.getJSType().resolve(reporter, scope));
}
}
return this;
}
boolean hasUnknownParamsOrReturn() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type == null || type.isUnknownType()) {
return true;
}
}
}
return returnType == null || returnType.isUnknownType();
}
@Override
StringBuilder appendTo(StringBuilder sb, boolean forAnnotations) {
return sb.append("[ArrowType]");
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return returnType.hasAnyTemplateTypes()
|| hasTemplatedParameterType();
}
private boolean hasTemplatedParameterType() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type != null && type.hasAnyTemplateTypes()) {
return true;
}
}
}
return false;
}
}