From 4254fc2a99c41ee6345df4bbe4804c16a39d3a2b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 5 Sep 2023 07:30:04 -0700 Subject: [PATCH 1/3] Track recursive homomorphic mapped types by the symbol of their target --- src/compiler/checker.ts | 24 ++++++++++++++++++++---- src/compiler/types.ts | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f4c2dd1d9094..e56ccce00c38b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 14575e7f1733d..404cb9fd555fc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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, From cd21478c25905ca72a15c5ed3a529bf6a3d5228c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 5 Sep 2023 08:24:43 -0700 Subject: [PATCH 2/3] Add tests --- .../deeplyNestedMappedTypes.errors.txt | 48 ++++++ .../reference/deeplyNestedMappedTypes.symbols | 138 ++++++++++++++++++ .../reference/deeplyNestedMappedTypes.types | 99 +++++++++++++ .../cases/compiler/deeplyNestedMappedTypes.ts | 40 +++++ 4 files changed, 325 insertions(+) create mode 100644 tests/baselines/reference/deeplyNestedMappedTypes.errors.txt create mode 100644 tests/baselines/reference/deeplyNestedMappedTypes.symbols create mode 100644 tests/baselines/reference/deeplyNestedMappedTypes.types create mode 100644 tests/cases/compiler/deeplyNestedMappedTypes.ts diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt b/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt new file mode 100644 index 0000000000000..62b50a106638c --- /dev/null +++ b/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt @@ -0,0 +1,48 @@ +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 (1 errors) ==== + // Simplified repro from #55535 + + type Id = { [K in keyof T]: Id }; + + 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'. + + // Repro from issue linked in #55535 + + type RequiredDeep = { [K in keyof T]-?: RequiredDeep }; + + type A = { a?: { b: { c: 1 | { d: 2000 } }}} + type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}} + + type C = RequiredDeep; + type D = RequiredDeep; + + 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, 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 `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord } : Record; + + 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 + \ No newline at end of file diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.symbols b/tests/baselines/reference/deeplyNestedMappedTypes.symbols new file mode 100644 index 0000000000000..cb70a68706c8b --- /dev/null +++ b/tests/baselines/reference/deeplyNestedMappedTypes.symbols @@ -0,0 +1,138 @@ +//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] //// + +=== deeplyNestedMappedTypes.ts === +// Simplified repro from #55535 + +type Id = { [K in keyof T]: Id }; +>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)) + +// Repro from issue linked in #55535 + +type RequiredDeep = { [K in keyof T]-?: RequiredDeep }; +>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) +>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) +>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 12, 26)) +>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) +>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) +>T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) +>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 12, 26)) + +type A = { a?: { b: { c: 1 | { d: 2000 } }}} +>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 12, 64)) +>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 14, 10)) +>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 14, 16)) +>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 14, 21)) +>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 14, 30)) + +type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}} +>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 14, 44)) +>a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 15, 10)) +>b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 15, 16)) +>c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 15, 21)) +>d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 15, 26)) +>e : Symbol(e, Decl(deeplyNestedMappedTypes.ts, 15, 31)) +>f : Symbol(f, Decl(deeplyNestedMappedTypes.ts, 15, 36)) +>g : Symbol(g, Decl(deeplyNestedMappedTypes.ts, 15, 41)) +>x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 15, 52)) + +type C = RequiredDeep; +>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) +>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) +>A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 12, 64)) + +type D = RequiredDeep; +>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) +>RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) +>B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 14, 44)) + +type Test1 = [C, D] extends [D, C] ? true : false; // false +>Test1 : Symbol(Test1, Decl(deeplyNestedMappedTypes.ts, 18, 25)) +>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) +>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) +>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) +>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) + +type Test2 = C extends D ? true : false; // false +>Test2 : Symbol(Test2, Decl(deeplyNestedMappedTypes.ts, 20, 50)) +>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) +>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) + +type Test3 = D extends C ? true : false; // false +>Test3 : Symbol(Test3, Decl(deeplyNestedMappedTypes.ts, 21, 40)) +>D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) +>C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) + +// Simplified repro from #54246 + +// Except for the final non-recursive Record, 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 `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord } : Record; +>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) +>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 30, 18)) +>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) +>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 30, 18)) +>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 30, 59)) +>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 30, 71)) +>P : Symbol(P, Decl(deeplyNestedMappedTypes.ts, 30, 82)) +>K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 30, 59)) +>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) +>KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 30, 71)) +>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 30, 18)) +>V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) + +type Bar1 = NestedRecord<"x.y.z.a.b.c", number>; +>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 30, 129)) +>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) + +type Bar2 = NestedRecord<"x.y.z.a.b.c", string>; +>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 32, 48)) +>NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) + +declare const bar1: Bar1; +>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 35, 13)) +>Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 30, 129)) + +const bar2: Bar2 = bar1; // Error expected +>bar2 : Symbol(bar2, Decl(deeplyNestedMappedTypes.ts, 36, 5)) +>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 32, 48)) +>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 35, 13)) + diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.types b/tests/baselines/reference/deeplyNestedMappedTypes.types new file mode 100644 index 0000000000000..00a531f54d1c3 --- /dev/null +++ b/tests/baselines/reference/deeplyNestedMappedTypes.types @@ -0,0 +1,99 @@ +//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] //// + +=== deeplyNestedMappedTypes.ts === +// Simplified repro from #55535 + +type Id = { [K in keyof T]: Id }; +>Id : Id + +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; }; }; }; }; }; }> + +// Repro from issue linked in #55535 + +type RequiredDeep = { [K in keyof T]-?: RequiredDeep }; +>RequiredDeep : RequiredDeep + +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; +>C : RequiredDeep + +type D = RequiredDeep; +>D : RequiredDeep + +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, 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 `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord } : Record; +>NestedRecord : NestedRecord + +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>; }; }; }; }; } + diff --git a/tests/cases/compiler/deeplyNestedMappedTypes.ts b/tests/cases/compiler/deeplyNestedMappedTypes.ts new file mode 100644 index 0000000000000..62bd40f8e224e --- /dev/null +++ b/tests/cases/compiler/deeplyNestedMappedTypes.ts @@ -0,0 +1,40 @@ +// @strict: true +// @noEmit: true + +// Simplified repro from #55535 + +type Id = { [K in keyof T]: Id }; + +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 + +// Repro from issue linked in #55535 + +type RequiredDeep = { [K in keyof T]-?: RequiredDeep }; + +type A = { a?: { b: { c: 1 | { d: 2000 } }}} +type B = { a?: { b: { c: { d: { e: { f: { g: 2 }}}}, x: 1000 }}} + +type C = RequiredDeep; +type D = RequiredDeep; + +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, 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 `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord } : Record; + +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 From 07455b3b4796ca9e1868104d6bc4c614f83edb91 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 9 Sep 2023 12:22:24 -0700 Subject: [PATCH 3/3] Add test --- .../deeplyNestedMappedTypes.errors.txt | 17 +- .../reference/deeplyNestedMappedTypes.symbols | 159 +++++++++++------- .../reference/deeplyNestedMappedTypes.types | 28 +++ .../cases/compiler/deeplyNestedMappedTypes.ts | 8 + 4 files changed, 151 insertions(+), 61 deletions(-) diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt b/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt index 62b50a106638c..7882bbfc99f06 100644 --- a/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt +++ b/tests/baselines/reference/deeplyNestedMappedTypes.errors.txt @@ -1,9 +1,12 @@ 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 (1 errors) ==== +==== deeplyNestedMappedTypes.ts (2 errors) ==== // Simplified repro from #55535 type Id = { [K in keyof T]: Id }; @@ -16,6 +19,18 @@ deeplyNestedMappedTypes.ts(9,7): error TS2322: Type 'Id<{ x: { y: { z: { a: { b: ~~~~ !!! 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 = { [K in keyof T]: Id2> }; + + 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 diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.symbols b/tests/baselines/reference/deeplyNestedMappedTypes.symbols index cb70a68706c8b..9a67e438cd172 100644 --- a/tests/baselines/reference/deeplyNestedMappedTypes.symbols +++ b/tests/baselines/reference/deeplyNestedMappedTypes.symbols @@ -41,61 +41,100 @@ const foo2: Foo2 = foo1; // Error expected >Foo2 : Symbol(Foo2, Decl(deeplyNestedMappedTypes.ts, 4, 65)) >foo1 : Symbol(foo1, Decl(deeplyNestedMappedTypes.ts, 7, 13)) +type Id2 = { [K in keyof T]: Id2> }; +>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 = { [K in keyof T]-?: RequiredDeep }; ->RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) ->T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) ->K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 12, 26)) ->T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) ->RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) ->T : Symbol(T, Decl(deeplyNestedMappedTypes.ts, 12, 18)) ->K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 12, 26)) +>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, 12, 64)) ->a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 14, 10)) ->b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 14, 16)) ->c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 14, 21)) ->d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 14, 30)) +>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, 14, 44)) ->a : Symbol(a, Decl(deeplyNestedMappedTypes.ts, 15, 10)) ->b : Symbol(b, Decl(deeplyNestedMappedTypes.ts, 15, 16)) ->c : Symbol(c, Decl(deeplyNestedMappedTypes.ts, 15, 21)) ->d : Symbol(d, Decl(deeplyNestedMappedTypes.ts, 15, 26)) ->e : Symbol(e, Decl(deeplyNestedMappedTypes.ts, 15, 31)) ->f : Symbol(f, Decl(deeplyNestedMappedTypes.ts, 15, 36)) ->g : Symbol(g, Decl(deeplyNestedMappedTypes.ts, 15, 41)) ->x : Symbol(x, Decl(deeplyNestedMappedTypes.ts, 15, 52)) +>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; ->C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) ->RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) ->A : Symbol(A, Decl(deeplyNestedMappedTypes.ts, 12, 64)) +>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; ->D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) ->RequiredDeep : Symbol(RequiredDeep, Decl(deeplyNestedMappedTypes.ts, 8, 24)) ->B : Symbol(B, Decl(deeplyNestedMappedTypes.ts, 14, 44)) +>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, 18, 25)) ->C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) ->D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) ->D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) ->C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) +>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, 20, 50)) ->C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) ->D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) +>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, 21, 40)) ->D : Symbol(D, Decl(deeplyNestedMappedTypes.ts, 17, 25)) ->C : Symbol(C, Decl(deeplyNestedMappedTypes.ts, 15, 64)) +>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 @@ -104,35 +143,35 @@ type Test3 = D extends C ? true : false; // false // type always terminates, but we're unaware of a general algorithm that accomplishes this. type NestedRecord = K extends `${infer K0}.${infer KR}` ? { [P in K0]: NestedRecord } : Record; ->NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) ->K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 30, 18)) ->V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) ->K : Symbol(K, Decl(deeplyNestedMappedTypes.ts, 30, 18)) ->K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 30, 59)) ->KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 30, 71)) ->P : Symbol(P, Decl(deeplyNestedMappedTypes.ts, 30, 82)) ->K0 : Symbol(K0, Decl(deeplyNestedMappedTypes.ts, 30, 59)) ->NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) ->KR : Symbol(KR, Decl(deeplyNestedMappedTypes.ts, 30, 71)) ->V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) +>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, 30, 18)) ->V : Symbol(V, Decl(deeplyNestedMappedTypes.ts, 30, 35)) +>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, 30, 129)) ->NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) +>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, 32, 48)) ->NestedRecord : Symbol(NestedRecord, Decl(deeplyNestedMappedTypes.ts, 22, 40)) +>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, 35, 13)) ->Bar1 : Symbol(Bar1, Decl(deeplyNestedMappedTypes.ts, 30, 129)) +>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, 36, 5)) ->Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 32, 48)) ->bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 35, 13)) +>bar2 : Symbol(bar2, Decl(deeplyNestedMappedTypes.ts, 44, 5)) +>Bar2 : Symbol(Bar2, Decl(deeplyNestedMappedTypes.ts, 40, 48)) +>bar1 : Symbol(bar1, Decl(deeplyNestedMappedTypes.ts, 43, 13)) diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.types b/tests/baselines/reference/deeplyNestedMappedTypes.types index 00a531f54d1c3..2743fd69bf063 100644 --- a/tests/baselines/reference/deeplyNestedMappedTypes.types +++ b/tests/baselines/reference/deeplyNestedMappedTypes.types @@ -31,6 +31,34 @@ 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 = { [K in keyof T]: Id2> }; +>Id2 : Id2 + +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 = { [K in keyof T]-?: RequiredDeep }; diff --git a/tests/cases/compiler/deeplyNestedMappedTypes.ts b/tests/cases/compiler/deeplyNestedMappedTypes.ts index 62bd40f8e224e..6c368e3026b28 100644 --- a/tests/cases/compiler/deeplyNestedMappedTypes.ts +++ b/tests/cases/compiler/deeplyNestedMappedTypes.ts @@ -11,6 +11,14 @@ type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>; declare const foo1: Foo1; const foo2: Foo2 = foo1; // Error expected +type Id2 = { [K in keyof T]: Id2> }; + +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 = { [K in keyof T]-?: RequiredDeep };