Skip to content

Commit cd37a32

Browse files
authored
Fix non-homomorphic mapped type constraint issues (#41807)
* Less aggressive wildcard check, 'keyof any' constraint for 'infer T' in mapped type constraint position * Accept new baselines * Add regression tests
1 parent 143d110 commit cd37a32

File tree

6 files changed

+128
-7
lines changed

6 files changed

+128
-7
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12081,6 +12081,11 @@ namespace ts {
1208112081
else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) {
1208212082
inferences = append(inferences, stringType);
1208312083
}
12084+
// When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any'
12085+
// constraint.
12086+
else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) {
12087+
inferences = append(inferences, keyofConstraintType);
12088+
}
1208412089
}
1208512090
}
1208612091
}
@@ -15281,8 +15286,7 @@ namespace ts {
1528115286
}
1528215287
}
1528315288
// If the constraint type of the instantiation is the wildcard type, return the wildcard type.
15284-
const result = <MappedType>instantiateAnonymousType(type, mapper);
15285-
return getConstraintTypeFromMappedType(result) === wildcardType ? wildcardType : result;
15289+
return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper);
1528615290
}
1528715291

1528815292
function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {

tests/baselines/reference/recursiveMappedTypes.errors.txt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313
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.
77
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite.
8-
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(66,25): error TS2313: Type parameter 'P' has a circular constraint.
98
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(73,5): error TS2502: '"each"' is referenced directly or indirectly in its own type annotation.
109
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(73,13): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'.
1110

1211

13-
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (10 errors) ====
12+
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (9 errors) ====
1413
// Recursive mapped types simply appear empty
1514

1615
type Recurse = {
@@ -92,9 +91,6 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(73,13): error TS261
9291

9392
type NonOptionalKeys<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
9493
type Child<T> = { [P in NonOptionalKeys<T>]: T[P] }
95-
~~~~~~~~~~~~~~~~~~
96-
!!! error TS2313: Type parameter 'P' has a circular constraint.
97-
!!! related TS2751 tests/cases/conformance/types/mapped/recursiveMappedTypes.ts:73:13: Circularity originates in type at this location.
9894

9995
export interface ListWidget {
10096
"type": "list",
@@ -112,4 +108,16 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(73,13): error TS261
112108

113109
declare let x: ListChild;
114110
x.type;
111+
112+
// Repros from #41790
113+
114+
export type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
115+
116+
export type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
117+
export type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> =
118+
ThemeType[K] extends TVal[] ? number :
119+
ThemeType[K] extends Record<infer E, TVal> ? E :
120+
ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
121+
122+
export type Foo<T> = T extends { [P in infer E]: any } ? E : never;
115123

tests/baselines/reference/recursiveMappedTypes.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ type ListChild = Child<ListWidget>
7878

7979
declare let x: ListChild;
8080
x.type;
81+
82+
// Repros from #41790
83+
84+
export type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
85+
86+
export type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
87+
export type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> =
88+
ThemeType[K] extends TVal[] ? number :
89+
ThemeType[K] extends Record<infer E, TVal> ? E :
90+
ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
91+
92+
export type Foo<T> = T extends { [P in infer E]: any } ? E : never;
8193

8294

8395
//// [recursiveMappedTypes.js]
@@ -108,4 +120,10 @@ export interface ListWidget {
108120
"collapsable"?: boolean;
109121
"each": Child<ListWidget>;
110122
}
123+
export declare type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
124+
export declare type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
125+
export declare type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> = ThemeType[K] extends TVal[] ? number : ThemeType[K] extends Record<infer E, TVal> ? E : ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
126+
export declare type Foo<T> = T extends {
127+
[P in infer E]: any;
128+
} ? E : never;
111129
export {};

tests/baselines/reference/recursiveMappedTypes.symbols

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,64 @@ declare let x: ListChild;
219219
x.type;
220220
>x : Symbol(x, Decl(recursiveMappedTypes.ts, 77, 11))
221221

222+
// Repros from #41790
223+
224+
export type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
225+
>TV : Symbol(TV, Decl(recursiveMappedTypes.ts, 78, 7))
226+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 82, 15))
227+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 82, 17))
228+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 82, 15))
229+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 82, 15))
230+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 82, 17))
231+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
232+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 82, 64))
233+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 82, 64))
234+
235+
export type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
236+
>ObjectOrArray : Symbol(ObjectOrArray, Decl(recursiveMappedTypes.ts, 82, 85))
237+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 84, 26))
238+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 84, 28))
239+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 84, 26))
240+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
241+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 84, 28))
242+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 84, 26))
243+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
244+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 84, 28))
245+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 84, 26))
246+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 84, 26))
247+
248+
export type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> =
249+
>ThemeValue : Symbol(ThemeValue, Decl(recursiveMappedTypes.ts, 84, 104))
250+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 85, 23))
251+
>ThemeType : Symbol(ThemeType, Decl(recursiveMappedTypes.ts, 85, 49))
252+
>ThemeType : Symbol(ThemeType, Decl(recursiveMappedTypes.ts, 85, 49))
253+
>TVal : Symbol(TVal, Decl(recursiveMappedTypes.ts, 85, 60))
254+
255+
ThemeType[K] extends TVal[] ? number :
256+
>ThemeType : Symbol(ThemeType, Decl(recursiveMappedTypes.ts, 85, 49))
257+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 85, 23))
258+
>TVal : Symbol(TVal, Decl(recursiveMappedTypes.ts, 85, 60))
259+
260+
ThemeType[K] extends Record<infer E, TVal> ? E :
261+
>ThemeType : Symbol(ThemeType, Decl(recursiveMappedTypes.ts, 85, 49))
262+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 85, 23))
263+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
264+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 87, 37))
265+
>TVal : Symbol(TVal, Decl(recursiveMappedTypes.ts, 85, 60))
266+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 87, 37))
267+
268+
ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
269+
>ThemeType : Symbol(ThemeType, Decl(recursiveMappedTypes.ts, 85, 49))
270+
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 85, 23))
271+
>ObjectOrArray : Symbol(ObjectOrArray, Decl(recursiveMappedTypes.ts, 82, 85))
272+
>F : Symbol(F, Decl(recursiveMappedTypes.ts, 88, 44))
273+
>F : Symbol(F, Decl(recursiveMappedTypes.ts, 88, 44))
274+
275+
export type Foo<T> = T extends { [P in infer E]: any } ? E : never;
276+
>Foo : Symbol(Foo, Decl(recursiveMappedTypes.ts, 88, 60))
277+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 90, 16))
278+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 90, 16))
279+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 90, 34))
280+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 90, 44))
281+
>E : Symbol(E, Decl(recursiveMappedTypes.ts, 90, 44))
282+

