Skip to content

Commit

Permalink
Improves function call template type inference by decomposing argumen…
Browse files Browse the repository at this point in the history
…t union types.

Decomposition allows better matching when part of the arg type union, but not the entire union, matches the parameter type. Example, passing `Array<string>|Set<string>` to a function expecting `Iterable<T>` now infers `T` to be `string`.

This change also includes additional tests for function call template type inference to confirm existing behaviour, not directly related to this change.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=229455278
  • Loading branch information
nreid260 authored and lauraharker committed Jan 16, 2019
1 parent f66ff18 commit 8632f4e
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 16 deletions.
28 changes: 21 additions & 7 deletions src/com/google/javascript/jscomp/TypeInference.java
Expand Up @@ -1774,18 +1774,32 @@ private Map<TemplateType, JSType> inferTemplateTypesFromParameters(
private void maybeResolveTemplatedType( private void maybeResolveTemplatedType(
JSType paramType, JSType paramType,
JSType argType, JSType argType,
Map<TemplateType, JSType> resolvedTypes, Set<JSType> seenTypes) { Map<TemplateType, JSType> resolvedTypes,
Set<JSType> seenTypes) {
if (paramType.isTemplateType()) { if (paramType.isTemplateType()) {
// Recursive base case.
// example: @param {T} // example: @param {T}
resolvedTemplateType( resolvedTemplateType(resolvedTypes, paramType.toMaybeTemplateType(), argType);
resolvedTypes, paramType.toMaybeTemplateType(), argType); return;
} else if (paramType.isUnionType()) { }

// Unpack unions.
if (paramType.isUnionType()) {
// example: @param {Array.<T>|NodeList|Arguments|{length:number}} // example: @param {Array.<T>|NodeList|Arguments|{length:number}}
UnionType unionType = paramType.toMaybeUnionType(); UnionType unionType = paramType.toMaybeUnionType();
for (JSType alernative : unionType.getAlternates()) { for (JSType alternate : unionType.getAlternates()) {
maybeResolveTemplatedType(alernative, argType, resolvedTypes, seenTypes); maybeResolveTemplatedType(alternate, argType, resolvedTypes, seenTypes);
} }
} else if (paramType.isFunctionType()) { return;
} else if (argType.isUnionType()) {
UnionType unionType = argType.toMaybeUnionType();
for (JSType alternate : unionType.getAlternates()) {
maybeResolveTemplatedType(paramType, alternate, resolvedTypes, seenTypes);
}
return;
}

if (paramType.isFunctionType()) {
FunctionType paramFunctionType = paramType.toMaybeFunctionType(); FunctionType paramFunctionType = paramType.toMaybeFunctionType();
FunctionType argFunctionType = argType FunctionType argFunctionType = argType
.restrictByNotNullOrUndefined() .restrictByNotNullOrUndefined()
Expand Down
145 changes: 136 additions & 9 deletions test/com/google/javascript/jscomp/TypeInferenceTest.java
Expand Up @@ -1607,16 +1607,143 @@ public void testIssue785() {
} }


@Test @Test
public void testTemplateForTypeTransformationTests() { public void testFunctionTemplateType_literalParam() {
inFunction( inFunction(
"/**\n" lines(
+ " * @param {T} a\n" "/**",
+ " * @return {R}\n" " * @template T",
+ " * @template T, R\n" " * @param {T} a",
+ " */\n" " * @return {T}",
+ "function f(a){}\n" " */",
+ "var result = f(10);"); "function f(a){}",
verify("result", UNKNOWN_TYPE); "",
"var result = f(10);"));
verify("result", NUMBER_TYPE);
}

@Test
public void testFunctionTemplateType_unionsPossibilities() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {T} a",
" * @param {T} b",
" * @return {T}",
" */",
"function f(a, b){}",
"",
"var result = f(10, 'x');"));
verify("result", registry.createUnionType(NUMBER_TYPE, STRING_TYPE));
}

@Test
public void testFunctionTemplateType_willUseUnknown() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {T} a",
" * @return {T}",
" */",
"function f(a){}",
"",
"var result = f(/** @type {?} */ ({}));"));
verify("result", UNKNOWN_TYPE);
}

@Test
public void testFunctionTemplateType_willUseUnknown_butPrefersTighterTypes() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {T} a",
" * @param {T} b",
" * @param {T} c",
" * @return {T}",
" */",
"function f(a, b, c){}",
"",
// Make sure `?` is dispreferred before *and* after a known type.
"var result = f('x', /** @type {?} */ ({}), 5);"));
verify("result", registry.createUnionType(NUMBER_TYPE, STRING_TYPE));
}

@Test
public void testFunctionTemplateType_recursesIntoFunctionParams() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {function(T)} a",
" * @return {T}",
" */",
"function f(a){}",
"",
"var result = f(function(/** number */ a) { });"));
verify("result", NUMBER_TYPE);
}

@Test
public void testFunctionTemplateType_recursesIntoFunctionParams_throughUnknown() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {function(T)=} a",
" * @return {T}",
" */",
"function f(a){}",
"",
"var result = f(/** @type {?} */ ({}));"));
verify("result", UNKNOWN_TYPE);
}

@Test
public void testFunctionTemplateType_unpacksUnions_fromParamType() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {!Iterable<T>|number} a",
" * @return {T}",
" */",
"function f(a){}",
"",
"var result = f(/** @type {!Iterable<number>} */ ({}));"));
verify("result", NUMBER_TYPE);
}

@Test
public void testFunctionTemplateType_unpacksUnions_fromArgType() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {!Iterable<T>} a",
" * @return {T}",
" */",
"function f(a){}",
"",
// The arg type is illegal, but the inference should still work.
"var result = f(/** @type {!Iterable<number>|number} */ ({}));"));
verify("result", NUMBER_TYPE);
}

@Test
public void testFunctionTemplateType_unpacksUnions_fromArgType_acrossSubtypes() {
inFunction(
lines(
"/**",
" * @template T",
" * @param {!Iterable<T>} a",
" * @return {T}",
" */",
"function f(a){}",
"",
"var result = f(/** @type {!Array<number>|!Generator<string>} */ ({}));"));
verify("result", registry.createUnionType(NUMBER_TYPE, STRING_TYPE));
} }


@Test @Test
Expand Down

0 comments on commit 8632f4e

Please sign in to comment.