Skip to content

Commit 40b896a

Browse files
authored
Merge pull request #17912 from Microsoft/fix2-getConstraintOfIndexedAccess
Fix2 get constraint of indexed access
2 parents 7a1deae + 8f45373 commit 40b896a

17 files changed

+787
-195
lines changed

src/compiler/checker.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6351,7 +6351,7 @@ namespace ts {
63516351
return getObjectFlags(type) & ObjectFlags.Mapped && !!(<MappedType>type).declaration.questionToken;
63526352
}
63536353

6354-
function isGenericMappedType(type: Type) {
6354+
function isGenericMappedType(type: Type): type is MappedType {
63556355
return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type));
63566356
}
63576357

@@ -6463,12 +6463,17 @@ namespace ts {
64636463
}
64646464

64656465
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
6466-
const transformed = getTransformedIndexedAccessType(type);
6466+
const transformed = getSimplifiedIndexedAccessType(type);
64676467
if (transformed) {
64686468
return transformed;
64696469
}
64706470
const baseObjectType = getBaseConstraintOfType(type.objectType);
64716471
const baseIndexType = getBaseConstraintOfType(type.indexType);
6472+
if (baseIndexType === stringType && !getIndexInfoOfType(baseObjectType || type.objectType, IndexKind.String)) {
6473+
// getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature.
6474+
// to avoid this, return `undefined`.
6475+
return undefined;
6476+
}
64726477
return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined;
64736478
}
64746479

