diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7e645e99d847..fc3063e9c1dda 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)), @@ -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 symbol = type.symbol; + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + const id = symbol && ((isConstructorObject ? "+" : "") + getSymbolId(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); @@ -3909,17 +3918,14 @@ namespace ts { function typeReferenceToTypeNode(type: TypeReference) { const typeArguments: ReadonlyArray = getTypeArguments(type); if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { + const typeArgumentNode = context.visitedTypes && context.visitedTypes.has("" + getTypeId(typeArguments[0])) + ? createElidedInformationPlaceholder(context) + : visitAndTransformType(typeArguments[0], t => typeToTypeNodeHelper(t, context)); if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { - depth++; - const typeArgumentNode = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context); - depth--; - createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); + return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); } - depth++; - const elementType = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context); - depth--; - const arrayType = createArrayTypeNode(elementType); + const arrayType = createArrayTypeNode(typeArgumentNode); return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { @@ -9077,19 +9083,29 @@ namespace ts { return type; } - function createDeferredTypeReference(target: GenericType, typeArgumentNodes: ReadonlyArray, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray): TypeReference { + function createDeferredTypeReference(target: GenericType, typeArgumentNodes: ReadonlyArray, typeNodeDecoder: (node: TypeNode, i: number, len: number) => Type, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray): TypeReference { const type = createObjectType(ObjectFlags.Reference, target.symbol); type.target = target; type.typeArgumentNodes = typeArgumentNodes; + type.typeNodeDecoder = typeNodeDecoder; type.mapper = mapper; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; return type; } + function mapWithLength(array: ReadonlyArray, f: (x: T, i: number, length: number) => U): U[] { + const len = array.length; + const result: U[] = new Array(len); + for (let i = 0; i < len; i++) { + result[i] = f(array[i], i, len); + } + return result; + } + function getTypeArguments(type: TypeReference): ReadonlyArray { if (!type.resolvedTypeArguments) { - const typeArguments = type.typeArgumentNodes ? map(type.typeArgumentNodes, getTypeFromTypeNode) : emptyArray; + const typeArguments = type.typeArgumentNodes ? mapWithLength(type.typeArgumentNodes, type.typeNodeDecoder!) : emptyArray; type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; } return type.resolvedTypeArguments; @@ -9587,7 +9603,7 @@ namespace ts { const target = isReadonlyTypeOperator(node.parent) ? globalReadonlyArrayType : globalArrayType; const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - links.resolvedType = createDeferredTypeReference(target, [node.elementType], /*mapper*/ undefined, aliasSymbol, aliasTypeArguments); + links.resolvedType = createDeferredTypeReference(target, [node.elementType], getTypeFromTypeNode, /*mapper*/ undefined, aliasSymbol, aliasTypeArguments); } return links.resolvedType; } @@ -9665,17 +9681,29 @@ namespace ts { return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType; } + function createDeferredTupleType(typeNodes: readonly TypeNode[], aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, minLength = typeNodes.length, hasRestElement = false, readonly = false, associatedNames?: __String[]) { + const arity = typeNodes.length; + if (arity === 1 && hasRestElement) { + return createArrayType(getTypeFromTypeNode(typeNodes[0]), readonly); + } + const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, readonly, associatedNames); + return typeNodes.length ? createDeferredTypeReference(tupleType, typeNodes, getTypeFromTupleTypeArgumentNode, /*mapper*/ undefined, aliasSymbol, aliasTypeArguments) : tupleType; + } + + function getTypeFromTupleTypeArgumentNode(node: TypeNode, idx: number, len: number) { + const type = getTypeFromTypeNode(node); + return node.kind === SyntaxKind.RestType && idx === (len - 1) && getIndexTypeOfType(type, IndexKind.Number) || type; + } + function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const lastElement = lastOrUndefined(node.elementTypes); const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined; const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1; - const elementTypes = map(node.elementTypes, n => { - const type = getTypeFromTypeNode(n); - return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type; - }); - links.resolvedType = createTupleType(elementTypes, minLength, !!restElement, isReadonlyTypeOperator(node.parent)); + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasArgs = getTypeArgumentsForAliasSymbol(aliasSymbol); + links.resolvedType = createDeferredTupleType(node.elementTypes, aliasSymbol, aliasArgs, minLength, !!restElement, isReadonlyTypeOperator(node.parent)); } return links.resolvedType; } @@ -11653,7 +11681,7 @@ namespace ts { const typeArgumentNodes = (type).typeArgumentNodes; if (typeArgumentNodes) { const combinedMapper = combineTypeMappers((type).mapper, mapper); - return createDeferredTypeReference((type).target, typeArgumentNodes, combinedMapper, + return createDeferredTypeReference((type).target, typeArgumentNodes, (type).typeNodeDecoder!, combinedMapper, (type).aliasSymbol, instantiateTypes((type).aliasTypeArguments, combinedMapper)); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f61aecbc9c3df..2e48313f1f285 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4202,6 +4202,7 @@ namespace ts { target: GenericType; // Type reference target resolvedTypeArguments?: ReadonlyArray; // Type reference type arguments (undefined if none) typeArgumentNodes?: ReadonlyArray; + typeNodeDecoder?: (node: TypeNode, i: number, len: number) => Type; mapper?: TypeMapper; /* @internal */ literalType?: TypeReference; // Clone of type with ObjectFlags.ArrayLiteral set diff --git a/tests/cases/conformance/types/typeAliases/selfReferentialTypeAliases.ts b/tests/cases/conformance/types/typeAliases/selfReferentialTypeAliases.ts new file mode 100644 index 0000000000000..8acf29a3684a7 --- /dev/null +++ b/tests/cases/conformance/types/typeAliases/selfReferentialTypeAliases.ts @@ -0,0 +1,23 @@ +type HypertextNode = string | [string, { [key: string]: any }, ...HypertextNode[]]; + +const hypertextNode: HypertextNode = + ["div", { id: "parent" }, + ["div", { id: "first-child" }, "I'm the first child"], + ["div", { id: "second-child" }, "I'm the second child"] + ]; + +type Alternating = [T, Alternating?]; + +declare function reparam(x: Alternating): T; + +// inference for this alternating reference pattern is.... interesting. +const re1 = reparam([12]); +const re2 = reparam(["ok"]); +const re3 = reparam([12, ["ok"]]); +const re4 = reparam(["ok", [12]]); +const re5 = reparam([12, ["ok", [0]]]); +const re6 = reparam(["ok", [12, ["k"]]]); +const re7 = reparam([12, "not ok"]); // arity error +const re8 = reparam(["ok", [12, ["ok", [12, "not ok"]]]]); // deep arity error +const re9 = reparam([12, [12]]); // non-alternating +const re10 = reparam(["ok", [12, ["ok", [12, ["ok", ["not ok"]]]]]]); // deep non-alternating - we should strive to issue an error here, I think, but we infer `string | number` for T and do not diff --git a/tests/cases/user/create-react-app/create-react-app b/tests/cases/user/create-react-app/create-react-app index 24780bbc60881..437b83f0337a5 160000 --- a/tests/cases/user/create-react-app/create-react-app +++ b/tests/cases/user/create-react-app/create-react-app @@ -1 +1 @@ -Subproject commit 24780bbc608810d874575791bffeb0148e311fef +Subproject commit 437b83f0337a5d57ce7dd976d2c3b44cb2037e45 diff --git a/tests/cases/user/puppeteer/puppeteer b/tests/cases/user/puppeteer/puppeteer index 498492d4a3c63..b6b29502eb6a7 160000 --- a/tests/cases/user/puppeteer/puppeteer +++ b/tests/cases/user/puppeteer/puppeteer @@ -1 +1 @@ -Subproject commit 498492d4a3c63924e3325cf2d9f9d27d920c73a4 +Subproject commit b6b29502eb6a75fe3869806f0e7b27195fe51b0d diff --git a/tests/cases/user/webpack/webpack b/tests/cases/user/webpack/webpack index 8748eee7f8d65..743ae6da9a6fc 160000 --- a/tests/cases/user/webpack/webpack +++ b/tests/cases/user/webpack/webpack @@ -1 +1 @@ -Subproject commit 8748eee7f8d65bc5eb62ed74a9db82c5d8518b6e +Subproject commit 743ae6da9a6fc3b459a7ab3bb250fb07d14f9c5d