Skip to content

Commit 4d6dd11

Browse files
authored
Add missing mapped type indexed access constraint (#47370)
* Type { [P in K]: E }[X] has constraint E with X substutited for P * Add regression test * Fix PragmaMap and ReadonlyPragmaMap declarations * Explore additional constraint * Revert previous change * Add tests
1 parent 852b1c2 commit 4d6dd11

File tree

5 files changed

+273
-7
lines changed

5 files changed

+273
-7
lines changed

src/compiler/checker.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -11749,6 +11749,11 @@ namespace ts {
1174911749
}
1175011750

1175111751
function getConstraintFromIndexedAccess(type: IndexedAccessType) {
11752+
if (isMappedTypeGenericIndexedAccess(type)) {
11753+
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
11754+
// we substitute an instantiation of E where P is replaced with X.
11755+
return substituteIndexedMappedType(type.objectType as MappedType, type.indexType);
11756+
}
1175211757
const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType);
1175311758
if (indexConstraint && indexConstraint !== type.indexType) {
1175411759
const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags);
@@ -11962,6 +11967,11 @@ namespace ts {
1196211967
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
1196311968
}
1196411969
if (t.flags & TypeFlags.IndexedAccess) {
11970+
if (isMappedTypeGenericIndexedAccess(t)) {
11971+
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
11972+
// we substitute an instantiation of E where P is replaced with X.
11973+
return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType));
11974+
}
1196511975
const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType);
1196611976
const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType);
1196711977
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags);
@@ -12055,13 +12065,7 @@ namespace ts {
1205512065
* type itself.
1205612066
*/
1205712067
function getApparentType(type: Type): Type {
12058-
// We obtain the base constraint for all instantiable types, except indexed access types of the form
12059-
// { [P in K]: E }[X], where K is non-generic and X is generic. For those types, we instead substitute an
12060-
// instantiation of E where P is replaced with X. We do this because getBaseConstraintOfType directly
12061-
// lowers to an instantiation where X's constraint is substituted for X, which isn't always desirable.
12062-
const t = !(type.flags & TypeFlags.Instantiable) ? type :
12063-
isMappedTypeGenericIndexedAccess(type) ? substituteIndexedMappedType((type as IndexedAccessType).objectType as MappedType, (type as IndexedAccessType).indexType) :
12064-
getBaseConstraintOfType(type) || unknownType;
12068+
const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType;
1206512069
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) :
1206612070
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) :
1206712071
t.flags & TypeFlags.StringLike ? globalStringType :
@@ -19220,6 +19224,17 @@ namespace ts {
1922019224
resetErrorInfo(saveErrorInfo);
1922119225
return result;
1922219226
}
19227+
if (isMappedTypeGenericIndexedAccess(source)) {
19228+
// For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X
19229+
// substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X.
19230+
const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType);
19231+
if (indexConstraint) {
19232+
if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) {
19233+
resetErrorInfo(saveErrorInfo);
19234+
return result;
19235+
}
19236+
}
19237+
}
1922319238
}
1922419239
}
1922519240
else if (source.flags & TypeFlags.Index) {

tests/baselines/reference/correlatedUnions.js

+50
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,30 @@ function ff1() {
157157
const x1 = apply('sum', 1, 2)
158158
const x2 = apply('concat', 'str1', 'str2', 'str3' )
159159
}
160+
161+
// Repro from #47368
162+
163+
type ArgMap = { a: number, b: string };
164+
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
165+
type Funcs = { [K in keyof ArgMap]: Func<K> };
166+
167+
function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
168+
funcs[key](arg);
169+
}
170+
171+
function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
172+
const func = funcs[key]; // Type Funcs[K]
173+
func(arg);
174+
}
175+
176+
function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
177+
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
178+
func(arg);
179+
}
180+
181+
function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
182+
x = y;
183+
}
160184

161185

