できない.dev

TypeScript で「Type instantiation is excessively deep and possibly infinite」が解決できない

再帰条件型の展開が深くなりすぎると tsc は推論を諦めてこのエラーを出す。
再帰の終端条件を明示する、中間結果を type alias に固定する、ユニオンの分配を制御するの 3 手で大半は通る。

公開: 更新:

要約

Type instantiation is excessively deep and possibly infinite. は、tsc が 条件型の再帰展開を深掘りしきれずに諦めた ときに出る。
終端条件付きカウンタを再帰型に挿す、中間型を type で固定する、[T] extends [U] でユニオン分配を抑える、の 3 系統で大半は解消する。
最後の手段として TypeScript を上げて再帰最適化に頼る。

よくある原因

  1. 終端条件の欠落: 再帰条件型 Recur<T>T を縮約せず、同じ形のまま自身を呼び続ける。
  2. マップ型 × 大ユニオン: { [K in keyof T]: ... } をネストしたまま、T が深くて広いユニオンになっている。
  3. ユニオンの自動分配: 条件型は引数がユニオンだと自動で分配される。
    再帰中に分配されると組合せが指数的に膨らむ。
  4. 古い TypeScript: 4.0 前後では再帰条件型の最適化が限定的で、5.x なら通る型でも落ちることがある。

解決策

1. 終端条件付きの再帰

type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8];
type Flatten<T, D extends number = 5> =
  D extends 0
    ? T
    : T extends ReadonlyArray<infer U>
      ? Flatten<U, Depth[D] extends number ? Depth[D] : 0>
      : T;

カウンタ型引数を使って 必ず終わる 再帰にする(条件型の公式ドキュメント(新しいタブで開く))。

2. 中間型を固定する

type Step1<T> = /* 重い計算 */ never;
type Step2<T> = /* 別計算 */ never;
type Result<T> = Step2<Step1<T>>;

ネストを浅くすると tsc が同じ部分式を都度再評価しなくなる。

3. ユニオン分配を抑制する

type IsExactArray<T> = [T] extends [readonly unknown[]] ? true : false;

[T] extends [U] のタプル包み形にするとユニオンの分配が止まり、再帰の組合せ数が一気に減る。

4. TypeScript を更新する

npm install -D typescript@latest

TypeScript Performance Wiki(新しいタブで開く) でも、再帰型の最適化はバージョンごとに改善が続いている。
コード側で組合せを減らすのが筋だが、5.x 系で素直に通るケースは少なくない。

実行例

実際に上記の手順を node:20 環境(TypeScript 5.9.3)で動かすと、終端条件を 500 に設定した型では TS2589 が発生して終了コード 2 で失敗し、終端を 5 に収めた場合は終了コード 0 で通ることが確認できる。

Version 5.9.3
$ npx tsc --noEmit bad.ts
bad.ts(3,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tsc の終了コード: 2(2 = TS2589 Type instantiation is excessively deep)
$ npx tsc --noEmit good.ts
tsc の終了コード: 0(0 = 展開が終端に届き通る)

— 2026-08-02 時点の出力

この記事は役立ちましたか?