Track recursive homomorphic mapped types by the symbol of their target (#55638)

This commit is contained in:
Anders Hejlsberg 2023-09-11 14:03:00 -07:00 committed by GitHub
parent eb2d1f93f2
commit 4f899a1691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 437 additions and 4 deletions

View File

@ -23484,10 +23484,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// unique AST node.
return (type as TypeReference).node!;
}
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
// We track all object types that have an associated symbol (representing the origin of the type), but
// exclude the static side of classes from this check since it shares its symbol with the instance side.
return type.symbol;
if (type.symbol) {
// We track object types that have a symbol by that symbol (representing the origin of the type).
if (getObjectFlags(type) & ObjectFlags.Mapped) {
// When a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that
// type as the recursion identity. This is a better strategy than using the symbol of the mapped
// type, which doesn't work well for recursive mapped types.
type = getMappedTargetWithSymbol(type);
}
if (!(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
// We exclude the static side of a class since it shares its symbol with the instance side.
return type.symbol;
}
}
if (isTupleType(type)) {
return type.target;
@ -23511,6 +23519,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}
function getMappedTargetWithSymbol(type: Type) {
let target = type;
while ((getObjectFlags(target) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && isMappedTypeWithKeyofConstraintDeclaration(target as MappedType)) {
target = getModifiersTypeFromMappedType(target as MappedType);
}
return target.symbol ? target : type;
}
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
}

View File

@ -6204,6 +6204,8 @@ export const enum ObjectFlags {
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
/** @internal */
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType,
/** @internal */
InstantiatedMapped = Mapped | Instantiated,
// Object flags that uniquely identify the kind of ObjectType
/** @internal */
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,

View File

@ -0,0 +1,63 @@
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; }; }; }; }; }; }>'.
The types of 'x.y.z.a.b.c' are incompatible between these types.
Type 'number' is not assignable to type 'string'.
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; }; }; }; }; }; }>'.
The types of 'x.y.z.a.b.c' are incompatible between these types.
Type 'number' is not assignable to type 'string'.
==== deeplyNestedMappedTypes.ts (2 errors) ====
// Simplified repro from #55535
type Id<T> = { [K in keyof T]: Id<T[K]> };
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
declare const foo1: Foo1;
const foo2: Foo2 = foo1; // Error expected
~~~~
!!! error TS2322: Type 'Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
!!! error TS2322: The types of 'x.y.z.a.b.c' are incompatible between these types.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
declare const foo3: Foo3;
const foo4: Foo4 = foo3; // Error expected
~~~~
!!! error TS2322: Type 'Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>' is not assignable to type 'Id2<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>'.
!!! error TS2322: The types of 'x.y.z.a.b.c' are incompatible between these types.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
// Repro from issue linked in #55535
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
type C = RequiredDeep<A>;
type D = RequiredDeep<B>;
type Test1 = [C, D] extends [D, C] ? true : false; // false
type Test2 = C extends D ? true : false; // false
type Test3 = D extends C ? true : false; // false
// Simplified repro from #54246
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
declare const bar1: Bar1;
const bar2: Bar2 = bar1; // Error expected

View File

