diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58717c941d932..987ed00c43af3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9369,12 +9369,24 @@ namespace ts { // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. const objectType = getSimplifiedType(type.objectType); const indexType = getSimplifiedType(type.indexType); - if (objectType.flags & TypeFlags.Union) { - return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType))); + // T[A | B] -> T[A] | T[B] + if (indexType.flags & TypeFlags.Union) { + return type.simplified = mapType(indexType, t => getSimplifiedType(getIndexedAccessType(objectType, t))); } - if (objectType.flags & TypeFlags.Intersection) { - return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType)))); + // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again + if (!(indexType.flags & TypeFlags.Instantiable)) { + // (T | U)[K] -> T[K] | U[K] + if (objectType.flags & TypeFlags.Union) { + return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType))); + } + // (T & U)[K] -> T[K] & U[K] + if (objectType.flags & TypeFlags.Intersection) { + return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType)))); + } } + // So ultimately: + // ((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] + // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. We do not further simplify the result because mapped types can be recursive diff --git a/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.js b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.js new file mode 100644 index 0000000000000..ec101ecd3afec --- /dev/null +++ b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.js @@ -0,0 +1,25 @@ +//// [contextualTypeOfIndexedAccessParameter.ts] +type Keys = "a" | "b"; + +type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} }; + +declare function f(key: K, options: OptionsForKey[K]): void; + +f("a", { + cb: p => p, +}); + +function g< + K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) { + x = y; +} + + +//// [contextualTypeOfIndexedAccessParameter.js] +"use strict"; +f("a", { + cb: function (p) { return p; } +}); +function g(x, y) { + x = y; +} diff --git a/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.symbols b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.symbols new file mode 100644 index 0000000000000..74cc7843f8709 --- /dev/null +++ b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.symbols @@ -0,0 +1,47 @@ +=== tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts === +type Keys = "a" | "b"; +>Keys : Symbol(Keys, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 0)) + +type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} }; +>OptionsForKey : Symbol(OptionsForKey, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 22)) +>a : Symbol(a, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 22)) +>cb : Symbol(cb, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 27)) +>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 33)) +>b : Symbol(b, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 61)) + +declare function f(key: K, options: OptionsForKey[K]): void; +>f : Symbol(f, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 70)) +>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19)) +>Keys : Symbol(Keys, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 0)) +>key : Symbol(key, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 35)) +>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19)) +>options : Symbol(options, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 42)) +>OptionsForKey : Symbol(OptionsForKey, Decl(contextualTypeOfIndexedAccessParameter.ts, 0, 22)) +>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 4, 19)) + +f("a", { +>f : Symbol(f, Decl(contextualTypeOfIndexedAccessParameter.ts, 2, 70)) + + cb: p => p, +>cb : Symbol(cb, Decl(contextualTypeOfIndexedAccessParameter.ts, 6, 8)) +>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 7, 7)) +>p : Symbol(p, Decl(contextualTypeOfIndexedAccessParameter.ts, 7, 7)) + +}); + +function g< +>g : Symbol(g, Decl(contextualTypeOfIndexedAccessParameter.ts, 8, 3)) + + K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) { +>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 10, 11)) +>x : Symbol(x, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 25)) +>a : Symbol(a, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 30)) +>b : Symbol(b, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 46)) +>K : Symbol(K, Decl(contextualTypeOfIndexedAccessParameter.ts, 10, 11)) +>y : Symbol(y, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 63)) + + x = y; +>x : Symbol(x, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 25)) +>y : Symbol(y, Decl(contextualTypeOfIndexedAccessParameter.ts, 11, 63)) +} + diff --git a/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.types b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.types new file mode 100644 index 0000000000000..e2fe48b76e27c --- /dev/null +++ b/tests/baselines/reference/contextualTypeOfIndexedAccessParameter.types @@ -0,0 +1,45 @@ +=== tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts === +type Keys = "a" | "b"; +>Keys : Keys + +type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} }; +>OptionsForKey : OptionsForKey +>a : { cb: (p: number) => number; } +>cb : (p: number) => number +>p : number +>b : {} + +declare function f(key: K, options: OptionsForKey[K]): void; +>f : (key: K, options: OptionsForKey[K]) => void +>key : K +>options : OptionsForKey[K] + +f("a", { +>f("a", { cb: p => p,}) : void +>f : (key: K, options: OptionsForKey[K]) => void +>"a" : "a" +>{ cb: p => p,} : { cb: (p: number) => number; } + + cb: p => p, +>cb : (p: number) => number +>p => p : (p: number) => number +>p : number +>p : number + +}); + +function g< +>g : (x: ({ a: string; } & { b: string; })[K], y: string) => void + + K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) { +>x : ({ a: string; } & { b: string; })[K] +>a : string +>b : string +>y : string + + x = y; +>x = y : string +>x : ({ a: string; } & { b: string; })[K] +>y : string +} + diff --git a/tests/baselines/reference/infiniteConstraints.errors.txt b/tests/baselines/reference/infiniteConstraints.errors.txt index 71034e87edfa8..36b77b2b3f611 100644 --- a/tests/baselines/reference/infiniteConstraints.errors.txt +++ b/tests/baselines/reference/infiniteConstraints.errors.txt @@ -1,4 +1,6 @@ -error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. tests/cases/compiler/infiniteConstraints.ts(27,37): error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'. 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<" tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. -!!! error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +!!! error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +!!! error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. +!!! error TS2321: Excessive stack depth comparing types 'Extract], Record<"val", string>>["val"]' and 'Extract>], Record<"val", string>>["val"]'. ==== tests/cases/compiler/infiniteConstraints.ts (7 errors) ==== // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint diff --git a/tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts b/tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts new file mode 100644 index 0000000000000..2135ed5cb29e4 --- /dev/null +++ b/tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts @@ -0,0 +1,15 @@ +// @strict: true +type Keys = "a" | "b"; + +type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} }; + +declare function f(key: K, options: OptionsForKey[K]): void; + +f("a", { + cb: p => p, +}); + +function g< + K extends "a" | "b">(x: ({ a: string } & { b: string })[K], y: string) { + x = y; +}