Skip to content

Commit 04fd365

Browse files
authored
Merge pull request #27911 from Microsoft/fixCircularMappedType
Fix circular mapped type instantiations for arrays and tuples
2 parents b852f3a + 3930525 commit 04fd365

File tree

7 files changed

+93
-12
lines changed

7 files changed

+93
-12
lines changed

src/compiler/checker.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -10369,7 +10369,15 @@ namespace ts {
1036910369
if (typeVariable) {
1037010370
const mappedTypeVariable = instantiateType(typeVariable, mapper);
1037110371
if (typeVariable !== mappedTypeVariable) {
10372-
return mapType(mappedTypeVariable, t => {
10372+
// If we are already in the process of creating an instantiation of this mapped type,
10373+
// return the error type. This situation only arises if we are instantiating the mapped
10374+
// type for an array or tuple type, as we then need to eagerly resolve the (possibly
10375+
// circular) element type(s).
10376+
if (type.instantiating) {
10377+
return errorType;
10378+
}
10379+
type.instantiating = true;
10380+
const result = mapType(mappedTypeVariable, t => {
1037310381
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) {
1037410382
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
1037510383
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
@@ -10379,6 +10387,8 @@ namespace ts {
1037910387
}
1038010388
return t;
1038110389
});
10390+
type.instantiating = false;
10391+
return result;
1038210392
}
1038310393
}
1038410394
return instantiateAnonymousType(type, mapper);

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4074,6 +4074,7 @@ namespace ts {
40744074
templateType?: Type;
40754075
modifiersType?: Type;
40764076
resolvedApparentType?: Type;
4077+
instantiating?: boolean;
40774078
}
40784079

40794080
export interface EvolvingArrayType extends ObjectType {

tests/baselines/reference/recursiveMappedTypes.errors.txt

+11-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,14 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231
3131
[K in keyof Recurse1]: Recurse1[K]
3232
~~~~~~~~~~~~~~
3333
!!! error TS2313: Type parameter 'K' has a circular constraint.
34-
}
34+
}
35+
36+
// Repro from #27881
37+
38+
export type Circular<T> = {[P in keyof T]: Circular<T>};
39+
type tup = [number, number, number, number];
40+
41+
function foo(arg: Circular<tup>): tup {
42+
return arg;
43+
}
44+

tests/baselines/reference/recursiveMappedTypes.js

+18-9
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,28 @@ type Recurse1 = {
1111

1212
type Recurse2 = {
1313
[K in keyof Recurse1]: Recurse1[K]
14-
}
14+
}
15+
16+
// Repro from #27881
17+
18+
export type Circular<T> = {[P in keyof T]: Circular<T>};
19+
type tup = [number, number, number, number];
20+
21+
function foo(arg: Circular<tup>): tup {
22+
return arg;
23+
}
24+
1525

1626
//// [recursiveMappedTypes.js]
27+
"use strict";
1728
// Recursive mapped types simply appear empty
29+
exports.__esModule = true;
30+
function foo(arg) {
31+
return arg;
32+
}
1833

1934

2035
//// [recursiveMappedTypes.d.ts]
21-
declare type Recurse = {
22-
[K in keyof Recurse]: Recurse[K];
23-
};
24-
declare type Recurse1 = {
25-
[K in keyof Recurse2]: Recurse2[K];
26-
};
27-
declare type Recurse2 = {
28-
[K in keyof Recurse1]: Recurse1[K];
36+
export declare type Circular<T> = {
37+
[P in keyof T]: Circular<T>;
2938
};

tests/baselines/reference/recursiveMappedTypes.symbols

+25
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,28 @@ type Recurse2 = {
3030
>Recurse1 : Symbol(Recurse1, Decl(recursiveMappedTypes.ts, 4, 1))
3131
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 11, 5))
3232
}
33+
34+
// Repro from #27881
35+
36+
export type Circular<T> = {[P in keyof T]: Circular<T>};
37+
>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1))
38+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21))
39+
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 16, 28))
40+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21))
41+
>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1))
42+
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21))
43+
44+
type tup = [number, number, number, number];
45+
>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56))
46+
47+
function foo(arg: Circular<tup>): tup {
48+
>foo : Symbol(foo, Decl(recursiveMappedTypes.ts, 17, 44))
49+
>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13))
50+
>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1))
51+
>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56))
52+
>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56))
53+
54+
return arg;
55+
>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13))
56+
}
57+

tests/baselines/reference/recursiveMappedTypes.types

+17
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,20 @@ type Recurse2 = {
1818

1919
[K in keyof Recurse1]: Recurse1[K]
2020
}
21+
22+
// Repro from #27881
23+
24+
export type Circular<T> = {[P in keyof T]: Circular<T>};
25+
>Circular : Circular<T>
26+
27+
type tup = [number, number, number, number];
28+
>tup : [number, number, number, number]
29+
30+
function foo(arg: Circular<tup>): tup {
31+
>foo : (arg: [any, any, any, any]) => [number, number, number, number]
32+
>arg : [any, any, any, any]
33+
34+
return arg;
35+
>arg : [any, any, any, any]
36+
}
37+

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,13 @@ type Recurse1 = {
1212

1313
type Recurse2 = {
1414
[K in keyof Recurse1]: Recurse1[K]
15-
}
15+
}
16+
17+
// Repro from #27881
18+
19+
export type Circular<T> = {[P in keyof T]: Circular<T>};
20+
type tup = [number, number, number, number];
21+
22+
function foo(arg: Circular<tup>): tup {
23+
return arg;
24+
}

0 commit comments

Comments
 (0)