Skip to content

Commit fecbdb6

Browse files
authored
Merge pull request #28965 from Microsoft/simplifyIndexedAccess
Simplify indexed access types applied to mapped types
2 parents 5ab24ed + c3a9394 commit fecbdb6

10 files changed

+120
-36
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9686,26 +9686,15 @@ namespace ts {
96869686

96879687
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
96889688
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
9689-
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
9690-
// and we might never terminate.
9689+
// construct the type Box<T[X]>.
96919690
if (isGenericMappedType(objectType)) {
9692-
return type.simplified = substituteIndexedMappedType(objectType, type);
9693-
}
9694-
if (objectType.flags & TypeFlags.TypeParameter) {
9695-
const constraint = getConstraintOfTypeParameter(objectType as TypeParameter);
9696-
if (constraint && isGenericMappedType(constraint)) {
9697-
return type.simplified = substituteIndexedMappedType(constraint, type);
9698-
}
9691+
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]);
9692+
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
9693+
return type.simplified = mapType(instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper), getSimplifiedType);
96999694
}
97009695
return type.simplified = type;
97019696
}
97029697

9703-
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
9704-
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]);
9705-
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
9706-
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
9707-
}
9708-
97099698
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName, missingType = accessNode ? errorType : unknownType): Type {
97109699
if (objectType === wildcardType || indexType === wildcardType) {
97119700
return wildcardType;

tests/baselines/reference/keyofAndIndexedAccess.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,10 @@ function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
484484
function updateIds<T extends Record<K, string>, K extends string>(
485485
obj: T,
486486
idFields: K[],
487-
idMapping: { [oldId: string]: string }
487+
idMapping: Partial<Record<T[K], T[K]>>
488488
): Record<K, string> {
489489
for (const idField of idFields) {
490-
const newId = idMapping[obj[idField]];
490+
const newId: T[K] | undefined = idMapping[obj[idField]];
491491
if (newId) {
492492
obj[idField] = newId;
493493
}
@@ -1312,9 +1312,7 @@ declare type Handler<T> = {
13121312
declare function onChangeGenericFunction<T>(handler: Handler<T & {
13131313
preset: number;
13141314
}>): void;
1315-
declare function updateIds<T extends Record<K, string>, K extends string>(obj: T, idFields: K[], idMapping: {
1316-
[oldId: string]: string;
1317-
}): Record<K, string>;
1315+
declare function updateIds<T extends Record<K, string>, K extends string>(obj: T, idFields: K[], idMapping: Partial<Record<T[K], T[K]>>): Record<K, string>;
13181316
declare function updateIds2<T extends {
13191317
[x: string]: string;
13201318
}, K extends keyof T>(obj: T, key: K, stringMap: {

tests/baselines/reference/keyofAndIndexedAccess.symbols

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,9 +1767,14 @@ function updateIds<T extends Record<K, string>, K extends string>(
17671767
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 483, 11))
17681768
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 482, 47))
17691769

1770-
idMapping: { [oldId: string]: string }
1770+
idMapping: Partial<Record<T[K], T[K]>>
17711771
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 484, 18))
1772-
>oldId : Symbol(oldId, Decl(keyofAndIndexedAccess.ts, 485, 18))
1772+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
1773+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
1774+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 482, 19))
1775+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 482, 47))
1776+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 482, 19))
1777+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 482, 47))
17731778

17741779
): Record<K, string> {
17751780
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
@@ -1779,8 +1784,10 @@ function updateIds<T extends Record<K, string>, K extends string>(
17791784
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 487, 14))
17801785
>idFields : Symbol(idFields, Decl(keyofAndIndexedAccess.ts, 483, 11))
17811786

1782-
const newId = idMapping[obj[idField]];
1787+
const newId: T[K] | undefined = idMapping[obj[idField]];
17831788
>newId : Symbol(newId, Decl(keyofAndIndexedAccess.ts, 488, 13))
1789+
>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 482, 19))
1790+
>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 482, 47))
17841791
>idMapping : Symbol(idMapping, Decl(keyofAndIndexedAccess.ts, 484, 18))
17851792
>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 482, 66))
17861793
>idField : Symbol(idField, Decl(keyofAndIndexedAccess.ts, 487, 14))

tests/baselines/reference/keyofAndIndexedAccess.types

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,40 +1731,39 @@ function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
17311731
// Repro from #13285
17321732

