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
Omitis not the correct one - The implementation shown when hovering over
Omitdoes 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'.