@ -0,0 +1,177 @@
//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] ////
=== deeplyNestedMappedTypes.ts ===
// Simplified repro from #55535
type Id<T> = { [K in keyof T]: Id<T[K]> };
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 2, 16))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 2, 8))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 2, 16))
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
>Foo1 : Symbol(Foo1, Decl(deeplyNestedMappedTypes.ts, 2, 42))
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 4, 16))
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 4, 21))
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 4, 26))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 4, 31))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 4, 36))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 4, 41))
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
>Foo2 : Symbol(Foo2, Decl(deeplyNestedMappedTypes.ts, 4, 65))
>Id : Symbol(Id, Decl(deeplyNestedMappedTypes.ts, 0, 0))
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 5, 16))
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 5, 21))
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 5, 26))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 5, 31))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 5, 36))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 5, 41))
declare const foo1: Foo1;
>foo1 : Symbol(foo1, Decl(deeplyNestedMappedTypes.ts, 7, 13))
>Foo1 : Symbol(Foo1, Decl(deeplyNestedMappedTypes.ts, 2, 42))
const foo2: Foo2 = foo1; // Error expected
>foo2 : Symbol(foo2, Decl(deeplyNestedMappedTypes.ts, 8, 5))
>Foo2 : Symbol(Foo2, Decl(deeplyNestedMappedTypes.ts, 4, 65))
>foo1 : Symbol(foo1, Decl(deeplyNestedMappedTypes.ts, 7, 13))
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 10, 17))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 10, 9))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 10, 17))
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
>Foo3 : Symbol(Foo3, Decl(deeplyNestedMappedTypes.ts, 10, 49))
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 12, 17))
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 12, 22))
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 12, 27))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 12, 32))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 12, 37))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 12, 42))
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
>Foo4 : Symbol(Foo4, Decl(deeplyNestedMappedTypes.ts, 12, 66))
>Id2 : Symbol(Id2, Decl(deeplyNestedMappedTypes.ts, 8, 24))
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 13, 17))
>y : Symbol(y, Decl(deeplyNestedMappedTypes.ts, 13, 22))
>z : Symbol(z, Decl(deeplyNestedMappedTypes.ts, 13, 27))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 13, 32))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 13, 37))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 13, 42))
declare const foo3: Foo3;
>foo3 : Symbol(foo3, Decl(deeplyNestedMappedTypes.ts, 15, 13))
>Foo3 : Symbol(Foo3, Decl(deeplyNestedMappedTypes.ts, 10, 49))
const foo4: Foo4 = foo3; // Error expected
>foo4 : Symbol(foo4, Decl(deeplyNestedMappedTypes.ts, 16, 5))
>Foo4 : Symbol(Foo4, Decl(deeplyNestedMappedTypes.ts, 12, 66))
>foo3 : Symbol(foo3, Decl(deeplyNestedMappedTypes.ts, 15, 13))
// Repro from issue linked in #55535
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 20, 26))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 20, 18))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 20, 26))
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 20, 64))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 22, 10))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 22, 16))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 22, 21))
>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 22, 30))
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 22, 44))
>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 23, 10))
>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 23, 16))
>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 23, 21))
>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 23, 26))
>e : Symbol(e, Decl(deeplyNestedMappedTypes.ts, 23, 31))
>f : Symbol(f, Decl(deeplyNestedMappedTypes.ts, 23, 36))
>g : Symbol(g, Decl(deeplyNestedMappedTypes.ts, 23, 41))
>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 23, 52))
type C = RequiredDeep<A>;
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 20, 64))
type D = RequiredDeep<B>;
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 16, 24))
>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 22, 44))
type Test1 = [C, D] extends [D, C] ? true : false; // false
>Test1 : Symbol(Test1, Decl(deeplyNestedMappedTypes.ts, 26, 25))
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
type Test2 = C extends D ? true : false; // false
>Test2 : Symbol(Test2, Decl(deeplyNestedMappedTypes.ts, 28, 50))
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
type Test3 = D extends C ? true : false; // false
>Test3 : Symbol(Test3, Decl(deeplyNestedMappedTypes.ts, 29, 40))
>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 25, 25))
>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 23, 64))
// Simplified repro from #54246
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 38, 59))
>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 38, 71))
>P : Symbol(P, Decl(deeplyNestedMappedTypes.ts, 38, 82))
>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 38, 59))
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 38, 71))
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 38, 18))
>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 38, 35))
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 38, 129))
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 40, 48))
>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 30, 40))
declare const bar1: Bar1;
>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 43, 13))
>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 38, 129))
const bar2: Bar2 = bar1; // Error expected
>bar2 : Symbol(bar2, Decl(deeplyNestedMappedTypes.ts, 44, 5))
>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 40, 48))
>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 43, 13))

View File

