tags : TypeScript
TypeScript 型制約テクニック: 「最低限含む型」を保証する _proof
背景
- 通常のジェネリクス制約 `X extends AB` は
- X が AB に代入可能な型(= AB 以下の型)しか許さない
- AB より広い型(例: AB | C)は許されない
- 「X が AB を最低限含む(= AB ⊆ X)」という条件は直接は書けない
解決策
-
ダミー引数 `_proof` を追加して、型条件を満たさない場合にコンパイルエラーにする
-
型条件:
_proof: AB extends X ? [] : ["X must include A & B"]- `AB extends X` が true → 型は `[]` になり、引数を渡さなくてよい
- false → 型は要素1つの配列になり、呼び出し時に渡せないため型エラー
実装例
type A = { type: 'A'; n: number };
type B = { type: 'B'; s: string };
type AB = A | B;
function handle<X>(
value: X,
..._proof: AB extends X ? [] : ["X must include A & B"]
) {
console.log(value);
}
// OK
handle<AB>({ type: 'A', n: 1 });
handle<AB | { type: 'C'; flag: boolean }>({ type: 'C', flag: true });
// NG: AB を含まない
handle<{ type: 'C'; flag: boolean }>({ type: 'C', flag: true });
// → 型エラー: X must include A & Bバリエーション
- 可変長引数 `…_proof` の代わりに通常引数でも可能
function handle<X>( value: X, _proof?: AB extends X ? undefined : ["X must include A & B"] ) { /* ... */ } - 可変長にする理由:
- `[]` で「何も渡さない」ことを自然に表現できる
- 条件が false のとき、即配列型不一致でエラーにできる
利用シーン
- 「X は最低限このバリアント群を含んでいる」ことを保証したいとき
- ts-pattern 等のパターンマッチで必須ケースを型安全に処理したいとき
- Effect/Schema 等で decode 後の型を保証する用途