Skip to content

Commit d9ee867

Browse files
authored
Merge pull request #29740 from Microsoft/fixCircularMappedArrayTuple
Fix issues related to circular mapped array and tuple types
2 parents 33af4ea + f8eb671 commit d9ee867

File tree

7 files changed

+309
-21
lines changed

7 files changed

+309
-21
lines changed

src/compiler/checker.ts

+12-16
Original file line numberDiff line numberDiff line change
@@ -10854,7 +10854,7 @@ namespace ts {
1085410854
function getHomomorphicTypeVariable(type: MappedType) {
1085510855
const constraintType = getConstraintTypeFromMappedType(type);
1085610856
if (constraintType.flags & TypeFlags.Index) {
10857-
const typeVariable = (<IndexType>constraintType).type;
10857+
const typeVariable = getActualTypeVariable((<IndexType>constraintType).type);
1085810858
if (typeVariable.flags & TypeFlags.TypeParameter) {
1085910859
return <TypeParameter>typeVariable;
1086010860
}
@@ -10877,26 +10877,15 @@ namespace ts {
1087710877
if (typeVariable) {
1087810878
const mappedTypeVariable = instantiateType(typeVariable, mapper);
1087910879
if (typeVariable !== mappedTypeVariable) {
10880-
// If we are already in the process of creating an instantiation of this mapped type,
10881-
// return the error type. This situation only arises if we are instantiating the mapped
10882-
// type for an array or tuple type, as we then need to eagerly resolve the (possibly
10883-
// circular) element type(s).
10884-
if (type.instantiating) {
10885-
return errorType;
10886-
}
10887-
type.instantiating = true;
10888-
const modifiers = getMappedTypeModifiers(type);
10889-
const result = mapType(mappedTypeVariable, t => {
10890-
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) {
10880+
return mapType(mappedTypeVariable, t => {
10881+
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) {
1089110882
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
10892-
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper), getModifiedReadonlyState(isReadonlyArrayType(t), modifiers)) :
10883+
return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) :
1089310884
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
1089410885
instantiateAnonymousType(type, replacementMapper);
1089510886
}
1089610887
return t;
1089710888
});
10898-
type.instantiating = false;
10899-
return result;
1090010889
}
1090110890
}
1090210891
return instantiateAnonymousType(type, mapper);
@@ -10906,6 +10895,12 @@ namespace ts {
1090610895
return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
1090710896
}
1090810897

10898+
function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) {
10899+
const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper);
10900+
return elementType === errorType ? errorType :
10901+
createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType)));
10902+
}
10903+
1090910904
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
1091010905
const minLength = tupleType.target.minLength;
1091110906
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
@@ -10915,7 +10910,8 @@ namespace ts {
1091510910
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
1091610911
minLength;
1091710912
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers);
10918-
return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames);
10913+
return contains(elementTypes, errorType) ? errorType :
10914+
createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames);
1091910915
}
1092010916

