Open7
TypeScriptでOmitが使えないパターン

Omitとは、TypeScriptでオブジェクトから特定のプロパティを削除するUtility Typeである。
type A = {
a: number
b: string
}
type B = Omit<A, 'a'>
const b: B = {
a: 8, // エラー
b: "hello",
}

このOmitには大きな問題がある。
type A = {
a: number
} & (
{
b: number
c?: never
} | {
b?: never
c: number
}
)
このような型を考える。これはbが指定されているときはcが指定できなく、また逆も然り、という型である。これを使うと、
// Type '{ a: number; b: number; c: number; }' is not assignable to type 'A'.
// Type '{ a: number; b: number; c: number; }' is not assignable to type '{ a: number; } & { b: never; c: number; }'.
// Type '{ a: number; b: number; c: number; }' is not assignable to type '{ b: never; c: number; }'.
// Types of property 'b' are incompatible.
// Type 'number' is not assignable to type 'never'.
const a: A = {
a: 8,
b: 8,
c: 8
}
これをエラーにできる

このとき、a
プロパティを削除したいとする。
普通はこれを
type B = Omit<A, 'a'>
と書く。しかしこれではBが
type B = {
b: number;
c: number;
}
となってしまい、結果的に、
// No Error!
const b: B = {
b: 8,
c: 8
}
これが許されてしまう

これはOmitの型が
type Omit<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; }
で表現されていることに由来する。取り除きたいプロパティ名を与えると、それを除いたkeyのunionを作り、そのプロパティのみで構築されるオブジェクト型を再構築している。このときにAの型を作る際に使った &
や |
の表現が保持できない。なぜならシンプルに T[P]
という型で表現するため、プロパティ間の依存が取り除かれてしまうからである。

以下解決思案中 ↓

解決策があった
type A = {
a: number
} & (
{
b: number
c?: never
} | {
b?: never
c: number
}
)
type MyOmit<T, K extends keyof T> = {
[Property in keyof T as Exclude<Property, K>]: T[Property];
};
type B = MyOmit<A, 'a'>
// Type '{ b: number; c: number; }' is not assignable to type 'B'.
// Type '{ b: number; c: number; }' is not assignable to type 'OmitX<{ a: number; } & { b?: undefined; c: number; }, "a">'.
// Types of property 'b' are incompatible.
// Type 'number' is not assignable to type 'undefined'
const b: B = {
b: 8,
c: 8,
}