Skip to content

Commit 4f899a1

Browse files
authored
Track recursive homomorphic mapped types by the symbol of their target (#55638)
1 parent eb2d1f9 commit 4f899a1

File tree

6 files changed

+437
-4
lines changed

6 files changed

+437
-4
lines changed

src/compiler/checker.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -23484,10 +23484,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2348423484
// unique AST node.
2348523485
return (type as TypeReference).node!;
2348623486
}
23487-
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23488-
// We track all object types that have an associated symbol (representing the origin of the type), but
23489-
// exclude the static side of classes from this check since it shares its symbol with the instance side.
23490-
return type.symbol;
23487+
if (type.symbol) {
23488+
// We track object types that have a symbol by that symbol (representing the origin of the type).
23489+
if (getObjectFlags(type) & ObjectFlags.Mapped) {
23490+
// When a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that
23491+
// type as the recursion identity. This is a better strategy than using the symbol of the mapped
23492+
// type, which doesn't work well for recursive mapped types.
23493+
type = getMappedTargetWithSymbol(type);
23494+
}
23495+
if (!(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23496+
// We exclude the static side of a class since it shares its symbol with the instance side.
23497+
return type.symbol;
23498+
}
2349123499
}
2349223500
if (isTupleType(type)) {
2349323501
return type.target;
@@ -23511,6 +23519,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2351123519
return type;
2351223520
}
2351323521

23522+
function getMappedTargetWithSymbol(type: Type) {
23523+
let target = type;
23524+
while ((getObjectFlags(target) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && isMappedTypeWithKeyofConstraintDeclaration(target as MappedType)) {
23525+
target = getModifiersTypeFromMappedType(target as MappedType);
23526+
}
23527+
return target.symbol ? target : type;
23528+
}
23529+
2351423530
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
2351523531
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
2351623532
}

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6204,6 +6204,8 @@ export const enum ObjectFlags {
62046204
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
62056205
/** @internal */
62066206
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType,
6207+
/** @internal */
6208+
InstantiatedMapped = Mapped | Instantiated,
62076209
// Object flags that uniquely identify the kind of ObjectType
62086210
/** @internal */
62096211
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
deeplyNestedMappedTypes.ts(9,7): error TS2322: Type 'Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
2+
The types of 'x.y.z.a.b.c' are incompatible between these types.
3+
Type 'number' is not assignable to type 'string'.
4+
deeplyNestedMappedTypes.ts(17,7): error TS2322: Type 'Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id2<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
5+
The types of 'x.y.z.a.b.c' are incompatible between these types.
6+
Type 'number' is not assignable to type 'string'.
7+
8+
9+
==== deeplyNestedMappedTypes.ts (2 errors) ====
10+
// Simplified repro from #55535
11+
12+
type Id<T> = { [K in keyof T]: Id<T[K]> };
13+
14+
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
15+
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
16+
17+
declare const foo1: Foo1;
18+
const foo2: Foo2 = foo1; // Error expected
19+
~~~~
20+
!!! error TS2322: Type 'Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
21+
!!! error TS2322: The types of 'x.y.z.a.b.c' are incompatible between these types.
22+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
23+
24+
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
25+
26+
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
27+
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
28+
29+
declare const foo3: Foo3;
30+
const foo4: Foo4 = foo3; // Error expected
31+
~~~~
32+
!!! error TS2322: Type 'Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id2<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
33+
!!! error TS2322: The types of 'x.y.z.a.b.c' are incompatible between these types.
34+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
35+
36+
// Repro from issue linked in #55535
37+
38+
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
39+
40+
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
41+
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
42+
43+
type C = RequiredDeep<A>;
44+
type D = RequiredDeep<B>;
45+
46+
type Test1 = [C, D] extends [D, C] ? true : false; // false
47+
type Test2 = C extends D ? true : false; // false
48+
type Test3 = D extends C ? true : false; // false
49+
50+
// Simplified repro from #54246
51+
52+
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
53+
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
54+
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
55+
56+
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
57+
58+
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
59+
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
60+
61+
declare const bar1: Bar1;
62+
const bar2: Bar2 = bar1; // Error expected
63+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] ////
2+
3+
=== deeplyNestedMappedTypes.ts ===
4+
// Simplified repro from #55535
5+
6+
type Id<T> = { [K in keyof T]: Id<T[K]> };
7+
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
8+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
9+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 2, 16))
10+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
11+
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
12+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
13+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 2, 16))
14+
15+
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
16+
>Foo1 : Symbol(Foo1, Decl(deeplyNestedMappedTypes.ts, 2, 42))
17+
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
18+
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 4, 16))
19+
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 4, 21))
20+
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 4, 26))
21+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 4, 31))
22+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 4, 36))
23+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 4, 41))
24+
25+
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
26+
>Foo2 : Symbol(Foo2, Decl(deeplyNestedMappedTypes.ts, 4, 65))
27+
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
28+
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 5, 16))
29+
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 5, 21))
30+
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 5, 26))
31+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 5, 31))
32+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 5, 36))
33+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 5, 41))
34+
35+
declare const foo1: Foo1;
36+
>foo1 : Symbol(foo1, Decl(deeplyNestedMappedTypes.ts, 7, 13))
37+
>Foo1 : Symbol(Foo1, Decl(deeplyNestedMappedTypes.ts, 2, 42))
38+
39+
const foo2: Foo2 = foo1; // Error expected
40+
>foo2 : Symbol(foo2, Decl(deeplyNestedMappedTypes.ts, 8, 5))
41+
>Foo2 : Symbol(Foo2, Decl(deeplyNestedMappedTypes.ts, 4, 65))
42+
>foo1 : Symbol(foo1, Decl(deeplyNestedMappedTypes.ts, 7, 13))
43+
44+
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
45+
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
46+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
47+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 10, 17))
48+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
49+
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
50+
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
51+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
52+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 10, 17))
53+
54+
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
55+
>Foo3 : Symbol(Foo3, Decl(deeplyNestedMappedTypes.ts, 10, 49))
56+
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
57+
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 12, 17))
58+
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 12, 22))
59+
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 12, 27))
60+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 12, 32))
61+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 12, 37))
62+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 12, 42))
63+
64+
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
65+
>Foo4 : Symbol(Foo4, Decl(deeplyNestedMappedTypes.ts, 12, 66))
66+
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
67+
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 13, 17))
68+
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 13, 22))
69+
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 13, 27))
70+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 13, 32))
71+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 13, 37))
72+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 13, 42))
73+
74+
declare const foo3: Foo3;
75+
>foo3 : Symbol(foo3, Decl(deeplyNestedMappedTypes.ts, 15, 13))
76+
>Foo3 : Symbol(Foo3, Decl(deeplyNestedMappedTypes.ts, 10, 49))
77+
78+
const foo4: Foo4 = foo3; // Error expected
79+
>foo4 : Symbol(foo4, Decl(deeplyNestedMappedTypes.ts, 16, 5))
80+
>Foo4 : Symbol(Foo4, Decl(deeplyNestedMappedTypes.ts, 12, 66))
81+
>foo3 : Symbol(foo3, Decl(deeplyNestedMappedTypes.ts, 15, 13))
82+
83+
// Repro from issue linked in #55535
84+
85+
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
86+
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
87+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
88+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 20, 26))
89+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
90+
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
91+
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
92+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 20, 26))
93+
94+
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
95+
>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 20, 64))
96+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 22, 10))
97+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 22, 16))
98+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 22, 21))
99+
>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 22, 30))
100+
101+
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
102+
>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 22, 44))
103+
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 23, 10))
104+
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 23, 16))
105+
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 23, 21))
106+
>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 23, 26))
107+
>e : Symbol(e, Decl(deeplyNestedMappedTypes.ts, 23, 31))
108+
>f : Symbol(f, Decl(deeplyNestedMappedTypes.ts, 23, 36))
109+
>g : Symbol(g, Decl(deeplyNestedMappedTypes.ts, 23, 41))
110+
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 23, 52))
111+
112+
type C = RequiredDeep<A>;
113+
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
114+
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
115+
>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 20, 64))
116+
117+
type D = RequiredDeep<B>;
118+
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
119+
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
120+
>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 22, 44))
121+
122+
type Test1 = [C, D] extends [D, C] ? true : false; // false
123+
>Test1 : Symbol(Test1, Decl(deeplyNestedMappedTypes.ts, 26, 25))
124+
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
125+
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
126+
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
127+
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
128+
129+
type Test2 = C extends D ? true : false; // false
130+
>Test2 : Symbol(Test2, Decl(deeplyNestedMappedTypes.ts, 28, 50))
131+
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
132+
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
133+
134+
type Test3 = D extends C ? true : false; // false
135+
>Test3 : Symbol(Test3, Decl(deeplyNestedMappedTypes.ts, 29, 40))
136+
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
137+
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
138+
139+
// Simplified repro from #54246
140+
141+
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
142+
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
143+
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
144+
145+
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
146+
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
147+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
148+
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
149+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
150+
>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 38, 59))
151+
>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 38, 71))
152+
>P : Symbol(P, Decl(deeplyNestedMappedTypes.ts, 38, 82))
153+
>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 38, 59))
154+
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
155+
>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 38, 71))
156+
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
157+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
158+
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
159+
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
160+
161+
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
162+
>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 38, 129))
163+
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
164+
165+
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
166+
>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 40, 48))
167+
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
168+
169+
declare const bar1: Bar1;
170+
>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 43, 13))
171+
>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 38, 129))
172+
173+
const bar2: Bar2 = bar1; // Error expected
174+
>bar2 : Symbol(bar2, Decl(deeplyNestedMappedTypes.ts, 44, 5))
175+
>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 40, 48))
176+
>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 43, 13))
177+

0 commit comments

Comments
 (0)