Skip to content

Commit

Permalink
Modify IThenables and its subclasses to use covariant generics. This …
Browse files Browse the repository at this point in the history
…is a temporary measure until full generics variance support can be added.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191618427
  • Loading branch information
concavelenz authored and lauraharker committed Apr 4, 2018
1 parent 38c5d06 commit d9934ad
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 13 deletions.
39 changes: 31 additions & 8 deletions src/com/google/javascript/rhino/jstype/JSType.java
Expand Up @@ -79,7 +79,7 @@ public abstract class JSType implements TypeI {
private static final CanCastToVisitor CAN_CAST_TO_VISITOR =
new CanCastToVisitor();

private static final ImmutableSet<String> COVARIANT_TYPES =
private static final ImmutableSet<String> BIVARIANT_TYPES =
ImmutableSet.of("Object", "IArrayLike", "Array");

/**
Expand Down Expand Up @@ -1579,16 +1579,25 @@ static boolean isSubtypeHelper(JSType thisType, JSType thatType,
TemplateTypeMap thisTypeParams = thisType.getTemplateTypeMap();
TemplateTypeMap thatTypeParams = thatType.getTemplateTypeMap();
boolean templateMatch = true;
if (isExemptFromTemplateTypeInvariance(thatType)) {
if (isBivariantType(thatType)) {
// Array and Object are exempt from template type invariance; their
// template types maps are considered a match only if the ObjectElementKey
// values are subtypes/supertypes of one another.
// template types maps are bivariant. That is they are considered
// a match if either ObjectElementKey values are subtypes of the
// other.
TemplateType key = thisType.registry.getObjectElementKey();
JSType thisElement = thisTypeParams.getResolvedTemplateType(key);
JSType thatElement = thatTypeParams.getResolvedTemplateType(key);

templateMatch = thisElement.isSubtype(thatElement, implicitImplCache, subtypingMode)
|| thatElement.isSubtype(thisElement, implicitImplCache, subtypingMode);
} else if (isIThenableSubtype(thatType)) {
// NOTE: special case IThenable subclass (Promise, etc). These classes are expected to be
// covariant.
TemplateType key = thisType.registry.getThenableValueKey();
JSType thisElement = thisTypeParams.getResolvedTemplateType(key);
JSType thatElement = thatTypeParams.getResolvedTemplateType(key);

templateMatch = thisElement.isSubtype(thatElement, implicitImplCache, subtypingMode);
} else {
templateMatch = thisTypeParams.checkEquivalenceHelper(
thatTypeParams, EquivalenceMethod.INVARIANT, subtypingMode);
Expand Down Expand Up @@ -1622,12 +1631,26 @@ static boolean isSubtypeHelper(JSType thisType, JSType thatType,
}

/**
* Determines if the specified type is exempt from standard invariant
* templatized typing rules.
* Determines if the supplied type should be checked as a bivariant
* templatized type rather the standard invariant templatized type
* rules.
*/
static boolean isExemptFromTemplateTypeInvariance(JSType type) {
static boolean isBivariantType(JSType type) {
ObjectType objType = type.toObjectType();
return objType == null || COVARIANT_TYPES.contains(objType.getReferenceName());
return objType != null
// && objType.isNativeObjectType()
&& BIVARIANT_TYPES.contains(objType.getReferenceName());
}

/**
* Determines if the specified type is exempt from standard invariant templatized typing rules.
*/
static boolean isIThenableSubtype(JSType type) {
if (type.isTemplatizedType()) {
TemplatizedType ttype = type.toMaybeTemplatizedType();
return ttype.getTemplateTypeMap().hasTemplateKey(ttype.registry.getThenableValueKey());
}
return false;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/com/google/javascript/rhino/jstype/JSTypeNative.java
Expand Up @@ -109,6 +109,9 @@ public enum JSTypeNative {
I_OBJECT_FUNCTION_TYPE,
I_OBJECT_TYPE,

I_THENABLE_FUNCTION_TYPE,
I_THENABLE_TYPE,

NULL_TYPE,

NUMBER_TYPE,
Expand Down
38 changes: 33 additions & 5 deletions src/com/google/javascript/rhino/jstype/JSTypeRegistry.java
Expand Up @@ -114,6 +114,9 @@ public class JSTypeRegistry implements TypeIRegistry {
/** The template variable corresponding to the VALUE type in {@code Generator<VALUE>} */
private TemplateType generatorTemplate;

/** The template variable corresponding to the VALUE type in {@code IThenable<VALUE>} */
private TemplateType iThenableTemplateKey;

/**
* The template variable in {@code Array<T>}
*/
Expand Down Expand Up @@ -226,18 +229,18 @@ public JSTypeRegistry(ErrorReporter reporter, Set<String> forwardDeclaredTypes)
}

private JSType getSentinelObjectLiteral() {
if (this.sentinelObjectLiteral == null) {
this.sentinelObjectLiteral = createAnonymousObjectType(null);
if (sentinelObjectLiteral == null) {
sentinelObjectLiteral = createAnonymousObjectType(null);
}
return this.sentinelObjectLiteral;
return sentinelObjectLiteral;
}

/**
* @return The template variable corresponding to the property value type for
* Javascript Objects and Arrays.
*/
public TemplateType getObjectElementKey() {
return this.iObjectElementTemplateKey;
return iObjectElementTemplateKey;
}

/**
Expand All @@ -246,7 +249,16 @@ public TemplateType getObjectElementKey() {
*/
public TemplateType getObjectIndexKey() {
checkNotNull(iObjectIndexTemplateKey);
return this.iObjectIndexTemplateKey;
return iObjectIndexTemplateKey;
}

/**
* @return The template variable corresponding to the
* property key type of the built-in Javascript object.
*/
public TemplateType getThenableValueKey() {
checkNotNull(iThenableTemplateKey);
return iThenableTemplateKey;
}

/**
Expand Down Expand Up @@ -325,6 +337,7 @@ private void initializeBuiltInTypes() {
iteratorTemplate = new TemplateType(this, "VALUE");
generatorTemplate = new TemplateType(this, "VALUE");
iterableTemplate = new TemplateType(this, "VALUE");
iThenableTemplateKey = new TemplateType(this, "TYPE");

// Top Level Prototype (the One)
// The initializations of TOP_LEVEL_PROTOTYPE and OBJECT_FUNCTION_TYPE
Expand Down Expand Up @@ -470,6 +483,20 @@ private void initializeBuiltInTypes() {
registerNativeType(JSTypeNative.GENERATOR_FUNCTION_TYPE, generatorFunctionType);
registerNativeType(JSTypeNative.GENERATOR_TYPE, generatorFunctionType.getInstanceType());

FunctionType ithenableFunctionType =
new FunctionType(
this,
"IThenable",
null,
createArrowType(),
null,
createTemplateTypeMap(ImmutableList.of(iThenableTemplateKey), null),
Kind.INTERFACE,
true,
false);
registerNativeType(JSTypeNative.I_THENABLE_FUNCTION_TYPE, ithenableFunctionType);
registerNativeType(JSTypeNative.I_THENABLE_TYPE, ithenableFunctionType.getInstanceType());

// Boolean
FunctionType BOOLEAN_OBJECT_FUNCTION_TYPE =
new FunctionType(
Expand Down Expand Up @@ -825,6 +852,7 @@ private void initializeRegistry() {
registerGlobalType(getNativeType(JSTypeNative.GENERATOR_TYPE));
registerGlobalType(getNativeType(JSTypeNative.DATE_TYPE));
registerGlobalType(getNativeType(JSTypeNative.I_OBJECT_TYPE));
registerGlobalType(getNativeType(JSTypeNative.I_THENABLE_TYPE));
registerGlobalType(getNativeType(JSTypeNative.NULL_TYPE));
registerGlobalType(getNativeType(JSTypeNative.NULL_TYPE), "Null");
registerGlobalType(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE));
Expand Down
106 changes: 106 additions & 0 deletions test/com/google/javascript/jscomp/TypeCheckTest.java
Expand Up @@ -19463,6 +19463,112 @@ public void testSuperclassDefinedInBlockOnNamespace() {
"required: string"));
}

public void testCovariantIThenable1() {
testTypes(
lines(
"/** @type {!IThenable<string|number>} */ var x;",
"function fn(/** !IThenable<string> */ a ) {",
" x = a;",
"}"));
}

public void testCovariantIThenable2() {
testTypes(
lines(
"/** @type {!IThenable<string>} */ var x;",
"function fn(/** !IThenable<string|number> */ a ) {",
" x = a;",
"}"),
lines(
"assignment",
"found : IThenable<(number|string)>",
"required: IThenable<string>"));
}

public void testCovariantIThenable3() {
testTypes(
lines(
"/** @type {!Promise<string|number>} */ var x;",
"function fn(/** !Promise<string> */ a ) {",
" x = a;",
"}"));
}

public void testCovariantIThenable4() {
testTypes(
lines(
"/** @type {!Promise<string>} */ var x;",
"function fn(/** !Promise<string|number> */ a ) {",
" x = a;",
"}"),
lines(
"assignment",
"found : Promise<(number|string)>",
"required: Promise<string>"));
}

public void testCovariantIThenableNonThenable1() {
testTypes(
lines(
"/** @type {!Array<string>} */ var x;",
"function fn(/** !IThenable<string> */ a ) {",
" x = a;",
"}"),
lines(
"assignment", //
"found : IThenable<string>",
"required: Array<string>"));
}

public void testCovariantIThenableNonThenable2() {
testTypes(
lines(
"/** @type {!IThenable<string>} */ var x;",
"function fn(/** !Array<string> */ a ) {",
" x = a;",
"}"),
lines(
"assignment", //
"found : Array<string>",
"required: IThenable<string>"));
}

public void testCovariantIThenableNonThenable3() {
testTypes(
lines(
"/** ",
" @constructor",
" @template T",
" */",
"function C() {}",
"/** @type {!C<string>} */ var x;",
"function fn(/** !IThenable<string> */ a ) {",
" x = a;",
"}"),
lines(
"assignment", //
"found : IThenable<string>",
"required: C<string>"));
}

public void testCovariantIThenableNonThenable4() {
testTypes(
lines(
"/** ",
" @constructor",
" @template T",
" */",
"function C() {}",
"/** @type {!IThenable<string>} */ var x;",
"function fn(/** !C<string> */ a ) {",
" x = a;",
"}"),
lines(
"assignment", //
"found : C<string>",
"required: IThenable<string>"));
}

private void testTypes(String js) {
testTypes(js, (String) null);
}
Expand Down

0 comments on commit d9934ad

Please sign in to comment.