162186
//// [correlatedUnions.js]
@@ -244,6 +268,20 @@ function ff1() {
244268
var x1 = apply('sum', 1, 2);
245269
var x2 = apply('concat', 'str1', 'str2', 'str3');
246270
}
271+
function f1(funcs, key, arg) {
272+
funcs[key](arg);
273+
}
274+
function f2(funcs, key, arg) {
275+
var func = funcs[key]; // Type Funcs[K]
276+
func(arg);
277+
}
278+
function f3(funcs, key, arg) {
279+
var func = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
280+
func(arg);
281+
}
282+
function f4(x, y) {
283+
x = y;
284+
}
247285

248286

249287
//// [correlatedUnions.d.ts]
@@ -348,3 +386,15 @@ declare const scrollEvent: {
348386
readonly callback: (ev: Event) => void;
349387
};
350388
declare function ff1(): void;
389+
declare type ArgMap = {
390+
a: number;
391+
b: string;
392+
};
393+
declare type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
394+
declare type Funcs = {
395+
[K in keyof ArgMap]: Func<K>;
396+
};
397+
declare function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
398+
declare function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
399+
declare function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]): void;
400+
declare function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]): void;

tests/baselines/reference/correlatedUnions.symbols

+102
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,105 @@ function ff1() {
573573
>apply : Symbol(apply, Decl(correlatedUnions.ts, 150, 5))
574574
}
575575

576+
// Repro from #47368
577+
578+
type ArgMap = { a: number, b: string };
579+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
580+
>a : Symbol(a, Decl(correlatedUnions.ts, 161, 15))
581+
>b : Symbol(b, Decl(correlatedUnions.ts, 161, 26))
582+
583+
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
584+
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
585+
>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10))
586+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
587+
>x : Symbol(x, Decl(correlatedUnions.ts, 162, 37))
588+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
589+
>K : Symbol(K, Decl(correlatedUnions.ts, 162, 10))
590+
591+
type Funcs = { [K in keyof ArgMap]: Func<K> };
592+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
593+
>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16))
594+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
595+
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
596+
>K : Symbol(K, Decl(correlatedUnions.ts, 163, 16))
597+
598+
function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
599+
>f1 : Symbol(f1, Decl(correlatedUnions.ts, 163, 46))
600+
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))
601+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
602+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36))
603+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
604+
>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49))
605+
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))
606+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57))
607+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
608+
>K : Symbol(K, Decl(correlatedUnions.ts, 165, 12))
609+
610+
funcs[key](arg);
611+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 165, 36))
612+
>key : Symbol(key, Decl(correlatedUnions.ts, 165, 49))
613+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 165, 57))
614+
}
615+
616+
function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
617+
>f2 : Symbol(f2, Decl(correlatedUnions.ts, 167, 1))
618+
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))
619+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
620+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36))
621+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
622+
>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49))
623+
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))
624+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57))
625+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
626+
>K : Symbol(K, Decl(correlatedUnions.ts, 169, 12))
627+
628+
const func = funcs[key]; // Type Funcs[K]
629+
>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9))
630+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 169, 36))
631+
>key : Symbol(key, Decl(correlatedUnions.ts, 169, 49))
632+
633+
func(arg);
634+
>func : Symbol(func, Decl(correlatedUnions.ts, 170, 9))
635+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 169, 57))
636+
}
637+
638+
function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
639+
>f3 : Symbol(f3, Decl(correlatedUnions.ts, 172, 1))
640+
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
641+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
642+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36))
643+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
644+
>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49))
645+
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
646+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57))
647+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
648+
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
649+
650+
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
651+
>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9))
652+
>Func : Symbol(Func, Decl(correlatedUnions.ts, 161, 39))
653+
>K : Symbol(K, Decl(correlatedUnions.ts, 174, 12))
654+
>funcs : Symbol(funcs, Decl(correlatedUnions.ts, 174, 36))
655+
>key : Symbol(key, Decl(correlatedUnions.ts, 174, 49))
656+
657+
func(arg);
658+
>func : Symbol(func, Decl(correlatedUnions.ts, 175, 9))
659+
>arg : Symbol(arg, Decl(correlatedUnions.ts, 174, 57))
660+
}
661+
662+
function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
663+
>f4 : Symbol(f4, Decl(correlatedUnions.ts, 177, 1))
664+
>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12))
665+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
666+
>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36))
667+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
668+
>ArgMap : Symbol(ArgMap, Decl(correlatedUnions.ts, 157, 1))
669+
>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59))
670+
>Funcs : Symbol(Funcs, Decl(correlatedUnions.ts, 162, 59))
671+
>K : Symbol(K, Decl(correlatedUnions.ts, 179, 12))
672+
673+
x = y;
674+
>x : Symbol(x, Decl(correlatedUnions.ts, 179, 36))
675+
>y : Symbol(y, Decl(correlatedUnions.ts, 179, 59))
676+
}
677+

