Skip to content

Commit

Permalink
Distribute indexes of indexed access types first (#27243)
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham authored Sep 21, 2018
1 parent 2b607a6 commit 219bb44
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 6 deletions.
20 changes: 16 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T[P]> }[X], we
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [contextualTypeOfIndexedAccessParameter.ts]
type Keys = "a" | "b";

type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };

declare function f<K extends Keys>(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;
}
Original file line number Diff line number Diff line change
@@ -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<K extends Keys>(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))
}

Original file line number Diff line number Diff line change
@@ -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<K extends Keys>(key: K, options: OptionsForKey[K]): void;
>f : <K extends Keys>(key: K, options: OptionsForKey[K]) => void
>key : K
>options : OptionsForKey[K]

f("a", {
>f("a", { cb: p => p,}) : void
>f : <K extends Keys>(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 : <K extends Keys>(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
}

8 changes: 6 additions & 2 deletions tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
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"]'.
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"]'.
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"]'.
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"]'.
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
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'.
Expand All @@ -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<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"]'.
!!! 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"]'.
!!! 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"]'.
!!! 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"]'.
==== tests/cases/compiler/infiniteConstraints.ts (7 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

Expand Down
15 changes: 15 additions & 0 deletions tests/cases/compiler/contextualTypeOfIndexedAccessParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @strict: true
type Keys = "a" | "b";

type OptionsForKey = { a: { cb: (p: number) => number } } & { b: {} };

declare function f<K extends Keys>(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;
}

0 comments on commit 219bb44

Please sign in to comment.