mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Track recursive homomorphic mapped types by the symbol of their target (#55638)
This commit is contained in:
parent
eb2d1f93f2
commit
4f899a1691
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
63
tests/baselines/reference/deeplyNestedMappedTypes.errors.txt
Normal file
63
tests/baselines/reference/deeplyNestedMappedTypes.errors.txt
Normal 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
|
||||
|
||||
177
tests/baselines/reference/deeplyNestedMappedTypes.symbols
Normal file
177
tests/baselines/reference/deeplyNestedMappedTypes.symbols
Normal 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))
|
||||
|
||||
127
tests/baselines/reference/deeplyNestedMappedTypes.types
Normal file
127
tests/baselines/reference/deeplyNestedMappedTypes.types
Normal 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>; }; }; }; }; }
|
||||
|
||||
48
tests/cases/compiler/deeplyNestedMappedTypes.ts
Normal file
48
tests/cases/compiler/deeplyNestedMappedTypes.ts
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user