diff --git a/src/com/google/javascript/jscomp/TypeInference.java b/src/com/google/javascript/jscomp/TypeInference.java index 13b0b298066..981b75b57cf 100644 --- a/src/com/google/javascript/jscomp/TypeInference.java +++ b/src/com/google/javascript/jscomp/TypeInference.java @@ -53,6 +53,7 @@ import com.google.javascript.rhino.jstype.TemplateType; import com.google.javascript.rhino.jstype.TemplateTypeMap; import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer; +import com.google.javascript.rhino.jstype.TemplatizedType; import com.google.javascript.rhino.jstype.UnionType; import java.util.ArrayList; import java.util.Collections; @@ -1215,11 +1216,11 @@ private void maybeResolveTemplatedType( JSType argType, Map resolvedTypes, Set seenTypes) { if (paramType.isTemplateType()) { - // @param {T} + // example: @param {T} resolvedTemplateType( resolvedTypes, paramType.toMaybeTemplateType(), argType); } else if (paramType.isUnionType()) { - // @param {Array.|NodeList|Arguments|{length:number}} + // example: @param {Array.|NodeList|Arguments|{length:number}} UnionType unionType = paramType.toMaybeUnionType(); for (JSType alernative : unionType.getAlternates()) { maybeResolveTemplatedType(alernative, argType, resolvedTypes, seenTypes); @@ -1245,7 +1246,7 @@ private void maybeResolveTemplatedType( argFunctionType.getParameters(), resolvedTypes, seenTypes); } } else if (paramType.isRecordType() && !paramType.isNominalType()) { - // @param {{foo:T}} + // example: @param {{foo:T}} if (seenTypes.add(paramType)) { ObjectType paramRecordType = paramType.toObjectType(); ObjectType argObjectType = argType.restrictByNotNullOrUndefined().toObjectType(); @@ -1262,25 +1263,31 @@ private void maybeResolveTemplatedType( seenTypes.remove(paramType); } } else if (paramType.isTemplatizedType()) { - // @param {Array} - ObjectType referencedParamType = paramType - .toMaybeTemplatizedType() - .getReferencedType(); - JSType argObjectType = argType - .restrictByNotNullOrUndefined() - .collapseUnion(); - - - if (argObjectType.isSubtype(referencedParamType)) { - // If the argument type is a subtype of the parameter type, resolve any - // template types amongst their templatized types. - TemplateTypeMap paramTypeMap = paramType.getTemplateTypeMap(); - TemplateTypeMap argTypeMap = argObjectType.getTemplateTypeMap(); - for (TemplateType key : paramTypeMap.getTemplateKeys()) { - maybeResolveTemplatedType( - paramTypeMap.getResolvedTemplateType(key), - argTypeMap.getResolvedTemplateType(key), - resolvedTypes, seenTypes); + // example: @param {Array} + TemplatizedType templatizedParamType = paramType.toMaybeTemplatizedType(); + int keyCount = templatizedParamType.getTemplateTypes().size(); + // TODO(johnlenz): determine why we are creating TemplatizedTypes for + // types with no type arguments. + if (keyCount > 0) { + ObjectType referencedParamType = templatizedParamType.getReferencedType(); + JSType argObjectType = argType + .restrictByNotNullOrUndefined() + .collapseUnion(); + + if (argObjectType.isSubtype(referencedParamType)) { + // If the argument type is a subtype of the parameter type, resolve any + // template types amongst their templatized types. + TemplateTypeMap paramTypeMap = paramType.getTemplateTypeMap(); + + ImmutableList keys = paramTypeMap.getTemplateKeys(); + TemplateTypeMap argTypeMap = argObjectType.getTemplateTypeMap(); + for (int index = keys.size() - keyCount; index < keys.size(); index++) { + TemplateType key = keys.get(index); + maybeResolveTemplatedType( + paramTypeMap.getResolvedTemplateType(key), + argTypeMap.getResolvedTemplateType(key), + resolvedTypes, seenTypes); + } } } } @@ -1402,14 +1409,13 @@ private Map evaluateTypeTransformations( } /** - * For functions with function(this: T, ...) and T as parameters, type - * inference will set the type of this on a function literal argument to the - * the actual type of T. + * For functions that use template types, specialize the function type for + * the call target based on the call-site specific arguments. + * Specifically, this enables inference to set the type of any function + * literal parameters based on these inferred types. */ - private boolean inferTemplatedTypesForCall( - Node n, FunctionType fnType) { - final ImmutableList keys = fnType.getTemplateTypeMap() - .getTemplateKeys(); + private boolean inferTemplatedTypesForCall(Node n, FunctionType fnType) { + ImmutableList keys = fnType.getTemplateTypeMap().getTemplateKeys(); if (keys.isEmpty()) { return false; } @@ -1426,22 +1432,18 @@ private boolean inferTemplatedTypesForCall( } // Try to infer the template types using the type transformations - Map typeTransformations = - evaluateTypeTransformations(keys, inferred); + Map typeTransformations = evaluateTypeTransformations(keys, inferred); if (typeTransformations != null) { inferred.putAll(typeTransformations); } // Replace all template types. If we couldn't find a replacement, we // replace it with UNKNOWN. - TemplateTypeReplacer replacer = new TemplateTypeReplacer( - registry, inferred); + TemplateTypeReplacer replacer = new TemplateTypeReplacer(registry, inferred); Node callTarget = n.getFirstChild(); - FunctionType replacementFnType = fnType.visit(replacer) - .toMaybeFunctionType(); + FunctionType replacementFnType = fnType.visit(replacer).toMaybeFunctionType(); checkNotNull(replacementFnType); - callTarget.setJSType(replacementFnType); n.setJSType(replacementFnType.getReturnType()); diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index fd222822371..f50f7c11fa2 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -17735,6 +17735,40 @@ public void testNoResolvedTypeDoesntCauseInfiniteLoop() throws Exception { null); } + public void testb38182645() throws Exception { + testTypes( + LINE_JOINER.join("", + "/**", + " * @interface", + " * @template VALUE", + " */", + "function MyI() {}", + "", + "", + "/**", + " * @constructor", + " * @implements {MyI}", + " * @template K, V", + " */", + "function MyMap() {}", + "", + "", + "/**", + " * @param {!MyMap} map", + " * @return {T}", + " * @template T", + " */", + "function getValueFromNameAndMap(map) {", + " return /** @type {?} */ (123);", + "}", + "var m = /** @type {!MyMap} */ (new MyMap());", + "var /** null */ n = getValueFromNameAndMap(m);"), + LINE_JOINER.join( + "initializing variable", + "found : number", + "required: null")); + } + private void testTypes(String js) { testTypes(js, (String) null); }