tests/baselines/reference/recursiveMappedTypes.types

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,21 @@ x.type;
133133
>x : Child<ListWidget>
134134
>type : any
135135

136+
// Repros from #41790
137+
138+
export type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
139+
>TV : TV<T, K>
140+
141+
export type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
142+
>ObjectOrArray : ObjectOrArray<T, K>
143+
144+
export type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> =
145+
>ThemeValue : ThemeValue<K, ThemeType, TVal>
146+
147+
ThemeType[K] extends TVal[] ? number :
148+
ThemeType[K] extends Record<infer E, TVal> ? E :
149+
ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
150+
151+
export type Foo<T> = T extends { [P in infer E]: any } ? E : never;
152+
>Foo : Foo<T>
153+

tests/cases/conformance/types/mapped/recursiveMappedTypes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,15 @@ type ListChild = Child<ListWidget>
7979

8080
declare let x: ListChild;
8181
x.type;
82+
83+
// Repros from #41790
84+
85+
export type TV<T, K extends keyof T> = T[K] extends Record<infer E, any> ? E : never;
86+
87+
export type ObjectOrArray<T, K extends keyof any = keyof any> = T[] | Record<K, T | Record<K, T> | T[]>;
88+
export type ThemeValue<K extends keyof ThemeType, ThemeType, TVal = any> =
89+
ThemeType[K] extends TVal[] ? number :
90+
ThemeType[K] extends Record<infer E, TVal> ? E :
91+
ThemeType[K] extends ObjectOrArray<infer F> ? F : never;
92+
93+
export type Foo<T> = T extends { [P in infer E]: any } ? E : never;

0 commit comments

Comments
 (0)