From 8632f4e7e808f01f49cdfb3af220fd4248357a3f Mon Sep 17 00:00:00 2001 From: nickreid Date: Tue, 15 Jan 2019 15:45:09 -0800 Subject: [PATCH] Improves function call template type inference by decomposing argument 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|Set` to a function expecting `Iterable` 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 --- .../javascript/jscomp/TypeInference.java | 28 +++- .../javascript/jscomp/TypeInferenceTest.java | 145 ++++++++++++++++-- 2 files changed, 157 insertions(+), 16 deletions(-) diff --git a/src/com/google/javascript/jscomp/TypeInference.java b/src/com/google/javascript/jscomp/TypeInference.java index 496cc8fc88a..7bcce7d1339 100644 --- a/src/com/google/javascript/jscomp/TypeInference.java +++ b/src/com/google/javascript/jscomp/TypeInference.java @@ -1774,18 +1774,32 @@ private Map inferTemplateTypesFromParameters( private void maybeResolveTemplatedType( JSType paramType, JSType argType, - Map resolvedTypes, Set seenTypes) { + Map resolvedTypes, + Set seenTypes) { if (paramType.isTemplateType()) { + // Recursive base case. // example: @param {T} - resolvedTemplateType( - resolvedTypes, paramType.toMaybeTemplateType(), argType); - } else if (paramType.isUnionType()) { + resolvedTemplateType(resolvedTypes, paramType.toMaybeTemplateType(), argType); + return; + } + + // Unpack unions. + if (paramType.isUnionType()) { // example: @param {Array.|NodeList|Arguments|{length:number}} UnionType unionType = paramType.toMaybeUnionType(); - for (JSType alernative : unionType.getAlternates()) { - maybeResolveTemplatedType(alernative, argType, resolvedTypes, seenTypes); + for (JSType alternate : unionType.getAlternates()) { + 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 argFunctionType = argType .restrictByNotNullOrUndefined() diff --git a/test/com/google/javascript/jscomp/TypeInferenceTest.java b/test/com/google/javascript/jscomp/TypeInferenceTest.java index e863bfc520c..617587658fa 100644 --- a/test/com/google/javascript/jscomp/TypeInferenceTest.java +++ b/test/com/google/javascript/jscomp/TypeInferenceTest.java @@ -1607,16 +1607,143 @@ public void testIssue785() { } @Test - public void testTemplateForTypeTransformationTests() { + public void testFunctionTemplateType_literalParam() { inFunction( - "/**\n" - + " * @param {T} a\n" - + " * @return {R}\n" - + " * @template T, R\n" - + " */\n" - + "function f(a){}\n" - + "var result = f(10);"); - verify("result", UNKNOWN_TYPE); + lines( + "/**", + " * @template T", + " * @param {T} a", + " * @return {T}", + " */", + "function f(a){}", + "", + "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|number} a", + " * @return {T}", + " */", + "function f(a){}", + "", + "var result = f(/** @type {!Iterable} */ ({}));")); + verify("result", NUMBER_TYPE); + } + + @Test + public void testFunctionTemplateType_unpacksUnions_fromArgType() { + inFunction( + lines( + "/**", + " * @template T", + " * @param {!Iterable} a", + " * @return {T}", + " */", + "function f(a){}", + "", + // The arg type is illegal, but the inference should still work. + "var result = f(/** @type {!Iterable|number} */ ({}));")); + verify("result", NUMBER_TYPE); + } + + @Test + public void testFunctionTemplateType_unpacksUnions_fromArgType_acrossSubtypes() { + inFunction( + lines( + "/**", + " * @template T", + " * @param {!Iterable} a", + " * @return {T}", + " */", + "function f(a){}", + "", + "var result = f(/** @type {!Array|!Generator} */ ({}));")); + verify("result", registry.createUnionType(NUMBER_TYPE, STRING_TYPE)); } @Test