diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b752f8fa5ff..81eda207fc3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3540,7 +3540,6 @@ namespace ts { } function createNodeBuilder() { - let depth = 0; return { typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), @@ -3712,7 +3711,7 @@ namespace ts { if (objectFlags & ObjectFlags.Reference) { Debug.assert(!!(type.flags & TypeFlags.Object)); - return typeReferenceToTypeNode(type); + return (type).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type); } if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { @@ -3804,10 +3803,7 @@ namespace ts { function createAnonymousTypeNode(type: ObjectType): TypeNode { const typeId = "" + type.id; const symbol = type.symbol; - let id: string; if (symbol) { - const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - id = (isConstructorObject ? "+" : "") + getSymbolId(symbol); if (isJSConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value; @@ -3831,25 +3827,7 @@ namespace ts { } } else { - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!context.visitedTypes) { - context.visitedTypes = createMap(); - } - if (!context.symbolDepth) { - context.symbolDepth = createMap(); - } - - const depth = context.symbolDepth.get(id) || 0; - if (depth > 10) { - return createElidedInformationPlaceholder(context); - } - context.symbolDepth.set(id, depth + 1); - context.visitedTypes.set(typeId, true); - const result = createTypeNodeFromObjectType(type); - context.visitedTypes.delete(typeId); - context.symbolDepth.set(id, depth); - return result; + return visitAndTransformType(type, createTypeNodeFromObjectType); } } else { @@ -3871,6 +3849,37 @@ namespace ts { } } + function visitAndTransformType(type: Type, transform: (type: Type) => T) { + const typeId = "" + type.id; + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + const id = getObjectFlags(type) & ObjectFlags.Reference ? "N" + getNodeId((type).node) : + (isConstructorObject ? "+" : "") + getSymbolId(type.symbol); + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!context.visitedTypes) { + context.visitedTypes = createMap(); + } + if (id && !context.symbolDepth) { + context.symbolDepth = createMap(); + } + + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); + } + context.visitedTypes.set(typeId, true); + const result = transform(type); + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + return result; + } + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { if (isGenericMappedType(type)) { return createMappedTypeNodeFromType(type); @@ -3910,15 +3919,10 @@ namespace ts { const typeArguments: ReadonlyArray = getTypeArguments(type); if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { - depth++; - const typeArgumentNode = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context); - depth--; - createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); + const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); + return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); } - - depth++; - const elementType = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context); - depth--; + const elementType = typeToTypeNodeHelper(typeArguments[0], context); const arrayType = createArrayTypeNode(elementType); return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); }