diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 97eace1d8e8cd..f7cc72c534d05 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10854,7 +10854,7 @@ namespace ts { function getHomomorphicTypeVariable(type: MappedType) { const constraintType = getConstraintTypeFromMappedType(type); if (constraintType.flags & TypeFlags.Index) { - const typeVariable = (constraintType).type; + const typeVariable = getActualTypeVariable((constraintType).type); if (typeVariable.flags & TypeFlags.TypeParameter) { return typeVariable; } @@ -10877,26 +10877,15 @@ namespace ts { if (typeVariable) { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { - // If we are already in the process of creating an instantiation of this mapped type, - // return the error type. This situation only arises if we are instantiating the mapped - // type for an array or tuple type, as we then need to eagerly resolve the (possibly - // circular) element type(s). - if (type.instantiating) { - return errorType; - } - type.instantiating = true; - const modifiers = getMappedTypeModifiers(type); - const result = mapType(mappedTypeVariable, t => { - if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) { + return mapType(mappedTypeVariable, t => { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) { const replacementMapper = createReplacementMapper(typeVariable, t, mapper); - return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper), getModifiedReadonlyState(isReadonlyArrayType(t), modifiers)) : + return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) : isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) : instantiateAnonymousType(type, replacementMapper); } return t; }); - type.instantiating = false; - return result; } } return instantiateAnonymousType(type, mapper); @@ -10906,6 +10895,12 @@ namespace ts { return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; } + function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { + const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); + return elementType === errorType ? errorType : + createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); + } + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { const minLength = tupleType.target.minLength; const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) => @@ -10915,7 +10910,8 @@ namespace ts { modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) : minLength; const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); - return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames); + return contains(elementTypes, errorType) ? errorType : + createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames); } function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ff5ce74d31d35..60444cc4f3edb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4114,7 +4114,6 @@ namespace ts { templateType?: Type; modifiersType?: Type; resolvedApparentType?: Type; - instantiating?: boolean; } export interface EvolvingArrayType extends ObjectType { diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index b702cf7e3169d..f2f59052a9012 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -4,9 +4,10 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(7,6): error TS2456: tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313: Type parameter 'K' has a circular constraint. tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself. tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (6 errors) ==== +==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (7 errors) ==== // Recursive mapped types simply appear empty type Recurse = { @@ -39,6 +40,47 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231 type tup = [number, number, number, number]; function foo(arg: Circular): tup { + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. return arg; } + + // Repro from #29442 + + type DeepMap = { + [K in keyof T]: T[K] extends unknown[] ? DeepMap : R; + }; + + type tpl = [string, [string, [string]]]; + type arr = string[][]; + + type t1 = DeepMap; // [number, [number, [number]]] + type t2 = DeepMap; // number[][] + + // Repro from #29577 + + type Transform = { [K in keyof T]: Transform }; + + interface User { + avatar: string; + } + + interface Guest { + displayName: string; + } + + interface Product { + users: (User | Guest)[]; + } + + declare var product: Transform; + product.users; // (Transform | Transform)[] + + // Repro from #29702 + + type Remap1 = { [P in keyof T]: Remap1; }; + type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; + + type a = Remap1; // string[] + type b = Remap2; // string[] \ No newline at end of file diff --git a/tests/baselines/reference/recursiveMappedTypes.js b/tests/baselines/reference/recursiveMappedTypes.js index 612c9d3fca4e8..e9e44a1df41cf 100644 --- a/tests/baselines/reference/recursiveMappedTypes.js +++ b/tests/baselines/reference/recursiveMappedTypes.js @@ -21,6 +21,45 @@ type tup = [number, number, number, number]; function foo(arg: Circular): tup { return arg; } + +// Repro from #29442 + +type DeepMap = { + [K in keyof T]: T[K] extends unknown[] ? DeepMap : R; +}; + +type tpl = [string, [string, [string]]]; +type arr = string[][]; + +type t1 = DeepMap; // [number, [number, [number]]] +type t2 = DeepMap; // number[][] + +// Repro from #29577 + +type Transform = { [K in keyof T]: Transform }; + +interface User { + avatar: string; +} + +interface Guest { + displayName: string; +} + +interface Product { + users: (User | Guest)[]; +} + +declare var product: Transform; +product.users; // (Transform | Transform)[] + +// Repro from #29702 + +type Remap1 = { [P in keyof T]: Remap1; }; +type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; + +type a = Remap1; // string[] +type b = Remap2; // string[] //// [recursiveMappedTypes.js] @@ -30,6 +69,7 @@ exports.__esModule = true; function foo(arg) { return arg; } +product.users; // (Transform | Transform)[] //// [recursiveMappedTypes.d.ts] diff --git a/tests/baselines/reference/recursiveMappedTypes.symbols b/tests/baselines/reference/recursiveMappedTypes.symbols index 7c0e2ffaa2341..1638dc03f56a4 100644 --- a/tests/baselines/reference/recursiveMappedTypes.symbols +++ b/tests/baselines/reference/recursiveMappedTypes.symbols @@ -55,3 +55,113 @@ function foo(arg: Circular): tup { >arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13)) } +// Repro from #29442 + +type DeepMap = { +>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13)) +>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33)) + + [K in keyof T]: T[K] extends unknown[] ? DeepMap : R; +>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13)) +>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3)) +>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13)) +>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3)) +>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33)) +>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33)) + +}; + +type tpl = [string, [string, [string]]]; +>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2)) + +type arr = string[][]; +>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40)) + +type t1 = DeepMap; // [number, [number, [number]]] +>t1 : Symbol(t1, Decl(recursiveMappedTypes.ts, 30, 22)) +>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1)) +>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2)) + +type t2 = DeepMap; // number[][] +>t2 : Symbol(t2, Decl(recursiveMappedTypes.ts, 32, 31)) +>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1)) +>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40)) + +// Repro from #29577 + +type Transform = { [K in keyof T]: Transform }; +>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15)) +>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15)) +>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15)) +>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23)) + +interface User { +>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56)) + + avatar: string; +>avatar : Symbol(User.avatar, Decl(recursiveMappedTypes.ts, 39, 16)) +} + +interface Guest { +>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1)) + + displayName: string; +>displayName : Symbol(Guest.displayName, Decl(recursiveMappedTypes.ts, 43, 17)) +} + +interface Product { +>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1)) + + users: (User | Guest)[]; +>users : Symbol(Product.users, Decl(recursiveMappedTypes.ts, 47, 19)) +>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56)) +>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1)) +} + +declare var product: Transform; +>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11)) +>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31)) +>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1)) + +product.users; // (Transform | Transform)[] +>product.users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19)) +>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11)) +>users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19)) + +// Repro from #29702 + +type Remap1 = { [P in keyof T]: Remap1; }; +>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12)) +>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20)) + +type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; +>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12)) +>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12)) + +type a = Remap1; // string[] +>a : Symbol(a, Decl(recursiveMappedTypes.ts, 57, 74)) +>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14)) + +type b = Remap2; // string[] +>b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26)) +>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51)) + diff --git a/tests/baselines/reference/recursiveMappedTypes.types b/tests/baselines/reference/recursiveMappedTypes.types index 5741f711e9d6f..126d1e1d74078 100644 --- a/tests/baselines/reference/recursiveMappedTypes.types +++ b/tests/baselines/reference/recursiveMappedTypes.types @@ -28,10 +28,72 @@ type tup = [number, number, number, number]; >tup : [number, number, number, number] function foo(arg: Circular): tup { ->foo : (arg: [any, any, any, any]) => [number, number, number, number] ->arg : [any, any, any, any] +>foo : (arg: any) => [number, number, number, number] +>arg : any return arg; ->arg : [any, any, any, any] +>arg : any } +// Repro from #29442 + +type DeepMap = { +>DeepMap : DeepMap + + [K in keyof T]: T[K] extends unknown[] ? DeepMap : R; +}; + +type tpl = [string, [string, [string]]]; +>tpl : [string, [string, [string]]] + +type arr = string[][]; +>arr : string[][] + +type t1 = DeepMap; // [number, [number, [number]]] +>t1 : [number, [number, [number]]] + +type t2 = DeepMap; // number[][] +>t2 : number[][] + +// Repro from #29577 + +type Transform = { [K in keyof T]: Transform }; +>Transform : Transform + +interface User { + avatar: string; +>avatar : string +} + +interface Guest { + displayName: string; +>displayName : string +} + +interface Product { + users: (User | Guest)[]; +>users : (User | Guest)[] +} + +declare var product: Transform; +>product : Transform + +product.users; // (Transform | Transform)[] +>product.users : (Transform | Transform)[] +>product : Transform +>users : (Transform | Transform)[] + +// Repro from #29702 + +type Remap1 = { [P in keyof T]: Remap1; }; +>Remap1 : Remap1 + +type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; +>Remap2 : Remap2 + +type a = Remap1; // string[] +>a : string[] + +type b = Remap2; // string[] +>b : string[] + diff --git a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts index 188efa2f51a54..69a0c1ca59700 100644 --- a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts +++ b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts @@ -22,3 +22,42 @@ type tup = [number, number, number, number]; function foo(arg: Circular): tup { return arg; } + +// Repro from #29442 + +type DeepMap = { + [K in keyof T]: T[K] extends unknown[] ? DeepMap : R; +}; + +type tpl = [string, [string, [string]]]; +type arr = string[][]; + +type t1 = DeepMap; // [number, [number, [number]]] +type t2 = DeepMap; // number[][] + +// Repro from #29577 + +type Transform = { [K in keyof T]: Transform }; + +interface User { + avatar: string; +} + +interface Guest { + displayName: string; +} + +interface Product { + users: (User | Guest)[]; +} + +declare var product: Transform; +product.users; // (Transform | Transform)[] + +// Repro from #29702 + +type Remap1 = { [P in keyof T]: Remap1; }; +type Remap2 = T extends object ? { [P in keyof T]: Remap2; } : T; + +type a = Remap1; // string[] +type b = Remap2; // string[]