できない.dev

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

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

#tsc#types#recursive-type#conditional-type#instantiation

要約

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 = [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 系で素直に通るケースは少なくない。

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