tests/baselines/reference/correlatedUnions.types

+75
Original file line numberDiff line numberDiff line change
@@ -549,3 +549,78 @@ function ff1() {
549549
>'str3' : "str3"
550550
}
551551

552+
// Repro from #47368
553+
554+
type ArgMap = { a: number, b: string };
555+
>ArgMap : ArgMap
556+
>a : number
557+
>b : string
558+
559+
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
560+
>Func : Func<K>
561+
>x : ArgMap[K]
562+
563+
type Funcs = { [K in keyof ArgMap]: Func<K> };
564+
>Funcs : Funcs
565+
566+
function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
567+
>f1 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
568+
>funcs : Funcs
569+
>key : K
570+
>arg : ArgMap[K]
571+
572+
funcs[key](arg);
573+
>funcs[key](arg) : void
574+
>funcs[key] : Funcs[K]
575+
>funcs : Funcs
576+
>key : K
577+
>arg : ArgMap[K]
578+
}
579+
580+
function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
581+
>f2 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
582+
>funcs : Funcs
583+
>key : K
584+
>arg : ArgMap[K]
585+
586+
const func = funcs[key]; // Type Funcs[K]
587+
>func : Funcs[K]
588+
>funcs[key] : Funcs[K]
589+
>funcs : Funcs
590+
>key : K
591+
592+
func(arg);
593+
>func(arg) : void
594+
>func : Funcs[K]
595+
>arg : ArgMap[K]
596+
}
597+
598+
function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
599+
>f3 : <K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) => void
600+
>funcs : Funcs
601+
>key : K
602+
>arg : ArgMap[K]
603+
604+
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
605+
>func : Func<K>
606+
>funcs[key] : Funcs[K]
607+
>funcs : Funcs
608+
>key : K
609+
610+
func(arg);
611+
>func(arg) : void
612+
>func : Func<K>
613+
>arg : ArgMap[K]
614+
}
615+
616+
function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
617+
>f4 : <K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) => void
618+
>x : Func<"b"> | Func<"a">
619+
>y : Funcs[K]
620+
621+
x = y;
622+
>x = y : Funcs[K]
623+
>x : Func<"b"> | Func<"a">
624+
>y : Funcs[K]
625+
}
626+

tests/cases/compiler/correlatedUnions.ts

+24
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,27 @@ function ff1() {
159159
const x1 = apply('sum', 1, 2)
160160
const x2 = apply('concat', 'str1', 'str2', 'str3' )
161161
}
162+
163+
// Repro from #47368
164+
165+
type ArgMap = { a: number, b: string };
166+
type Func<K extends keyof ArgMap> = (x: ArgMap[K]) => void;
167+
type Funcs = { [K in keyof ArgMap]: Func<K> };
168+
169+
function f1<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
170+
funcs[key](arg);
171+
}
172+
173+
function f2<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
174+
const func = funcs[key]; // Type Funcs[K]
175+
func(arg);
176+
}
177+
178+
function f3<K extends keyof ArgMap>(funcs: Funcs, key: K, arg: ArgMap[K]) {
179+
const func: Func<K> = funcs[key]; // Error, Funcs[K] not assignable to Func<K>
180+
func(arg);
181+
}
182+
183+
function f4<K extends keyof ArgMap>(x: Funcs[keyof ArgMap], y: Funcs[K]) {
184+
x = y;
185+
}

0 commit comments

Comments
 (0)