I've written several implementations of Omit
, including the one shown by Intellisense when hovering over Omit
itself. I'm running into difficulty understanding why some implementations are homomorphic and others are not.
I've found that:
- The implementation shown when hovering over
Omit
is not the correct one - The implementation shown when hovering over
Omit
does not preserve the 'optionality' of properties (i.e. is not homomorphic), and is therefore different than the real implementation, which does preserve 'optionality'. - Two other implementations I've written are also not-homomorphic, and I can't understand why.
Here's my code:
// a type with optional and readonly properties
type HasOptional = { a: number; b?: number, c: string; d?: readonly string[]; };
// first attempt
type Omit_1<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P]; };
type Omit_1_Optional = Omit_1<HasOptional, 'a'>; // b, d lost optionality
// Omit's 'fake' implementation, as shown by Intellisense
type Omit_2<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; };
type Omit_2_Optional = Omit_2<HasOptional, 'a'>; // b, d lost optionality
// Using Omit itself
type Omit_3<T, K extends string | number | symbol> = Omit<T, K>;
type Omit_3_Optional = Omit_3<HasOptional, 'a'>; // optionality maintained!
// Writing Omit's implementation explicitly
type Omit_4<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Omit_4_Optional = Omit_4<HasOptional, 'a'>; // optionality maintained!
I've seen here, in an answer about deep Omit that [P in K]:
is used as an extra level of indirection to cause homomorphic behavior, but that's also present here and yet the first two implementations don't preserve 'optionality'.