From f483738272f2933c2904d2683b871af26a306d51 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 08:36:54 -0800 Subject: [PATCH 1/6] Type { [P in K]: E }[X] has constraint E with X substutited for P --- src/compiler/checker.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e56a0b2caa24..bb3f059392e15 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11754,6 +11754,11 @@ namespace ts { } function getConstraintFromIndexedAccess(type: IndexedAccessType) { + if (isMappedTypeGenericIndexedAccess(type)) { + // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, + // we substitute an instantiation of E where P is replaced with X. + return substituteIndexedMappedType(type.objectType as MappedType, type.indexType); + } const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); @@ -11967,6 +11972,11 @@ namespace ts { return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType; } if (t.flags & TypeFlags.IndexedAccess) { + if (isMappedTypeGenericIndexedAccess(t)) { + // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, + // we substitute an instantiation of E where P is replaced with X. + return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType)); + } const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); @@ -12060,13 +12070,7 @@ namespace ts { * type itself. */ function getApparentType(type: Type): Type { - // We obtain the base constraint for all instantiable types, except indexed access types of the form - // { [P in K]: E }[X], where K is non-generic and X is generic. For those types, we instead substitute an - // instantiation of E where P is replaced with X. We do this because getBaseConstraintOfType directly - // lowers to an instantiation where X's constraint is substituted for X, which isn't always desirable. - const t = !(type.flags & TypeFlags.Instantiable) ? type : - isMappedTypeGenericIndexedAccess(type) ? substituteIndexedMappedType((type as IndexedAccessType).objectType as MappedType, (type as IndexedAccessType).indexType) : - getBaseConstraintOfType(type) || unknownType; + const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType; return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : t.flags & TypeFlags.StringLike ? globalStringType : From 0a0b22704236a57497a47663e13a01770d0aa2d0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 08:45:25 -0800 Subject: [PATCH 2/6] Add regression test --- tests/baselines/reference/correlatedUnions.js | 42 +++++++++ .../reference/correlatedUnions.symbols | 86 +++++++++++++++++++ .../reference/correlatedUnions.types | 64 ++++++++++++++ tests/cases/compiler/correlatedUnions.ts | 20 +++++ 4 files changed, 212 insertions(+) diff --git a/tests/baselines/reference/correlatedUnions.js b/tests/baselines/reference/correlatedUnions.js index 7d0d71e9beea2..aa835b09c8acd 100644 --- a/tests/baselines/reference/correlatedUnions.js +++ b/tests/baselines/reference/correlatedUnions.js @@ -157,6 +157,26 @@ function ff1() { const x1 = apply('sum', 1, 2) const x2 = apply('concat', 'str1', 'str2', 'str3' ) } + +// Repro from #47368 + +type ArgMap = { a: number, b: string }; +type Func = (x: ArgMap[K]) => void; +type Funcs = { [K in keyof ArgMap]: Func }; + +function f1(funcs: Funcs, key: K, arg: ArgMap[K]) { + funcs[key](arg); +} + +function f2(funcs: Funcs, key: K, arg: ArgMap[K]) { + const func = funcs[key]; // Type Funcs[K] + func(arg); +} + +function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { + const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func + func(arg); +} //// [correlatedUnions.js] @@ -244,6 +264,17 @@ function ff1() { var x1 = apply('sum', 1, 2); var x2 = apply('concat', 'str1', 'str2', 'str3'); } +function f1(funcs, key, arg) { + funcs[key](arg); +} +function f2(funcs, key, arg) { + var func = funcs[key]; // Type Funcs[K] + func(arg); +} +function f3(funcs, key, arg) { + var func = funcs[key]; // Error, Funcs[K] not assignable to Func + func(arg); +} //// [correlatedUnions.d.ts] @@ -348,3 +379,14 @@ declare const scrollEvent: { readonly callback: (ev: Event) => void; }; declare function ff1(): void; +declare type ArgMap = { + a: number; + b: string; +}; +declare type Func = (x: ArgMap[K]) => void; +declare type Funcs = { + [K in keyof ArgMap]: Func; +}; +declare function f1(funcs: Funcs, key: K, arg: ArgMap[K]): void; +declare function f2(funcs: Funcs, key: K, arg: ArgMap[K]): void; +declare function f3(funcs: Funcs, key: K, arg: ArgMap[K]): void; diff --git a/tests/baselines/reference/correlatedUnions.symbols b/tests/baselines/reference/correlatedUnions.symbols index ecb57667c815c..cd4905e4b2d84 100644 --- a/tests/baselines/reference/correlatedUnions.symbols +++ b/tests/baselines/reference/correlatedUnions.symbols @@ -573,3 +573,89 @@ function ff1() { >apply : Symbol(apply, Decl(correlatedUnions.ts, 150, 5)) } +// Repro from #47368 + +type ArgMap = { a: number, b: string }; +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>a : Symbol(a, Decl(correlatedUnions.ts, 161, 15)) +>b : Symbol(b, Decl(correlatedUnions.ts, 161, 26)) + +type Func = (x: ArgMap[K]) => void; +>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39)) +>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>x : Symbol(x, Decl(correlatedUnions.ts, 162, 37)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10)) + +type Funcs = { [K in keyof ArgMap]: Func }; +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39)) +>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16)) + +function f1(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f1 : Symbol(f1, Decl(correlatedUnions.ts, 163, 46)) +>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36)) +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49)) +>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12)) + + funcs[key](arg); +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36)) +>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57)) +} + +function f2(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f2 : Symbol(f2, Decl(correlatedUnions.ts, 167, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36)) +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49)) +>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12)) + + const func = funcs[key]; // Type Funcs[K] +>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9)) +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36)) +>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49)) + + func(arg); +>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57)) +} + +function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f3 : Symbol(f3, Decl(correlatedUnions.ts, 172, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36)) +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49)) +>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12)) + + const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func +>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9)) +>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39)) +>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12)) +>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36)) +>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49)) + + func(arg); +>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9)) +>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57)) +} + diff --git a/tests/baselines/reference/correlatedUnions.types b/tests/baselines/reference/correlatedUnions.types index e10785790a2eb..1126b30ef1e20 100644 --- a/tests/baselines/reference/correlatedUnions.types +++ b/tests/baselines/reference/correlatedUnions.types @@ -549,3 +549,67 @@ function ff1() { >'str3' : "str3" } +// Repro from #47368 + +type ArgMap = { a: number, b: string }; +>ArgMap : ArgMap +>a : number +>b : string + +type Func = (x: ArgMap[K]) => void; +>Func : Func +>x : ArgMap[K] + +type Funcs = { [K in keyof ArgMap]: Func }; +>Funcs : Funcs + +function f1(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f1 : (funcs: Funcs, key: K, arg: ArgMap[K]) => void +>funcs : Funcs +>key : K +>arg : ArgMap[K] + + funcs[key](arg); +>funcs[key](arg) : void +>funcs[key] : Funcs[K] +>funcs : Funcs +>key : K +>arg : ArgMap[K] +} + +function f2(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f2 : (funcs: Funcs, key: K, arg: ArgMap[K]) => void +>funcs : Funcs +>key : K +>arg : ArgMap[K] + + const func = funcs[key]; // Type Funcs[K] +>func : Funcs[K] +>funcs[key] : Funcs[K] +>funcs : Funcs +>key : K + + func(arg); +>func(arg) : void +>func : Funcs[K] +>arg : ArgMap[K] +} + +function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { +>f3 : (funcs: Funcs, key: K, arg: ArgMap[K]) => void +>funcs : Funcs +>key : K +>arg : ArgMap[K] + + const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func +>func : Func +>funcs[key] : Funcs[K] +>funcs : Funcs +>key : K + + func(arg); +>func(arg) : void +>func : Func +>arg : ArgMap[K] +} + diff --git a/tests/cases/compiler/correlatedUnions.ts b/tests/cases/compiler/correlatedUnions.ts index b0b3aa63faa71..0fe3ec2246c24 100644 --- a/tests/cases/compiler/correlatedUnions.ts +++ b/tests/cases/compiler/correlatedUnions.ts @@ -159,3 +159,23 @@ function ff1() { const x1 = apply('sum', 1, 2) const x2 = apply('concat', 'str1', 'str2', 'str3' ) } + +// Repro from #47368 + +type ArgMap = { a: number, b: string }; +type Func = (x: ArgMap[K]) => void; +type Funcs = { [K in keyof ArgMap]: Func }; + +function f1(funcs: Funcs, key: K, arg: ArgMap[K]) { + funcs[key](arg); +} + +function f2(funcs: Funcs, key: K, arg: ArgMap[K]) { + const func = funcs[key]; // Type Funcs[K] + func(arg); +} + +function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { + const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func + func(arg); +} From 82239206215481ce6890dfcd8508a355b78b31ce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 11:35:23 -0800 Subject: [PATCH 3/6] Fix PragmaMap and ReadonlyPragmaMap declarations --- src/compiler/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 130a156c33b54..74a92998c70d4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8637,7 +8637,7 @@ namespace ts { /* @internal */ export interface ReadonlyPragmaMap extends ReadonlyESMap { get(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]; - forEach(action: (value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void; + forEach(action: (value: PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][], key: keyof PragmaPseudoMap) => void): void; } /** @@ -8649,7 +8649,7 @@ namespace ts { export interface PragmaMap extends ESMap, ReadonlyPragmaMap { set(key: TKey, value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]): this; get(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]; - forEach(action: (value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void; + forEach(action: (value: PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][], key: keyof PragmaPseudoMap) => void): void; } /* @internal */ From d0f33031355c64265c17002d66b3d14e4da11df4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 14:53:39 -0800 Subject: [PATCH 4/6] Explore additional constraint --- src/compiler/checker.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bb3f059392e15..bd6eb100c0967 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19229,6 +19229,17 @@ namespace ts { resetErrorInfo(saveErrorInfo); return result; } + if (isMappedTypeGenericIndexedAccess(source)) { + // For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X + // substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X. + const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType); + if (indexConstraint) { + if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } } } else if (source.flags & TypeFlags.Index) { From c67f1604f41e4651b9f140a3a75355ab037a486d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 14:54:04 -0800 Subject: [PATCH 5/6] Revert previous change --- src/compiler/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 74a92998c70d4..130a156c33b54 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8637,7 +8637,7 @@ namespace ts { /* @internal */ export interface ReadonlyPragmaMap extends ReadonlyESMap { get(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]; - forEach(action: (value: PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][], key: keyof PragmaPseudoMap) => void): void; + forEach(action: (value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void; } /** @@ -8649,7 +8649,7 @@ namespace ts { export interface PragmaMap extends ESMap, ReadonlyPragmaMap { set(key: TKey, value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]): this; get(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]; - forEach(action: (value: PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][], key: keyof PragmaPseudoMap) => void): void; + forEach(action: (value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void; } /* @internal */ From fcde8a2673848fcfb41bd92d0dbe933ea62a6a2a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Jan 2022 14:54:11 -0800 Subject: [PATCH 6/6] Add tests --- tests/baselines/reference/correlatedUnions.js | 8 ++++++++ .../baselines/reference/correlatedUnions.symbols | 16 ++++++++++++++++ tests/baselines/reference/correlatedUnions.types | 11 +++++++++++ tests/cases/compiler/correlatedUnions.ts | 4 ++++ 4 files changed, 39 insertions(+) diff --git a/tests/baselines/reference/correlatedUnions.js b/tests/baselines/reference/correlatedUnions.js index aa835b09c8acd..b42a48c68a0e2 100644 --- a/tests/baselines/reference/correlatedUnions.js +++ b/tests/baselines/reference/correlatedUnions.js @@ -177,6 +177,10 @@ function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func func(arg); } + +function f4(x: Funcs[keyof ArgMap], y: Funcs[K]) { + x = y; +} //// [correlatedUnions.js] @@ -275,6 +279,9 @@ function f3(funcs, key, arg) { var func = funcs[key]; // Error, Funcs[K] not assignable to Func func(arg); } +function f4(x, y) { + x = y; +} //// [correlatedUnions.d.ts] @@ -390,3 +397,4 @@ declare type Funcs = { declare function f1(funcs: Funcs, key: K, arg: ArgMap[K]): void; declare function f2(funcs: Funcs, key: K, arg: ArgMap[K]): void; declare function f3(funcs: Funcs, key: K, arg: ArgMap[K]): void; +declare function f4(x: Funcs[keyof ArgMap], y: Funcs[K]): void; diff --git a/tests/baselines/reference/correlatedUnions.symbols b/tests/baselines/reference/correlatedUnions.symbols index cd4905e4b2d84..040c3983e95dd 100644 --- a/tests/baselines/reference/correlatedUnions.symbols +++ b/tests/baselines/reference/correlatedUnions.symbols @@ -659,3 +659,19 @@ function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { >arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57)) } +function f4(x: Funcs[keyof ArgMap], y: Funcs[K]) { +>f4 : Symbol(f4, Decl(correlatedUnions.ts, 177, 1)) +>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36)) +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1)) +>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59)) +>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59)) +>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12)) + + x = y; +>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36)) +>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59)) +} + diff --git a/tests/baselines/reference/correlatedUnions.types b/tests/baselines/reference/correlatedUnions.types index 1126b30ef1e20..ae14133757619 100644 --- a/tests/baselines/reference/correlatedUnions.types +++ b/tests/baselines/reference/correlatedUnions.types @@ -613,3 +613,14 @@ function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { >arg : ArgMap[K] } +function f4(x: Funcs[keyof ArgMap], y: Funcs[K]) { +>f4 : (x: Funcs[keyof ArgMap], y: Funcs[K]) => void +>x : Func<"b"> | Func<"a"> +>y : Funcs[K] + + x = y; +>x = y : Funcs[K] +>x : Func<"b"> | Func<"a"> +>y : Funcs[K] +} + diff --git a/tests/cases/compiler/correlatedUnions.ts b/tests/cases/compiler/correlatedUnions.ts index 0fe3ec2246c24..546d511034bae 100644 --- a/tests/cases/compiler/correlatedUnions.ts +++ b/tests/cases/compiler/correlatedUnions.ts @@ -179,3 +179,7 @@ function f3(funcs: Funcs, key: K, arg: ArgMap[K]) { const func: Func = funcs[key]; // Error, Funcs[K] not assignable to Func func(arg); } + +function f4(x: Funcs[keyof ArgMap], y: Funcs[K]) { + x = y; +}