From 55596f6c269f711f6006c12c004b36c8c2cae1e7 Mon Sep 17 00:00:00 2001 From: sdh Date: Mon, 25 Jun 2018 18:48:51 -0700 Subject: [PATCH] Resolve function templates within the function body Cleans up some ugliness in how JSTypeRegistry handles template types. Rather than storing temporary state in the registry, this state is now moved out into a temporary StaticTypedScope implementation. It does still require some special handling to look for names in this special type of scope, but the lifetime of this overlay is now much cleaner. For type resolution within the body, the types are actually added into the registry at the proper scope root, and vars are added to the scope so that JSTypeRegistry#getLookupScope is able to find them correctly. This has the chance to shadow outer names, but that shouldn't cause any problems. Fixes github.com/google/closure-compiler/issues/1924 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=202058228 --- .../javascript/jscomp/AbstractScope.java | 4 +- .../jscomp/FunctionTypeBuilder.java | 81 ++++++----- .../javascript/jscomp/TypedScopeCreator.java | 48 +++++-- .../rhino/jstype/JSTypeRegistry.java | 91 +++++++++--- .../javascript/jscomp/TypeCheckTest.java | 130 +++++++++++++++++- .../javascript/jscomp/TypeCheckTestCase.java | 1 - 6 files changed, 287 insertions(+), 68 deletions(-) diff --git a/src/com/google/javascript/jscomp/AbstractScope.java b/src/com/google/javascript/jscomp/AbstractScope.java index 903cd1a0d20..c4eb5a98996 100644 --- a/src/com/google/javascript/jscomp/AbstractScope.java +++ b/src/com/google/javascript/jscomp/AbstractScope.java @@ -389,7 +389,7 @@ private S thisScope() { } /** Performs simple validity checks on when constructing a child scope. */ - void checkChildScope(S parent) { + final void checkChildScope(S parent) { checkNotNull(parent); checkArgument(NodeUtil.createsScope(rootNode), rootNode); checkArgument( @@ -398,7 +398,7 @@ void checkChildScope(S parent) { } /** Performs simple validity checks on when constructing a root scope. */ - void checkRootScope() { + final void checkRootScope() { // TODO(tbreisacher): Can we tighten this to just NodeUtil.createsScope? checkArgument( NodeUtil.createsScope(rootNode) || rootNode.isScript() || rootNode.isRoot(), rootNode); diff --git a/src/com/google/javascript/jscomp/FunctionTypeBuilder.java b/src/com/google/javascript/jscomp/FunctionTypeBuilder.java index 11afa907252..f56c3b82276 100644 --- a/src/com/google/javascript/jscomp/FunctionTypeBuilder.java +++ b/src/com/google/javascript/jscomp/FunctionTypeBuilder.java @@ -39,6 +39,7 @@ import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; +import com.google.javascript.rhino.jstype.StaticTypedScope; import com.google.javascript.rhino.jstype.TemplateType; import java.util.ArrayList; import java.util.HashSet; @@ -71,7 +72,7 @@ final class FunctionTypeBuilder { private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final Node errorRoot; - private final TypedScope scope; + private final TypedScope enclosingScope; private FunctionContents contents = UnknownFunctionContents.get(); @@ -94,6 +95,7 @@ final class FunctionTypeBuilder { // list. private ImmutableList classTemplateTypeNames = ImmutableList.of(); private TypedScope declarationScope = null; + private StaticTypedScope templateScope; static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning( "JSC_EXTENDS_WITHOUT_TYPEDEF", @@ -226,13 +228,13 @@ public boolean apply(JSType type) { FunctionTypeBuilder(String fnName, AbstractCompiler compiler, Node errorRoot, TypedScope scope) { checkNotNull(errorRoot); - this.fnName = nullToEmpty(fnName); this.codingConvention = compiler.getCodingConvention(); this.typeRegistry = compiler.getTypeRegistry(); this.errorRoot = errorRoot; this.compiler = compiler; - this.scope = scope; + this.enclosingScope = scope; + this.templateScope = scope; } /** Format the function name for use in warnings. */ @@ -343,7 +345,7 @@ FunctionTypeBuilder inferReturnType( JSTypeExpression returnTypeExpr = fromInlineDoc ? info.getType() : info.getReturnType(); if (returnTypeExpr != null) { - returnType = returnTypeExpr.evaluate(scope, typeRegistry); + returnType = returnTypeExpr.evaluate(templateScope, typeRegistry); returnTypeInferred = false; } } @@ -394,7 +396,6 @@ FunctionTypeBuilder inferInheritance( if (nativeClassTemplateTypeNames != null && infoTemplateTypeNames.size() == nativeClassTemplateTypeNames.size()) { classTemplateTypeNames = nativeClassTemplateTypeNames; - typeRegistry.setTemplateTypeNames(classTemplateTypeNames); } else if (!infoTemplateTypeNames.isEmpty() && (isConstructor || isInterface)) { // Otherwise, create new template type for // the template values of the constructor/interface @@ -405,15 +406,25 @@ FunctionTypeBuilder inferInheritance( builder.add(typeRegistry.createTemplateType(typeParameter)); } classTemplateTypeNames = builder.build(); - typeRegistry.setTemplateTypeNames(classTemplateTypeNames); + } + + if (!classTemplateTypeNames.isEmpty()) { + // Make a new templateScope for resolving types against. + templateScope = typeRegistry.createScopeWithTemplates(templateScope, classTemplateTypeNames); + + // Register the template types on the function node. + Node functionNode = contents != null ? contents.getSourceNode() : null; + if (functionNode != null) { + typeRegistry.registerTemplateTypeNamesInScope(classTemplateTypeNames, functionNode); + } } // base type if (info != null && info.hasBaseType()) { if (isConstructor) { ObjectType infoBaseType = - info.getBaseType().evaluate(scope, typeRegistry).toMaybeObjectType(); - // TODO(sdh): ensure that JSDoc's baseType and AST's baseType are compatible if both are set + info.getBaseType().evaluate(templateScope, typeRegistry).toMaybeObjectType(); + // TODO(sdh): ensure JSDoc's baseType and AST's baseType are compatible if both are set baseType = infoBaseType; } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName()); @@ -431,7 +442,7 @@ FunctionTypeBuilder inferInheritance( implementedInterfaces = new ArrayList<>(); Set baseInterfaces = new HashSet<>(); for (JSTypeExpression t : info.getImplementedInterfaces()) { - JSType maybeInterType = t.evaluate(scope, typeRegistry); + JSType maybeInterType = t.evaluate(templateScope, typeRegistry); if (maybeInterType != null && maybeInterType.setValidator(new ImplementedTypeValidator())) { @@ -463,7 +474,7 @@ FunctionTypeBuilder inferInheritance( extendedInterfaces = new ArrayList<>(); if (info != null) { for (JSTypeExpression t : info.getExtendedInterfaces()) { - JSType maybeInterfaceType = t.evaluate(scope, typeRegistry); + JSType maybeInterfaceType = t.evaluate(templateScope, typeRegistry); if (maybeInterfaceType != null && maybeInterfaceType.setValidator(new ExtendedTypeValidator())) { extendedInterfaces.add((ObjectType) maybeInterfaceType); @@ -510,7 +521,7 @@ FunctionTypeBuilder inferThisType(JSDocInfo info) { // undefined "this" value, but all the existing "@this" annotations // don't declare restricted types. JSType maybeThisType = - info.getThisType().evaluate(scope, typeRegistry).restrictByNotNullOrUndefined(); + info.getThisType().evaluate(templateScope, typeRegistry).restrictByNotNullOrUndefined(); if (maybeThisType != null) { thisType = maybeThisType; } @@ -573,11 +584,10 @@ FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSD JSType parameterType = null; if (info != null && info.hasParameterType(argumentName)) { parameterType = - info.getParameterType(argumentName).evaluate(scope, typeRegistry); + info.getParameterType(argumentName).evaluate(templateScope, typeRegistry); } else if (arg.getJSDocInfo() != null && arg.getJSDocInfo().hasType()) { JSTypeExpression parameterTypeExpression = arg.getJSDocInfo().getType(); - parameterType = - parameterTypeExpression.evaluate(scope, typeRegistry); + parameterType = parameterTypeExpression.evaluate(templateScope, typeRegistry); isOptionalParam = parameterTypeExpression.isOptionalArg(); isVarArgs = parameterTypeExpression.isVarArgs(); } else if (oldParameterType != null && @@ -657,24 +667,17 @@ FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info, @Nullable JS // of inherited ones from an overridden function. if (info != null) { ImmutableList.Builder builder = ImmutableList.builder(); - ImmutableList infoTemplateTypeNames = - info.getTemplateTypeNames(); - ImmutableMap infoTypeTransformations = - info.getTypeTransformations(); - if (!infoTemplateTypeNames.isEmpty()) { - for (String key : infoTemplateTypeNames) { - builder.add(typeRegistry.createTemplateType(key)); - } + ImmutableList infoTemplateTypeNames = info.getTemplateTypeNames(); + ImmutableMap infoTypeTransformations = info.getTypeTransformations(); + for (String key : infoTemplateTypeNames) { + builder.add(typeRegistry.createTemplateType(key)); } - if (!infoTypeTransformations.isEmpty()) { - for (Entry entry : infoTypeTransformations.entrySet()) { - builder.add(typeRegistry.createTemplateTypeWithTransformation( - entry.getKey(), entry.getValue())); - } + for (Entry entry : infoTypeTransformations.entrySet()) { + builder.add(typeRegistry.createTemplateTypeWithTransformation( + entry.getKey(), entry.getValue())); } - if (!infoTemplateTypeNames.isEmpty() - || !infoTypeTransformations.isEmpty()) { - templateTypeNames = builder.build(); + if (!infoTemplateTypeNames.isEmpty() || !infoTypeTransformations.isEmpty()) { + this.templateTypeNames = builder.build(); } } @@ -684,6 +687,9 @@ FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info, @Nullable JS ownerType.getTemplateTypeMap().getTemplateKeys(); if (!ownerTypeKeys.isEmpty()) { ImmutableList.Builder builder = ImmutableList.builder(); + // TODO(sdh): The order of these should be switched to avoid class templates shadowing + // method templates, but this currently loosens type checking of arrays more than we'd like. + // See http://github.com/google/closure-compiler/issues/2973 builder.addAll(templateTypeNames); builder.addAll(ownerTypeKeys); keys = builder.build(); @@ -691,7 +697,14 @@ FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info, @Nullable JS } if (!keys.isEmpty()) { - typeRegistry.setTemplateTypeNames(keys); + // Add any templates from JSDoc into our template scope. + templateScope = typeRegistry.createScopeWithTemplates(templateScope, keys); + + // Register the template types on the function node. + Node functionNode = contents != null ? contents.getSourceNode() : null; + if (functionNode != null) { + typeRegistry.registerTemplateTypeNamesInScope(keys, functionNode); + } } return this; } @@ -817,8 +830,6 @@ FunctionType buildAndRegister() { fnType.setImplicitMatch(true); } - typeRegistry.clearTemplateTypeNames(); - return fnType; } @@ -965,12 +976,12 @@ private TypedScope getScopeDeclaredIn() { int dotIndex = fnName.indexOf('.'); if (dotIndex != -1) { String rootVarName = fnName.substring(0, dotIndex); - TypedVar rootVar = scope.getVar(rootVarName); + TypedVar rootVar = enclosingScope.getVar(rootVarName); if (rootVar != null) { return rootVar.getScope(); } } - return scope; + return enclosingScope; } /** diff --git a/src/com/google/javascript/jscomp/TypedScopeCreator.java b/src/com/google/javascript/jscomp/TypedScopeCreator.java index 11528dab387..bc10a15764f 100644 --- a/src/com/google/javascript/jscomp/TypedScopeCreator.java +++ b/src/com/google/javascript/jscomp/TypedScopeCreator.java @@ -75,6 +75,7 @@ import com.google.javascript.rhino.jstype.NominalTypeBuilderOti; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.Property; +import com.google.javascript.rhino.jstype.StaticTypedScope; import com.google.javascript.rhino.jstype.TemplateType; import com.google.javascript.rhino.jstype.TemplateTypeMap; import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer; @@ -540,6 +541,11 @@ private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { this.currentHoistScope = scope.getClosestHoistScope(); } + /** Returns the current compiler input. */ + CompilerInput getCompilerInput() { + return compiler.getInput(inputId); + } + /** Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, currentScope.getRootNode(), this); @@ -766,15 +772,11 @@ private JSType getDeclaredTypeInAnnotation(Node node, JSDocInfo info) { } } - if (!ownerTypeKeys.isEmpty()) { - typeRegistry.setTemplateTypeNames(ownerTypeKeys); - } - - jsType = info.getType().evaluate(currentScope, typeRegistry); - - if (!ownerTypeKeys.isEmpty()) { - typeRegistry.clearTemplateTypeNames(); - } + StaticTypedScope templateScope = + !ownerTypeKeys.isEmpty() + ? typeRegistry.createScopeWithTemplates(currentScope, ownerTypeKeys) + : currentScope; + jsType = info.getType().evaluate(templateScope, typeRegistry); } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); jsType = createFunctionTypeFromNodes(null, fnName, info, node); @@ -2301,6 +2303,34 @@ void declareArguments() { } } } + // Also add template params to the scope so that JSTypeRegistry can find them (they + // were already registered by FunctionTypeBuilder). + JSDocInfo info = NodeUtil.getBestJSDocInfo(functionNode); + if (info != null) { + Iterable templateNames = + Iterables.concat(info.getTemplateTypeNames(), info.getTypeTransformations().keySet()); + if (!Iterables.isEmpty(templateNames)) { + CompilerInput input = getCompilerInput(); + JSType voidType = typeRegistry.getNativeType(VOID_TYPE); + // Declare any template names in the function scope. This means that if someone shadows + // an outer variable FOO with a @template FOO and refers to FOO inside the method, we + // will treat it as undefined, rather than the correct type, which could lead to weird + // errors. Ideally we'd have a "don't use me" type that gives an error at use. + for (String name : templateNames) { + if (!currentScope.canDeclare(name)) { + validator.expectUndeclaredVariable( + NodeUtil.getSourceName(functionNode), + input, + functionNode, + functionNode.getParent(), + currentScope.getVar(name), + name, + voidType); + } + currentScope.declare(name, functionNode, voidType, input, /* inferred= */ false); + } + } + } } } // end declareArguments } // end FunctionScopeBuilder diff --git a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java index 6226bc614a1..d16f11e857b 100644 --- a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java +++ b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java @@ -58,10 +58,12 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Table; import com.google.javascript.rhino.ErrorReporter; +import com.google.javascript.rhino.HamtPMap; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; +import com.google.javascript.rhino.PMap; import com.google.javascript.rhino.SimpleErrorReporter; import com.google.javascript.rhino.StaticScope; import com.google.javascript.rhino.StaticSlot; @@ -210,9 +212,6 @@ public class JSTypeRegistry implements Serializable { // All the unresolved named types. private final List unresolvedNamedTypes = new ArrayList<>(); - // The template type name. - private final Map templateTypes = new HashMap<>(); - // A single empty TemplateTypeMap, which can be safely reused in cases where // there are no template types. private final TemplateTypeMap emptyTemplateTypeMap; @@ -769,6 +768,12 @@ private static void checkTypeName(String typeName) { private JSType getTypeInternal(StaticScope scope, String name) { checkTypeName(name); + if (scope instanceof SyntheticTemplateScope) { + TemplateType type = ((SyntheticTemplateScope) scope).getTemplateType(name); + if (type != null) { + return type; + } + } return getTypeForScopeInternal(getLookupScope(scope, name), name); } @@ -1262,11 +1267,6 @@ private JSType getJSTypeOrUnknown(Node n) { * @return the corresponding JSType object or {@code null} it cannot be found */ public JSType getTypeForScope(StaticScope scope, String jsTypeName) { - TemplateType templateType = templateTypes.get(jsTypeName); - if (templateType != null) { - return templateType; - } - return getTypeForScopeInternal(scope, jsTypeName); } @@ -1281,11 +1281,6 @@ public JSType getGlobalType(String jsTypeName) { * @return the corresponding JSType object or {@code null} it cannot be found */ public JSType getType(StaticScope scope, String jsTypeName) { - TemplateType templateType = templateTypes.get(jsTypeName); - if (templateType != null) { - return templateType; - } - return getTypeInternal(scope, jsTypeName); } @@ -2159,20 +2154,76 @@ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticTypedS } /** - * Sets the template type name. + * Registers template types on the given scope root. This takes a Node rather than a + * StaticScope because at the time it is called, the scope has not yet been created. */ - public void setTemplateTypeNames(List keys) { - checkNotNull(keys); + public void registerTemplateTypeNamesInScope(List keys, Node scopeRoot) { for (TemplateType key : keys) { - templateTypes.put(key.getReferenceName(), key); + scopedNameTable.put(scopeRoot, key.getReferenceName(), key); } } /** - * Clears the template type name. + * Returns a new scope that includes the given template names for type resolution + * purposes. */ - public void clearTemplateTypeNames() { - templateTypes.clear(); + public StaticTypedScope createScopeWithTemplates( + StaticTypedScope scope, Iterable templates) { + return new SyntheticTemplateScope(scope, templates); + } + + /** + * Synthetic scope that includes template names. This is necessary for resolving + * template names outside the body of templated functions (e.g. when evaluating + * JSDoc on things assigned to a prototype, or the parameter or return types of + * an annotated function), since there is not yet (and may never be) any real + * scope to attach the types to. + */ + private static class SyntheticTemplateScope implements StaticTypedScope, Serializable { + final StaticTypedScope delegate; + final PMap types; + + SyntheticTemplateScope(StaticTypedScope delegate, Iterable templates) { + this.delegate = delegate; + PMap types = + delegate instanceof SyntheticTemplateScope + ? ((SyntheticTemplateScope) delegate).types + : HamtPMap.empty(); + for (TemplateType key : templates) { + types = types.plus(key.getReferenceName(), key); + } + this.types = types; + } + + @Override + public Node getRootNode() { + return delegate.getRootNode(); + } + + @Override + public StaticTypedScope getParentScope() { + return delegate.getParentScope(); + } + + @Override + public StaticTypedSlot getSlot(String name) { + return delegate.getSlot(name); + } + + @Override + public StaticTypedSlot getOwnSlot(String name) { + return delegate.getOwnSlot(name); + } + + @Override + public JSType getTypeOfThis() { + return delegate.getTypeOfThis(); + } + + @Nullable + TemplateType getTemplateType(String name) { + return types.get(name); + } } /** diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index b652c9898bf..3eb21ae305d 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -14144,7 +14144,21 @@ public void testTemplateType26() { "initializing variable\n" + "found : Array\n" + "required: null"); } - public void testTemplateTypeForwardReferenceFunction() { + public void testTemplateTypeCollidesWithParameter() { + // Function templates are in the same scope as parameters, so cannot collide. + testTypes( + lines( + "/**", // + " * @param {T} T", + " * @template T", + " */", + "function f(T) {}"), + "variable T redefined with type undefined, " + + "original definition at [testcode]:5 with type T"); + } + + public void testTemplateTypeForwardReference() { + // TODO(martinprobst): the test below asserts incorrect behavior for backwards compatibility. testTypes( lines( "/** @param {!Foo} x */", @@ -20313,6 +20327,120 @@ public void testQuotedPropertyWithDotInCode() { }); } + public void testClassTemplateParamsInMethodBody() { + testTypes( + lines( + "/** @constructor @template T */", + "function Foo() {}", + "Foo.prototype.foo = function() {", + " var /** T */ x;", + "};")); + } + + public void testClassTtlParamsInMethodBody() { + // NOTE: TTL is not supported in class templates, so this does not work. + // This test simply documents this fact. + testTypes( + lines( + "/** @constructor @template T := 'number' =: */", + "function Foo() {}", + "Foo.prototype.foo = function() {", + " var /** T */ x;", + "};"), + "Bad type annotation. Unknown type T"); + } + + public void testClassTtlParamsInMethodSignature() { + // NOTE: TTL is not supported in class templates, so this does not work. + // This test simply documents this fact. + testTypes( + lines( + "/** @constructor @template T := 'number' =: */", + "function Foo() {}", + "/** @return {T} */", + "Foo.prototype.foo = function() {};"), + "Bad type annotation. Unknown type T"); + } + + public void testFunctionTemplateParamsInBody() { + testTypes( + lines( + "/** @template T */", + "function f(/** !Array */ arr) {", + " var /** T */ first = arr[0];", + "}")); + } + + public void testFunctionTtlParamsInBody() { + testTypes( + lines( + "/** @template T := 'number' =: */", + "function f(/** !Array */ arr) {", + " var /** T */ first = arr[0];", + "}")); + } + + public void testFunctionTemplateParamsInBodyMismatch() { + testTypes( + lines( + "/** @template T, V */", + "function f(/** !Array */ ts, /** !Array */ vs) {", + " var /** T */ t = vs[0];", + "}")); + // TODO(b/35241823): This should be an error, but we currently treat generic types + // as unknown. Once we have bounded generics we can treat templates as unique types. + // lines( + // "actual parameter 1 of f does not match formal parameter", + // "found : V", + // "required: T")); + } + + public void testClassAndMethodTemplateParamsInMethodBody() { + testTypes( + lines( + "/** @constructor @template T */", + "function Foo() {}", + "/** @template U */", + "Foo.prototype.foo = function() {", + " var /** T */ x;", + " var /** U */ y;", + "};")); + } + + public void testNestedFunctionTemplates() { + testTypes( + lines( + "/** @constructor @template A, B */", + "function Foo(/** A */ a, /** B */ b) {}", + "/** @template T */", + "function f(/** T */ t) {", + " /** @template S */", + " function g(/** S */ s) {", + " var /** !Foo */ foo = new Foo(t, s);", + " }", + "}")); + } + + public void testNestedFunctionTemplatesMismatch() { + testTypes( + lines( + "/** @constructor @template A, B */", + "function Foo(/** A */ a, /** B */ b) {}", + "/** @template T */", + "function f(/** T */ t) {", + " /** @template S */", + " function g(/** S */ s) {", + " var /** !Foo */ foo = new Foo(s, t);", + " }", + "}")); + // TODO(b/35241823): This should be an error, but we currently treat generic types + // as unknown. Once we have bounded generics we can treat templates as unique types. + // lines( + // "initializing variable", + // "found : Foo", + // "required: Foo")); + } + private void testClosureTypes(String js, String description) { testClosureTypesMultipleWarnings(js, description == null ? null : ImmutableList.of(description)); diff --git a/test/com/google/javascript/jscomp/TypeCheckTestCase.java b/test/com/google/javascript/jscomp/TypeCheckTestCase.java index 3eebfba5fbb..799882f524c 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTestCase.java +++ b/test/com/google/javascript/jscomp/TypeCheckTestCase.java @@ -262,7 +262,6 @@ protected TypeCheckResult parseAndTypeCheckWithScope(String js) { protected TypeCheckResult parseAndTypeCheckWithScope(String externs, String js) { registry.clearNamedTypes(); - registry.clearTemplateTypeNames(); compiler.init( ImmutableList.of(SourceFile.fromCode("[externs]", externs)), ImmutableList.of(SourceFile.fromCode("[testcode]", js)),