I've written a nested Omit type in two different ways, but the one that makes more sense to me gives convoluted type hints while the one that's less intuitive actually looks best. Here goes:
type HasSameNested = {
a: {
c: number; d: string
},
b: {
c: string; d: number
}
}; // flipped C and D types
type Omit2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
[P1 in keyof T]: P1 extends K1 ? {
[P2 in Exclude<keyof T[K1], K2>]: T[P1][P2]
} : T[P1]
}
type Omit2_2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
[P1 in keyof T]: P1 extends K1 ? {
[P2 in Exclude<keyof T[P1], K2>]: T[P1][P2]
} : T[P1]
}
type omit_union = Omit2<HasSameNested, 'a' | 'b', 'c'>;
let T6: omit_union;
T6.a; // works, type is { d: string }
T6.b; // works, type is { d: number }
type omit_union_2 = Omit2_2<HasSameNested, 'a' | 'b', 'c'>;
let T7: omit_union_2;
T7.a.d = 'a'; // works but types are ugly:
// a: { [P2 in Exclude<keyof HasSameNested[K1 & "a"], "c">]: HasSameNested[K1 & "a"][P2]; }
// d: d: HasSameNested[K1 & "a"]["d"]
T7.b.d = 4; // works but types are ugly, as above
Assigning the wrong type to T7.b.d or T7.a.d does tell me e.g. string is not assignable to number, but I don't understand why using Exclude<keyof T[P1], K2> gives such convoluted typing even though P1 in keyof T and P1 extends K1, and Exclude<keyof T[K1], K2> gives the correct types.
Omit2_2is weird... the seemingly unbound type parameterK1is present, which is surprising. Maybe that part is a bug in Intellisense?T extends U ? F<T> : Vis simplified toF<T & U>ifTdefinitely extendsU. The part where the type parameter makes it into IntelliSense is unexpected, though.type Hmm<T extends U, U> = T extends U ? { [K in keyof T]: any } : never; type What = Hmm<{ a: string }, {}>P1extendsK1? I've read the change but don't grasp the rationale - isn'tP1already specific enough? What extra information is conveyed byP1 & K1as a type?