Skip to content

Fix Diff and Omit #21156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 32 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8359,30 +8359,45 @@ namespace ts {
return false;
}

function isMappedTypeToNever(type: Type) {
return getObjectFlags(type) & ObjectFlags.Mapped && getTemplateTypeFromMappedType(type as MappedType) === neverType;
}

// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
// undefined if no transformation is possible.
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
const objectType = type.objectType;
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
// transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
// access types with default property values as expressed by D.
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
const regularTypes: Type[] = [];
const stringIndexTypes: Type[] = [];
for (const t of (<IntersectionType>objectType).types) {
if (isStringIndexOnlyType(t)) {
stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String));
}
else {
regularTypes.push(t);
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
// transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
// access types with default property values as expressed by D.
if (some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
const regularTypes: Type[] = [];
const stringIndexTypes: Type[] = [];
for (const t of (<IntersectionType>objectType).types) {
if (isStringIndexOnlyType(t)) {
stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String));
}
else {
regularTypes.push(t);
}
}
return getUnionType([
getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
getIntersectionType(stringIndexTypes)
]);
}
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a
// transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen
// eventually anyway, but it easier to reason about.
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType);
}
return getUnionType([
getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
getIntersectionType(stringIndexTypes)
]);
}

// 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]>.
Expand Down
15 changes: 15 additions & 0 deletions tests/baselines/reference/indexedAccessRetainsIndexSignature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//// [indexedAccessRetainsIndexSignature.ts]
type Diff<T extends string, U extends string> =
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(on many people's wishlist): add these to lib.d.ts 💟

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diff and Omit might someday be in lib.d.ts once conditional types are in Typescript, but the current implementation is way too hacky.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, only Diff implementation is way too hacky. But it will be solved if subtraction types make it into typescript (someday).

type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
// is in fact an equivalent of

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

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


//// [indexedAccessRetainsIndexSignature.js]
var o = { b: '' };
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts ===
type Diff<T extends string, U extends string> =
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27))

({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8))
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 26))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27))
>x : Symbol(x, Decl(indexedAccessRetainsIndexSignature.ts, 1, 48))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10))

type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12))

type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
>Omit1 : Symbol(Omit1, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
>Pick : Symbol(Pick, Decl(lib.d.ts, --, --))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13))

// is in fact an equivalent of

type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
>Omit2 : Symbol(Omit2, Decl(indexedAccessRetainsIndexSignature.ts, 3, 61))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37))
>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13))
>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11))
>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37))

type O = Omit<{ a: number, b: string }, 'a'>
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67))
>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71))
>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 8, 15))
>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 8, 26))

const o: O = { b: '' }
>o : Symbol(o, Decl(indexedAccessRetainsIndexSignature.ts, 9, 5))
>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67))
>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 9, 14))

64 changes: 64 additions & 0 deletions tests/baselines/reference/indexedAccessRetainsIndexSignature.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts ===
type Diff<T extends string, U extends string> =
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
>T : T
>U : U

({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
>P : P
>T : T
>P : P
>P : P
>U : U
>x : string
>T : T

type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
>Omit : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
>U : U
>K : K
>U : U
>Pick : Pick<T, K>
>U : U
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
>U : U
>K : K

type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
>Omit1 : Pick<U, ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof U]>
>U : U
>K : K
>U : U
>Pick : Pick<T, K>
>U : U
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
>U : U
>K : K

// is in fact an equivalent of

type Omit2<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
>Omit2 : Omit2<T, K>
>T : T
>K : K
>T : T
>P : P
>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T]
>T : T
>K : K
>T : T
>P : P

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

const o: O = { b: '' }
>o : Pick<{ a: number; b: string; }, "b">
>O : Pick<{ a: number; b: string; }, "b">
>{ b: '' } : { b: string; }
>b : string
>'' : ""

10 changes: 10 additions & 0 deletions tests/cases/compiler/indexedAccessRetainsIndexSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Diff<T extends string, U extends string> =
({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>
type Omit1<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
// is in fact an equivalent of

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

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