17331733
function updateIds<T extends Record<K, string>, K extends string>(
1734-
>updateIds : <T extends Record<K, string>, K extends string>(obj: T, idFields: K[], idMapping: { [oldId: string]: string; }) => Record<K, string>
1734+
>updateIds : <T extends Record<K, string>, K extends string>(obj: T, idFields: K[], idMapping: Partial<Record<T[K], T[K]>>) => Record<K, string>
17351735

17361736
obj: T,
17371737
>obj : T
17381738

17391739
idFields: K[],
17401740
>idFields : K[]
17411741

1742-
idMapping: { [oldId: string]: string }
1743-
>idMapping : { [oldId: string]: string; }
1744-
>oldId : string
1742+
idMapping: Partial<Record<T[K], T[K]>>
1743+
>idMapping : Partial<Record<T[K], T[K]>>
17451744

17461745
): Record<K, string> {
17471746
for (const idField of idFields) {
17481747
>idField : K
17491748
>idFields : K[]
17501749

1751-
const newId = idMapping[obj[idField]];
1752-
>newId : { [oldId: string]: string; }[T[K]]
1753-
>idMapping[obj[idField]] : { [oldId: string]: string; }[T[K]]
1754-
>idMapping : { [oldId: string]: string; }
1750+
const newId: T[K] | undefined = idMapping[obj[idField]];
1751+
>newId : T[K] | undefined
1752+
>idMapping[obj[idField]] : Partial<Record<T[K], T[K]>>[T[K]]
1753+
>idMapping : Partial<Record<T[K], T[K]>>
17551754
>obj[idField] : T[K]
17561755
>obj : T
17571756
>idField : K
17581757

17591758
if (newId) {
1760-
>newId : { [oldId: string]: string; }[T[K]]
1759+
>newId : T[K] | undefined
17611760

17621761
obj[idField] = newId;
1763-
>obj[idField] = newId : { [oldId: string]: string; }[T[K]]
1762+
>obj[idField] = newId : T[K]
17641763
>obj[idField] : T[K]
17651764
>obj : T
17661765
>idField : K
1767-
>newId : { [oldId: string]: string; }[T[K]]
1766+
>newId : T[K]
17681767
}
17691768
}
17701769
return obj;

tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,14 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(142,5): error
321321
~~~~
322322
!!! error TS2322: Type 'number[]' is not assignable to type 'T[K]'.
323323
}
324+
325+
// Repro from #28839
326+
327+
function f30<T, K extends keyof T>() {
328+
let x: Partial<Record<keyof T, string>>[K] = "hello";
329+
}
330+
331+
function f31<T, K extends keyof T>() {
332+
let x: Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K] = "hello";
333+
}
324334

tests/baselines/reference/keyofAndIndexedAccessErrors.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ function test1<T extends Record<string, any>, K extends keyof T>(t: T, k: K) {
142142
t[k] = "hello"; // Error
143143
t[k] = [10, 20]; // Error
144144
}
145+
146+
// Repro from #28839
147+
148+
function f30<T, K extends keyof T>() {
149+
let x: Partial<Record<keyof T, string>>[K] = "hello";
150+
}
151+
152+
function f31<T, K extends keyof T>() {
153+
let x: Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K] = "hello";
154+
}
145155

146156

147157
//// [keyofAndIndexedAccessErrors.js]
@@ -215,3 +225,10 @@ function test1(t, k) {
215225
t[k] = "hello"; // Error
216226
t[k] = [10, 20]; // Error
217227
}
228+
// Repro from #28839
229+
function f30() {
230+
var x = "hello";
231+
}
232+
function f31() {
233+
var x = "hello";
234+
}

tests/baselines/reference/keyofAndIndexedAccessErrors.symbols

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,39 @@ function test1<T extends Record<string, any>, K extends keyof T>(t: T, k: K) {
486486
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 138, 70))
487487
}
488488

489+
// Repro from #28839
490+
491+
function f30<T, K extends keyof T>() {
492+
>f30 : Symbol(f30, Decl(keyofAndIndexedAccessErrors.ts, 142, 1))
493+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 146, 13))
494+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 146, 15))
495+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 146, 13))
496+
497+
let x: Partial<Record<keyof T, string>>[K] = "hello";
498+
>x : Symbol(x, Decl(keyofAndIndexedAccessErrors.ts, 147, 7))
499+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
500+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
501+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 146, 13))
502+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 146, 15))
503+
}
504+
505+
function f31<T, K extends keyof T>() {
506+
>f31 : Symbol(f31, Decl(keyofAndIndexedAccessErrors.ts, 148, 1))
507+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 150, 13))
508+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 150, 15))
509+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 150, 13))
510+
511+
let x: Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K] = "hello";
512+
>x : Symbol(x, Decl(keyofAndIndexedAccessErrors.ts, 151, 7))
513+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
514+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
515+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
516+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
517+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
518+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
519+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
520+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
521+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 150, 13))
522+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 150, 15))
523+
}
524+

tests/baselines/reference/keyofAndIndexedAccessErrors.types

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,21 @@ function test1<T extends Record<string, any>, K extends keyof T>(t: T, k: K) {
465465
>20 : 20
466466
}
467467

468+
// Repro from #28839
469+
470+
function f30<T, K extends keyof T>() {
471+
>f30 : <T, K extends keyof T>() => void
472+
473+
let x: Partial<Record<keyof T, string>>[K] = "hello";
474+
>x : Partial<Record<keyof T, string>>[K]
475+
>"hello" : "hello"
476+
}
477+
478+
function f31<T, K extends keyof T>() {
479+
>f31 : <T, K extends keyof T>() => void
480+
481+
let x: Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K] = "hello";
482+
>x : Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K]
483+
>"hello" : "hello"
484+
}
485+

tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,10 @@ function onChangeGenericFunction<T>(handler: Handler<T & {preset: number}>) {
486486
function updateIds<T extends Record<K, string>, K extends string>(
487487
obj: T,
488488
idFields: K[],
489-
idMapping: { [oldId: string]: string }
489+
idMapping: Partial<Record<T[K], T[K]>>
490490
): Record<K, string> {
491491
for (const idField of idFields) {
492-
const newId = idMapping[obj[idField]];
492+
const newId: T[K] | undefined = idMapping[obj[idField]];
493493
if (newId) {
494494
obj[idField] = newId;
495495
}

tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,13 @@ function test1<T extends Record<string, any>, K extends keyof T>(t: T, k: K) {
141141
t[k] = "hello"; // Error
142142
t[k] = [10, 20]; // Error
143143
}
144+
145+
// Repro from #28839
146+
147+
function f30<T, K extends keyof T>() {
148+
let x: Partial<Record<keyof T, string>>[K] = "hello";
149+
}
150+
151+
function f31<T, K extends keyof T>() {
152+
let x: Partial<Partial<Partial<Partial<Partial<Partial<Partial<Record<keyof T, string>>>>>>>>[K] = "hello";
153+
}

0 commit comments

Comments
 (0)