@@ -6518,8 +6523,9 @@ namespace ts {
65186523
function computeBaseConstraint(t: Type): Type {
65196524
if (t.flags & TypeFlags.TypeParameter) {
65206525
const constraint = getConstraintFromTypeParameter(<TypeParameter>t);
6521-
return (<TypeParameter>t).isThisType ? constraint :
6522-
constraint ? getBaseConstraint(constraint) : undefined;
6526+
return (t as TypeParameter).isThisType || !constraint ?
6527+
constraint :
6528+
getBaseConstraint(constraint);
65236529
}
65246530
if (t.flags & TypeFlags.UnionOrIntersection) {
65256531
const types = (<UnionOrIntersectionType>t).types;
@@ -6538,7 +6544,7 @@ namespace ts {
65386544
return stringType;
65396545
}
65406546
if (t.flags & TypeFlags.IndexedAccess) {
6541-
const transformed = getTransformedIndexedAccessType(<IndexedAccessType>t);
6547+
const transformed = getSimplifiedIndexedAccessType(<IndexedAccessType>t);
65426548
if (transformed) {
65436549
return getBaseConstraint(transformed);
65446550
}
@@ -8350,7 +8356,7 @@ namespace ts {
83508356

83518357
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
83528358
// undefined if no transformation is possible.
8353-
function getTransformedIndexedAccessType(type: IndexedAccessType): Type {
8359+
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
83548360
const objectType = type.objectType;
83558361
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
83568362
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
@@ -8376,14 +8382,24 @@ namespace ts {
83768382
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
83778383
// construct the type Box<T[X]>.
83788384
if (isGenericMappedType(objectType)) {
8379-
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>objectType)], [type.indexType]);
8380-
const objectTypeMapper = (<MappedType>objectType).mapper;
8381-
const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper;
8382-
return instantiateType(getTemplateTypeFromMappedType(<MappedType>objectType), templateMapper);
8385+
return substituteIndexedMappedType(objectType, type);
8386+
}
8387+
if (objectType.flags & TypeFlags.TypeParameter) {
8388+
const constraint = getConstraintFromTypeParameter(objectType as TypeParameter);
8389+
if (constraint && isGenericMappedType(constraint)) {
8390+
return substituteIndexedMappedType(constraint, type);
8391+
}
83838392
}
83848393
return undefined;
83858394
}
83868395

8396+
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
8397+
const mapper = createTypeMapper([getTypeParameterFromMappedType(<MappedType>objectType)], [type.indexType]);
8398+
const objectTypeMapper = (<MappedType>objectType).mapper;
8399+
const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper;
8400+
return instantiateType(getTemplateTypeFromMappedType(<MappedType>objectType), templateMapper);
8401+
}
8402+
83878403
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type {
83888404
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
83898405
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
@@ -10059,7 +10075,7 @@ namespace ts {
1005910075
}
1006010076
else if (target.flags & TypeFlags.IndexedAccess) {
1006110077
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
10062-
// A is the apparent type of S.
10078+
// A is the apparent type of T.
1006310079
const constraint = getConstraintOfIndexedAccess(<IndexedAccessType>target);
1006410080
if (constraint) {
1006510081
if (result = isRelatedTo(source, constraint, reportErrors)) {

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ namespace ts {
893893
export function sum<T extends Record<K, number>, K extends string>(array: ReadonlyArray<T>, prop: K): number {
894894
let result = 0;
895895
for (const v of array) {
896-
// Note: we need the following type assertion because of GH #17069
896+
// TODO: Remove the following type assertion once the fix for #17069 is merged
897897
result += v[prop] as number;
898898
}
899899
return result;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [additionOperatorWithConstrainedTypeParameter.ts]
2+
// test for #17069
3+
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
4+
n = n + v[k];
5+
n += v[k]; // += should work the same way
6+
}
7+
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
8+
for (const v of vs) {
9+
n = n + v[k];
10+
n += v[k];
11+
}
12+
}
13+
14+
15+
//// [additionOperatorWithConstrainedTypeParameter.js]
16+
// test for #17069
17+
function sum(n, v, k) {
18+
n = n + v[k];
19+
n += v[k]; // += should work the same way
20+
}
21+
function realSum(n, vs, k) {
22+
for (var _i = 0, vs_1 = vs; _i < vs_1.length; _i++) {
23+
var v = vs_1[_i];
24+
n = n + v[k];
25+
n += v[k];
26+
}
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts ===
2+
// test for #17069
3+
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
4+
>sum : Symbol(sum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 0, 0))
5+
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13))
6+
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
7+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))
8+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))
9+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
10+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
11+
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13))
12+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))
13+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41))
14+
15+
n = n + v[k];
16+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
17+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
18+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
19+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))
20+
21+
n += v[k]; // += should work the same way
22+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60))
23+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70))
24+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76))
25+
}
26+
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
27+
>realSum : Symbol(realSum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 4, 1))
28+
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17))
29+
>Record : Symbol(Record, Decl(lib.d.ts, --, --))
30+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))
31+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))
32+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
33+
>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74))
34+
>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17))
35+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))
36+
>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45))
37+
38+
for (const v of vs) {
39+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
40+
>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74))
41+
42+
n = n + v[k];
43+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
44+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
45+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
46+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))
47+
48+
n += v[k];
49+
>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64))
50+
>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14))
51+
>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83))
52+
}
53+
}
54+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts ===
2+
// test for #17069
3+
function sum<T extends Record<K, number>, K extends string>(n: number, v: T, k: K) {
4+
>sum : <T extends Record<K, number>, K extends string>(n: number, v: T, k: K) => void
5+
>T : T
6+
>Record : Record<K, T>
7+
>K : K
8+
>K : K
9+
>n : number
10+
>v : T
11+
>T : T
12+
>k : K
13+
>K : K
14+
15+
n = n + v[k];
16+
>n = n + v[k] : number
17+
>n : number
18+
>n + v[k] : number
19+
>n : number
20+
>v[k] : T[K]
21+
>v : T
22+
>k : K
23+
24+
n += v[k]; // += should work the same way
25+
>n += v[k] : number
26+
>n : number
27+
>v[k] : T[K]
28+
>v : T
29+
>k : K
30+
}
31+
function realSum<T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) {
32+
>realSum : <T extends Record<K, number>, K extends string>(n: number, vs: T[], k: K) => void
33+
>T : T
34+
>Record : Record<K, T>
35+
>K : K
36+
>K : K
37+
>n : number
38+
>vs : T[]
39+
>T : T
40+
>k : K
41+
>K : K
42+
43+
for (const v of vs) {
44+
>v : T
45+
>vs : T[]
46+
47+
n = n + v[k];
48+
>n = n + v[k] : number
49+
>n : number
50+
>n + v[k] : number
51+
>n : number
52+
>v[k] : T[K]
53+
>v : T
54+
>k : K
55+
56+
n += v[k];
57+
>n += v[k] : number
58+
>n : number
59+
>v[k] : T[K]
60+
>v : T
61+
>k : K
62+
}
63+
}
64+

tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error
2727
Type 'T' is not assignable to type 'T & U'.
2828
Type 'T' is not assignable to type 'U'.
2929
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'.
30+
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(84,9): error TS2322: Type 'keyof T' is not assignable to type 'K'.
31+
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'.
3032

3133

32-
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ====
34+
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (27 errors) ====
3335
class Shape {
3436
name: string;
3537
width: number;
@@ -162,4 +164,18 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error
162164
~~
163165
!!! error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'.
164166
k2 = k1;
165-
}
167+
}
168+
169+
// Repro from #17166
170+
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
171+
for (let key in obj) {
172+
k = key // error, keyof T =/=> K
173+
~
174+
!!! error TS2322: Type 'keyof T' is not assignable to type 'K'.
175+
value = obj[key]; // error, T[keyof T] =/=> T[K]
176+
~~~~~
177+
!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'.
178+
}
179+
}
180+
181+

tests/baselines/reference/keyofAndIndexedAccessErrors.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,17 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
7777
o2 = o1; // Error
7878
k1 = k2; // Error
7979
k2 = k1;
80-
}
80+
}
81+
82+
// Repro from #17166
83+
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
84+
for (let key in obj) {
85+
k = key // error, keyof T =/=> K
86+
value = obj[key]; // error, T[keyof T] =/=> T[K]
87+
}
88+
}
89+
90+
8191

8292
//// [keyofAndIndexedAccessErrors.js]
8393
var Shape = /** @class */ (function () {
@@ -109,3 +119,10 @@ function f20(k1, k2, o1, o2) {
109119
k1 = k2; // Error
110120
k2 = k1;
111121
}
122+
// Repro from #17166
123+
function f3(obj, k, value) {
124+
for (var key in obj) {
125+
k = key; // error, keyof T =/=> K
126+
value = obj[key]; // error, T[keyof T] =/=> T[K]
127+
}
128+
}

tests/baselines/reference/keyofAndIndexedAccessErrors.symbols

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,34 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
268268
>k2 : Symbol(k2, Decl(keyofAndIndexedAccessErrors.ts, 69, 37))
269269
>k1 : Symbol(k1, Decl(keyofAndIndexedAccessErrors.ts, 69, 19))
270270
}
271+
272+
// Repro from #17166
273+
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
274+
>f3 : Symbol(f3, Decl(keyofAndIndexedAccessErrors.ts, 78, 1))
275+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
276+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))
277+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
278+
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))
279+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
280+
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41))
281+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))
282+
>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47))
283+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12))
284+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14))
285+
286+
for (let key in obj) {
287+
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))
288+
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))
289+
290+
k = key // error, keyof T =/=> K
291+
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41))
292+
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))
293+
294+
value = obj[key]; // error, T[keyof T] =/=> T[K]
295+
>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47))
296+
>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34))
297+
>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12))
298+
}
299+
}
300+
301+

tests/baselines/reference/keyofAndIndexedAccessErrors.types

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,37 @@ function f20<T, U>(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) {
299299
>k2 : keyof (T & U)
300300
>k1 : keyof (T | U)
301301
}
302+
303+
// Repro from #17166
304+
function f3<T, K extends keyof T>(obj: T, k: K, value: T[K]): void {
305+
>f3 : <T, K extends keyof T>(obj: T, k: K, value: T[K]) => void
306+
>T : T
307+
>K : K
308+
>T : T
309+
>obj : T
310+
>T : T
311+
>k : K
312+
>K : K
313+
>value : T[K]
314+
>T : T
315+
>K : K
316+
317+
for (let key in obj) {
318+
>key : keyof T
319+
>obj : T
320+
321+
k = key // error, keyof T =/=> K
322+
>k = key : keyof T
323+
>k : K
324+
>key : keyof T
325+
326+
value = obj[key]; // error, T[keyof T] =/=> T[K]
327+
>value = obj[key] : T[keyof T]
328+
>value : T[K]
329+
>obj[key] : T[keyof T]
330+
>obj : T
331+
>key : keyof T
332+
}
333+
}
334+
335+

0 commit comments

Comments
 (0)