1092110917
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {

src/compiler/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -4114,7 +4114,6 @@ namespace ts {
41144114
templateType?: Type;
41154115
modifiersType?: Type;
41164116
resolvedApparentType?: Type;
4117-
instantiating?: boolean;
41184117
}
41194118

41204119
export interface EvolvingArrayType extends ObjectType {

tests/baselines/reference/recursiveMappedTypes.errors.txt

+43-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(7,6): error TS2456:
44
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313: Type parameter 'K' has a circular constraint.
55
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself.
66
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint.
7+
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite.
78

89

9-
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (6 errors) ====
10+
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (7 errors) ====
1011
// Recursive mapped types simply appear empty
1112

1213
type Recurse = {
@@ -39,6 +40,47 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231
3940
type tup = [number, number, number, number];
4041

4142
function foo(arg: Circular<tup>): tup {
43+
~~~~~~~~~~~~~
44+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
4245
return arg;
4346
}
47+
48+
// Repro from #29442
49+
50+
type DeepMap<T extends unknown[], R> = {
51+
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
52+
};
53+
54+
type tpl = [string, [string, [string]]];
55+
type arr = string[][];
56+
57+
type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
58+
type t2 = DeepMap<arr, number>; // number[][]
59+
60+
// Repro from #29577
61+
62+
type Transform<T> = { [K in keyof T]: Transform<T[K]> };
63+
64+
interface User {
65+
avatar: string;
66+
}
67+
68+
interface Guest {
69+
displayName: string;
70+
}
71+
72+
interface Product {
73+
users: (User | Guest)[];
74+
}
75+
76+
declare var product: Transform<Product>;
77+
product.users; // (Transform<User> | Transform<Guest>)[]
78+
79+
// Repro from #29702
80+
81+
type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
82+
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
83+
84+
type a = Remap1<string[]>; // string[]
85+
type b = Remap2<string[]>; // string[]
4486

tests/baselines/reference/recursiveMappedTypes.js

+40
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ type tup = [number, number, number, number];
2121
function foo(arg: Circular<tup>): tup {
2222
return arg;
2323
}
24+
25+
// Repro from #29442
26+
27+
type DeepMap<T extends unknown[], R> = {
28+
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
29+
};
30+
31+
type tpl = [string, [string, [string]]];
32+
type arr = string[][];
33+
34+
type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
35+
type t2 = DeepMap<arr, number>; // number[][]
36+
37+
// Repro from #29577
38+
39+
type Transform<T> = { [K in keyof T]: Transform<T[K]> };
40+
41+
interface User {
42+
avatar: string;
43+
}
44+
45+
interface Guest {
46+
displayName: string;
47+
}
48+
49+
interface Product {
50+
users: (User | Guest)[];
51+
}
52+
53+
declare var product: Transform<Product>;
54+
product.users; // (Transform<User> | Transform<Guest>)[]
55+
56+
// Repro from #29702
57+
58+
type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
59+
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
60+
61+
type a = Remap1<string[]>; // string[]
62+
type b = Remap2<string[]>; // string[]
2463

2564

2665
//// [recursiveMappedTypes.js]
@@ -30,6 +69,7 @@ exports.__esModule = true;
3069
function foo(arg) {
3170
return arg;
3271
}
72+
product.users; // (Transform<User> | Transform<Guest>)[]
3373

3474

3575
//// [recursiveMappedTypes.d.ts]

tests/baselines/reference/recursiveMappedTypes.symbols

+110
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,113 @@ function foo(arg: Circular<tup>): tup {
5555
>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13))
5656
}
5757

58+
// Repro from #29442
59+
60+
type DeepMap<T extends unknown[], R> = {
61+
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
62+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
63+
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))
64+
65+
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
66+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
67+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
68+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
69+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
70+
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
71+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
72+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
73+
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))
74+
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))
75+
76+
};
77+
78+
type tpl = [string, [string, [string]]];
79+
>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2))
80+
81+
type arr = string[][];
82+
>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40))
83+
84+
type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
85+
>t1 : Symbol(t1, Decl(recursiveMappedTypes.ts, 30, 22))
86+
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
87+
>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2))
88+
89+
type t2 = DeepMap<arr, number>; // number[][]
90+
>t2 : Symbol(t2, Decl(recursiveMappedTypes.ts, 32, 31))
91+
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
92+
>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40))
93+
94+
// Repro from #29577
95+
96+
type Transform<T> = { [K in keyof T]: Transform<T[K]> };
97+
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
98+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
99+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23))
100+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
101+
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
102+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
103+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23))
104+
105+
interface User {
106+
>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56))
107+
108+
avatar: string;
109+
>avatar : Symbol(User.avatar, Decl(recursiveMappedTypes.ts, 39, 16))
110+
}
111+
112+
interface Guest {
113+
>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1))
114+
115+
displayName: string;
116+
>displayName : Symbol(Guest.displayName, Decl(recursiveMappedTypes.ts, 43, 17))
117+
}
118+
119+
interface Product {
120+
>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1))
121+
122+
users: (User | Guest)[];
123+
>users : Symbol(Product.users, Decl(recursiveMappedTypes.ts, 47, 19))
124+
>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56))
125+
>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1))
126+
}
127+
128+
declare var product: Transform<Product>;
129+
>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11))
130+
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
131+
>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1))
132+
133+
product.users; // (Transform<User> | Transform<Guest>)[]
134+
>product.users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19))
135+
>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11))
136+
>users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19))
137+
138+
// Repro from #29702
139+
140+
type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
141+
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))
142+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
143+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20))
144+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
145+
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))
146+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
147+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20))
148+
149+
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
150+
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
151+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
152+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
153+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39))
154+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
155+
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
156+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
157+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39))
158+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
159+
160+
type a = Remap1<string[]>; // string[]
161+
>a : Symbol(a, Decl(recursiveMappedTypes.ts, 57, 74))
162+
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))
163+
164+
type b = Remap2<string[]>; // string[]
165+
>b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26))
166+
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
167+

tests/baselines/reference/recursiveMappedTypes.types

+65-3
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,72 @@ type tup = [number, number, number, number];
2828
>tup : [number, number, number, number]
2929

3030
function foo(arg: Circular<tup>): tup {
31-
>foo : (arg: [any, any, any, any]) => [number, number, number, number]
32-
>arg : [any, any, any, any]
31+
>foo : (arg: any) => [number, number, number, number]
32+
>arg : any
3333

3434
return arg;
35-
>arg : [any, any, any, any]
35+
>arg : any
3636
}
3737

38+
// Repro from #29442
39+
40+
type DeepMap<T extends unknown[], R> = {
41+
>DeepMap : DeepMap<T, R>
42+
43+
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
44+
};
45+
46+
type tpl = [string, [string, [string]]];
47+
>tpl : [string, [string, [string]]]
48+
49+
type arr = string[][];
50+
>arr : string[][]
51+
52+
type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
53+
>t1 : [number, [number, [number]]]
54+
55+
type t2 = DeepMap<arr, number>; // number[][]
56+
>t2 : number[][]
57+
58+
// Repro from #29577
59+
60+
type Transform<T> = { [K in keyof T]: Transform<T[K]> };
61+
>Transform : Transform<T>
62+
63+
interface User {
64+
avatar: string;
65+
>avatar : string
66+
}
67+
68+
interface Guest {
69+
displayName: string;
70+
>displayName : string
71+
}
72+
73+
interface Product {
74+
users: (User | Guest)[];
75+
>users : (User | Guest)[]
76+
}
77+
78+
declare var product: Transform<Product>;
79+
>product : Transform<Product>
80+
81+
product.users; // (Transform<User> | Transform<Guest>)[]
82+
>product.users : (Transform<User> | Transform<Guest>)[]
83+
>product : Transform<Product>
84+
>users : (Transform<User> | Transform<Guest>)[]
85+
86+
// Repro from #29702
87+
88+
type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
89+
>Remap1 : Remap1<T>
90+
91+
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
92+
>Remap2 : Remap2<T>
93+
94+
type a = Remap1<string[]>; // string[]
95+
>a : string[]
96+
97+
type b = Remap2<string[]>; // string[]
98+
>b : string[]
99+

0 commit comments

Comments
 (0)