Skip to content

Commit d715d83

Browse files
Merge pull request microsoft#27254 from weswigham/port-distribution-fix
Distribute indexes of indexed access types first (microsoft#27243)
2 parents c48de89 + 317b2c2 commit d715d83

6 files changed

+154
-6
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9369,12 +9369,24 @@ namespace ts {
93699369
// '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type.
93709370
const objectType = getSimplifiedType(type.objectType);
93719371
const indexType = getSimplifiedType(type.indexType);
9372-
if (objectType.flags & TypeFlags.Union) {
9373-
return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
9372+
// T[A | B] -> T[A] | T[B]
9373+
if (indexType.flags & TypeFlags.Union) {
9374+
return type.simplified = mapType(indexType, t => getSimplifiedType(getIndexedAccessType(objectType, t)));
93749375
}
9375-
if (objectType.flags & TypeFlags.Intersection) {
9376-
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
9376+
// Only do the inner distributions if the index can no longer be instantiated to cause index distribution again
9377+
if (!(indexType.flags & TypeFlags.Instantiable)) {
9378+
// (T | U)[K] -> T[K] | U[K]
9379+
if (objectType.flags & TypeFlags.Union) {
9380+
return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
9381+
}
9382+
// (T & U)[K] -> T[K] & U[K]
9383+
if (objectType.flags & TypeFlags.Intersection) {
9384+
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
9385+
}
93779386
}
9387+
// So ultimately:
9388+
// ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2]
9389+
93789390
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
93799391
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
93809392
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [contextualTypeOfIndexedAccessParameter.ts]
2+
type Keys = "a" | "b";
3+
4+
type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };
5+
6+
declare function f<K extends Keys>(key: K, options: OptionsForKey[K]): void;
7+
8+
f("a", {
9+
cb: p => p,
10+
});
11+
12+
function g<
13+
K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) {
14+
x = y;
15+
}
16+
17+
18+
//// [contextualTypeOfIndexedAccessParameter.js]
19+
"use strict";
20+
f("a", {
21+
cb: function (p) { return p; }
22+
});
23+
function g(x, y) {
24+
x = y;
25+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts ===
2+
type Keys = "a" | "b";
3+
>Keys : Symbol(Keys, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 0))
4+
5+
type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };
6+
>OptionsForKey : Symbol(OptionsForKey, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 22))
7+
>a : Symbol(a, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 22))
8+
>cb : Symbol(cb, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 27))
9+
>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 33))
10+
>b : Symbol(b, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 61))
11+
12+
declare function f<K extends Keys>(key: K, options: OptionsForKey[K]): void;
13+
>f : Symbol(f, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 70))
14+
>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19))
15+
>Keys : Symbol(Keys, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 0))
16+
>key : Symbol(key, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 35))
17+
>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19))
18+
>options : Symbol(options, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 42))
19+
>OptionsForKey : Symbol(OptionsForKey, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 22))
20+
>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19))
21+
22+
f("a", {
23+
>f : Symbol(f, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 70))
24+
25+
cb: p => p,
26+
>cb : Symbol(cb, Decl(contextualTypeOfIndexedAccessParameter.ts, 6, 8))
27+
>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 7, 7))
28+
>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 7, 7))
29+
30+
});
31+
32+
function g<
33+
>g : Symbol(g, Decl(contextualTypeOfIndexedAccessParameter.ts, 8, 3))
34+
35+
K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) {
36+
>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 10, 11))
37+
>x : Symbol(x, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 25))
38+
>a : Symbol(a, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 30))
39+
>b : Symbol(b, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 46))
40+
>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 10, 11))
41+
>y : Symbol(y, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 63))
42+
43+
x = y;
44+
>x : Symbol(x, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 25))
45+
>y : Symbol(y, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 63))
46+
}
47+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts ===
2+
type Keys = "a" | "b";
3+
>Keys : Keys
4+
5+
type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };
6+
>OptionsForKey : OptionsForKey
7+
>a : { cb: (p: number) => number; }
8+
>cb : (p: number) => number
9+
>p : number
10+
>b : {}
11+
12+
declare function f<K extends Keys>(key: K, options: OptionsForKey[K]): void;
13+
>f : <K extends Keys>(key: K, options: OptionsForKey[K]) => void
14+
>key : K
15+
>options : OptionsForKey[K]
16+
17+
f("a", {
18+
>f("a", { cb: p => p,}) : void
19+
>f : <K extends Keys>(key: K, options: OptionsForKey[K]) => void
20+
>"a" : "a"
21+
>{ cb: p => p,} : { cb: (p: number) => number; }
22+
23+
cb: p => p,
24+
>cb : (p: number) => number
25+
>p => p : (p: number) => number
26+
>p : number
27+
>p : number
28+
29+
});
30+
31+
function g<
32+
>g : <K extends Keys>(x: ({ a: string; } & { b: string; })[K], y: string) => void
33+
34+
K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) {
35+
>x : ({ a: string; } & { b: string; })[K]
36+
>a : string
37+
>b : string
38+
>y : string
39+
40+
x = y;
41+
>x = y : string
42+
>x : ({ a: string; } & { b: string; })[K]
43+
>y : string
44+
}
45+

tests/baselines/reference/infiniteConstraints.errors.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string | number | symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string | number | symbol>>], Record<"val", string>>["val"]'.
1+
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, number>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, number>>], Record<"val", string>>["val"]'.
2+
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string>>], Record<"val", string>>["val"]'.
3+
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, symbol>>], Record<"val", string>>["val"]'.
24
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
35
tests/cases/compiler/infiniteConstraints.ts(27,37): error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'.
46
tests/cases/compiler/infiniteConstraints.ts(27,58): error TS2322: Type 'Record<"val", "test2">' is not assignable to type 'never'.
@@ -8,7 +10,9 @@ tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"
810
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
911

1012

11-
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string | number | symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string | number | symbol>>], Record<"val", string>>["val"]'.
13+
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, number>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, number>>], Record<"val", string>>["val"]'.
14+
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string>>], Record<"val", string>>["val"]'.
15+
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, symbol>>], Record<"val", string>>["val"]'.
1216
==== tests/cases/compiler/infiniteConstraints.ts (7 errors) ====
1317
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
1418

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @strict: true
2+
type Keys = "a" | "b";
3+
4+
type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };
5+
6+
declare function f<K extends Keys>(key: K, options: OptionsForKey[K]): void;
7+
8+
f("a", {
9+
cb: p => p,
10+
});
11+
12+
function g<
13+
K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) {
14+
x = y;
15+
}

0 commit comments

Comments
 (0)