Skip to content

Commit d74820d

Browse files
committed
Remove mapped types to never from intersections
when transforming an indexed access type in order to get its constraint.
1 parent baf31ec commit d74820d

5 files changed

Lines changed: 92 additions & 8 deletions

File tree

src/compiler/checker.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6468,7 +6468,7 @@ namespace ts {
64686468
}
64696469

64706470
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
6471-
const transformed = getSubstitutedIndexedMappedType(type);
6471+
const transformed = getSimplifiedIndexedAccessType(type);
64726472
if (transformed) {
64736473
return transformed;
64746474
}
@@ -8359,6 +8359,10 @@ namespace ts {
83598359
return false;
83608360
}
83618361

8362+
function isMappedTypeToNever(type: Type) {
8363+
return getObjectFlags(type) & ObjectFlags.Mapped && getTemplateTypeFromMappedType(type as MappedType) === neverType;
8364+
}
8365+
83628366
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
83638367
// undefined if no transformation is possible.
83648368
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
@@ -8383,11 +8387,22 @@ namespace ts {
83838387
getIntersectionType(stringIndexTypes)
83848388
]);
83858389
}
8386-
return getSubstitutedIndexedMappedType(type);
8387-
}
8390+
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
8391+
// more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a
8392+
// transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen
8393+
// eventually anyway, but it easier to reason about.
8394+
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
8395+
let nonNeverTypes: Type[];
8396+
for (const t of (<IntersectionType>objectType).types) {
8397+
if (!isMappedTypeToNever(t)) {
8398+
(nonNeverTypes || (nonNeverTypes = [])).push(t);
8399+
}
8400+
}
8401+
if (nonNeverTypes) {
8402+
return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType);
8403+
}
8404+
}
83888405

8389-
function getSubstitutedIndexedMappedType(type: IndexedAccessType): Type {
8390-
const objectType = type.objectType;
83918406
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
83928407
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
83938408
// construct the type Box<T[X]>.

tests/baselines/reference/indexedAccessRetainsIndexSignature.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
type Diff<T extends string, U extends string> =
33
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
44
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
5+
type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
6+
// is in fact an equivalent of
57

8+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
69

710
type O = Omit<{ a: number, b: string }, 'a'>
11+
const o: O = { b: '' }
812

913

1014
//// [indexedAccessRetainsIndexSignature.js]
15+
var o = { b: '' };

tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,39 @@ type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
2424
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
2525
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12))
2626

27+
type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
28+
>Omit1 : Symbol(Omit1, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59))
29+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
30+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13))
31+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
32+
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
33+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
34+
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
35+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
36+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13))
37+
38+
// is in fact an equivalent of
39+
40+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
41+
>Omit2 : Symbol(Omit2, Decl(indexedAccessRetainsIndexSignature.ts, 3, 61))
42+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
43+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13))
44+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
45+
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37))
46+
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
47+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
48+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13))
49+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
50+
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37))
2751

2852
type O = Omit<{ a: number, b: string }, 'a'>
29-
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59))
53+
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67))
3054
>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71))
31-
>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 5, 15))
32-
>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 5, 26))
55+
>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 8, 15))
56+
>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 8, 26))
57+
58+
const o: O = { b: '' }
59+
>o : Symbol(o, Decl(indexedAccessRetainsIndexSignature.ts, 9, 5))
60+
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67))
61+
>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 9, 14))
3362

tests/baselines/reference/indexedAccessRetainsIndexSignature.types

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,41 @@ type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
2424
>U : U
2525
>K : K
2626

27+
type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
28+
>Omit1 : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
29+
>U : U
30+
>K : K
31+
>U : U
32+
>Pick : Pick<T, K>
33+
>U : U
34+
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
35+
>U : U
36+
>K : K
37+
38+
// is in fact an equivalent of
39+
40+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
41+
>Omit2 : Omit2<T, K>
42+
>T : T
43+
>K : K
44+
>T : T
45+
>P : P
46+
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
47+
>T : T
48+
>K : K
49+
>T : T
50+
>P : P
2751

2852
type O = Omit<{ a: number, b: string }, 'a'>
2953
>O : Pick<{ a: number; b: string; }, "b">
3054
>Omit : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
3155
>a : number
3256
>b : string
3357

58+
const o: O = { b: '' }
59+
>o : Pick<{ a: number; b: string; }, "b">
60+
>O : Pick<{ a: number; b: string; }, "b">
61+
>{ b: '' } : { b: string; }
62+
>b : string
63+
>'' : ""
64+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
type Diff<T extends string, U extends string> =
22
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
33
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
4+
type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
5+
// is in fact an equivalent of
46

7+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
58

69
type O = Omit<{ a: number, b: string }, 'a'>
10+
const o: O = { b: '' }

0 commit comments

Comments
 (0)