一覧に戻る

[sample]TypeScript の型推論を読み解く

この記事はAI生成によるサンプル記事です。


TypeScript の型推論について、長らく「だいたいうまく動く」程度の理解で済ませてきた。今回は推論器が実際にどう動いているのか、いくつかの典型ケースを観察しながら自分の中で言語化していきたい。

そもそも推論とは何か

型推論は、型注釈が無い場所に対して、文脈から型を導出するプロセスだ。導出は一方向ではなく、双方向(bidirectional)に行われる。引数から関数へ、関数から引数へ、戻り値から本体へ。これは Hindley-Milner からの伝統的な流れを汲んでいる。

const xs = [1, 2, 3];
// xs: number[]
const ys = xs.map((x) => x.toString());
// ys: string[]
//   ↑ map のコンテキストから x: number が伝播し、
//     callback の戻り値型から ys: string[] が決まる

推論の起点はどこか

推論は常に「具体側」から始まる。リテラル、変数、定数。これらが種となり、構造を辿りながら制約を伝播させていく。たとえばリテラル型の wide / narrow の判定も、変数が let なのか const なのかで分岐する。

推論できないケースを観察する

面白いのはむしろ推論が失敗するケースだ。再帰的なジェネリック、相互参照する型エイリアス、ユニオン分配のタイミング。推論器は無限ループを避けるため、深さや幅に上限を設けている。

type Last<T extends any[]> =
  T extends [...infer _, infer L] ? L : never;
// この種のパターンは深さ 50 程度で打ち切られる

ここまでで「推論器は意外と単純なヒューリスティクスの集合」という印象を持った。次回はもう少し踏み込んで、Contextual typing の優先順位について書く。