@ -0,0 +1,127 @@
//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] ////
=== deeplyNestedMappedTypes.ts ===
// Simplified repro from #55535
type Id<T> = { [K in keyof T]: Id<T[K]> };
>Id : Id<T>
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
>Foo1 : Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; };}; }>
>x : { y: { z: { a: { b: { c: number; }; }; };}; }
>y : { z: { a: { b: { c: number; }; };}; }
>z : { a: { b: { c: number; };}; }
>a : { b: { c: number;}; }
>b : { c: number; }
>c : number
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
>Foo2 : Id<{ x: { y: { z: { a: { b: { c: string; }; }; }; };}; }>
>x : { y: { z: { a: { b: { c: string; }; }; };}; }
>y : { z: { a: { b: { c: string; }; };}; }
>z : { a: { b: { c: string; };}; }
>a : { b: { c: string;}; }
>b : { c: string; }
>c : string
declare const foo1: Foo1;
>foo1 : Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>
const foo2: Foo2 = foo1; // Error expected
>foo2 : Id<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>
>foo1 : Id<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
>Id2 : Id2<T>
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
>Foo3 : Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; };}; }>
>x : { y: { z: { a: { b: { c: number; }; }; };}; }
>y : { z: { a: { b: { c: number; }; };}; }
>z : { a: { b: { c: number; };}; }
>a : { b: { c: number;}; }
>b : { c: number; }
>c : number
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
>Foo4 : Id2<{ x: { y: { z: { a: { b: { c: string; }; }; }; };}; }>
>x : { y: { z: { a: { b: { c: string; }; }; };}; }
>y : { z: { a: { b: { c: string; }; };}; }
>z : { a: { b: { c: string; };}; }
>a : { b: { c: string;}; }
>b : { c: string; }
>c : string
declare const foo3: Foo3;
>foo3 : Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>
const foo4: Foo4 = foo3; // Error expected
>foo4 : Id2<{ x: { y: { z: { a: { b: { c: string; }; }; }; }; }; }>
>foo3 : Id2<{ x: { y: { z: { a: { b: { c: number; }; }; }; }; }; }>
// Repro from issue linked in #55535
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
>RequiredDeep : RequiredDeep<T>
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
>A : { a?: { b: { c: 1 | { d: 2000; };}; } | undefined; }
>a : { b: { c: 1 | { d: 2000; };}; } | undefined
>b : { c: 1 | { d: 2000;}; }
>c : { d: 2000; } | 1
>d : 2000
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
>B : { a?: { b: { c: { d: { e: { f: { g: 2; }; }; }; }; x: 1000;}; } | undefined; }
>a : { b: { c: { d: { e: { f: { g: 2; }; }; }; }; x: 1000;}; } | undefined
>b : { c: { d: { e: { f: { g: 2; }; }; };}; x: 1000; }
>c : { d: { e: { f: { g: 2; }; };}; }
>d : { e: { f: { g: 2; };}; }
>e : { f: { g: 2;}; }
>f : { g: 2; }
>g : 2
>x : 1000
type C = RequiredDeep<A>;
>C : RequiredDeep<A>
type D = RequiredDeep<B>;
>D : RequiredDeep<B>
type Test1 = [C, D] extends [D, C] ? true : false; // false
>Test1 : false
>true : true
>false : false
type Test2 = C extends D ? true : false; // false
>Test2 : false
>true : true
>false : false
type Test3 = D extends C ? true : false; // false
>Test3 : false
>true : true
>false : false
// Simplified repro from #54246
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
>NestedRecord : NestedRecord<K, V>
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
>Bar1 : { x: { y: { z: { a: { b: Record<"c", number>; }; }; }; }; }
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
>Bar2 : { x: { y: { z: { a: { b: Record<"c", string>; }; }; }; }; }
declare const bar1: Bar1;
>bar1 : { x: { y: { z: { a: { b: Record<"c", number>; }; }; }; }; }
const bar2: Bar2 = bar1; // Error expected
>bar2 : { x: { y: { z: { a: { b: Record<"c", string>; }; }; }; }; }
>bar1 : { x: { y: { z: { a: { b: Record<"c", number>; }; }; }; }; }

View File

@ -0,0 +1,48 @@
// @strict: true
// @noEmit: true
// Simplified repro from #55535
type Id<T> = { [K in keyof T]: Id<T[K]> };
type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
declare const foo1: Foo1;
const foo2: Foo2 = foo1; // Error expected
type Id2<T> = { [K in keyof T]: Id2<Id2<T[K]>> };
type Foo3 = Id2<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
type Foo4 = Id2<{ x: { y: { z: { a: { b: { c: string } } } } } }>;
declare const foo3: Foo3;
const foo4: Foo4 = foo3; // Error expected
// Repro from issue linked in #55535
type RequiredDeep<T> = { [K in keyof T]-?: RequiredDeep<T[K]> };
type A = { a?: { b: { c: 1 | { d: 2000 } }}}
type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}}
type C = RequiredDeep<A>;
type D = RequiredDeep<B>;
type Test1 = [C, D] extends [D, C] ? true : false; // false
type Test2 = C extends D ? true : false; // false
type Test3 = D extends C ? true : false; // false
// Simplified repro from #54246
// Except for the final non-recursive Record<K, V>, object types produced by NestedRecord all have the same symbol
// and thus are considered deeply nested after three levels of nesting. Ideally we'd detect that recursion in this
// type always terminates, but we're unaware of a general algorithm that accomplishes this.
type NestedRecord<K extends string, V> = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord<KR, V> } : Record<K, V>;
type Bar1 = NestedRecord<"x.y.z.a.b.c", number>;
type Bar2 = NestedRecord<"x.y.z.a.b.c", string>;
declare const bar1: Bar1;
const bar2: Bar2 = bar1; // Error expected