Skip to content

Commit 6f28b0a

Browse files
authored
Merge pull request #21156 from Microsoft/fix-diff-omit
Fix Diff and Omit
2 parents b8acf8d + fd1bb9b commit 6f28b0a

File tree

5 files changed

+183
-17
lines changed

5 files changed

+183
-17
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8359,30 +8359,45 @@ 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 {
83658369
const objectType = type.objectType;
8366-
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
8367-
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
8368-
// transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
8369-
// access types with default property values as expressed by D.
8370-
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
8371-
const regularTypes: Type[] = [];
8372-
const stringIndexTypes: Type[] = [];
8373-
for (const t of (<IntersectionType>objectType).types) {
8374-
if (isStringIndexOnlyType(t)) {
8375-
stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String));
8376-
}
8377-
else {
8378-
regularTypes.push(t);
8370+
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
8371+
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
8372+
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
8373+
// transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
8374+
// access types with default property values as expressed by D.
8375+
if (some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
8376+
const regularTypes: Type[] = [];
8377+
const stringIndexTypes: Type[] = [];
8378+
for (const t of (<IntersectionType>objectType).types) {
8379+
if (isStringIndexOnlyType(t)) {
8380+
stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String));
8381+
}
8382+
else {
8383+
regularTypes.push(t);
8384+
}
83798385
}
8386+
return getUnionType([
8387+
getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
8388+
getIntersectionType(stringIndexTypes)
8389+
]);
8390+
}
8391+
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
8392+
// more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a
8393+
// transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen
8394+
// eventually anyway, but it easier to reason about.
8395+
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
8396+
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
8397+
return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType);
83808398
}
8381-
return getUnionType([
8382-
getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
8383-
getIntersectionType(stringIndexTypes)
8384-
]);
83858399
}
8400+
83868401
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
83878402
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
83888403
// construct the type Box<T[X]>.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [indexedAccessRetainsIndexSignature.ts]
2+
type Diff<T extends string, U extends string> =
3+
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
4+
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
7+
8+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
9+
10+
type O = Omit<{ a: number, b: string }, 'a'>
11+
const o: O = { b: '' }
12+
13+
14+
//// [indexedAccessRetainsIndexSignature.js]
15+
var o = { b: '' };
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts ===
2+
type Diff<T extends string, U extends string> =
3+
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
4+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))
5+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27))
6+
7+
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
8+
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8))
9+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))
10+
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8))
11+
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 26))
12+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27))
13+
>x : Symbol(x, Decl(indexedAccessRetainsIndexSignature.ts, 1, 48))
14+
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))
15+
16+
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
17+
>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71))
18+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
19+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12))
20+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
21+
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
22+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
23+
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
24+
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
25+
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12))
26+
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))
51+
52+
type O = Omit<{ a: number, b: string }, 'a'>
53+
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67))
54+
>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71))
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))
62+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts ===
2+
type Diff<T extends string, U extends string> =
3+
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
4+
>T : T
5+
>U : U
6+
7+
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
8+
>P : P
9+
>T : T
10+
>P : P
11+
>P : P
12+
>U : U
13+
>x : string
14+
>T : T
15+
16+
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
17+
>Omit : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
18+
>U : U
19+
>K : K
20+
>U : U
21+
>Pick : Pick<T, K>
22+
>U : U
23+
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
24+
>U : U
25+
>K : K
26+
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
51+
52+
type O = Omit<{ a: number, b: string }, 'a'>
53+
>O : Pick<{ a: number; b: string; }, "b">
54+
>Omit : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
55+
>a : number
56+
>b : string
57+
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Diff<T extends string, U extends string> =
2+
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
3+
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
6+
7+
type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
8+
9+
type O = Omit<{ a: number, b: string }, 'a'>
10+
const o: O = { b: '' }

0 commit comments

Comments
 (0)