Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
24
Help us understand the problem. What is going on with this article?
@kgtkr

TypeScriptの型で遊ぶ時、再帰制限を無効化する

初めに

TypeScriptの型システムはチューリング完全なので何でも計算できます。
例えば繰り返し。

type Repeat<T, N extends number, R extends any[] = []> = R["length"] extends N
  ? R
  : Repeat<T, N, [T, ...R]>;

// type A = ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"]
type A = Repeat<"x", 10>;

わーい。ちょっと数増やすか…

// error TS2589: Type instantiation is excessively deep and possibly infinite.
type A = Repeat<"x", 100>;

あれ?(つらい)
再帰制限解除したいですね。しましょう。

注意

  • 当然ですがプロダクトで使うことは想定していません、やめましょう。

バージョンなど

  • typescript@4.1.0-dev.20200902

行番号、コンパイラのコードはこのバージョンの物を引用しています。
tsc --noEmit --lib esnext app.ts でコンパイルしています。

tsc.jsとtsserver.js

npmでTypeScriptをインストールすると node_modules/typescript/lib/tsc.jsnode_modules/typescript/lib/tsserver.js というファイルが作成されます。これがコンパイラと言語サーバーの本体で、バンドラでまとめられたjsファイルです。今回はこれをいじることで再帰制限を無効化します。再帰制限のコードはどちらにも同じものが含まれているので同じ部分を同じように書き換えればいいです。

再帰制限のコードを調べて消す

tsc.jsType instantiation is excessively deep and possibly infinite というエラーメッセージを元に調べると Type_instantiation_is_excessively_deep_and_possibly_infinite という変数が見つかります。この変数を使っているエラー報告は以下の2つです。

// tsc.js 42795行目
if (constraintDepth >= 50) {
    error(currentNode, ts.Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
    nonTerminating = true;
    return t.immediateBaseConstraint = noConstraintType;
}

// tsc.js 46210行目
if (instantiationDepth === 50 || instantiationCount >= 5000000) {
    error(currentNode, ts.Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
    return errorType;
}

constraintDepth はmapped typeとindex access typesを制約に使ったときに発生する無限再帰を防止するためのもののようなので今回は関係なさそうです。Fix infinite constraints #26558

instantiationDepth は型関数をインスタンス化する時の再帰の深さを制限するための物のようです。試しに instantiationDepth === 10 に書き換えると Repeat<"x", 6> でエラーになります。

instantiationCount は深さではなくインスタンス化の回数を制限していますね。例えば上の Repeat を以下のように書き換えると instantiationDepth は変わりませんが instantiationCount は増えます。(console.log すれば分かります)

type Repeat<T, N extends number, R extends any[] = []> = R["length"] extends N
  ? R
  : Repeat<[T, T, T, T, T, T], N, [T, ...R]>;

これで再帰制限チェックコードが分かりました。消してしまいましょう。テキストファイルなのでsedで簡単に消せます。

$ sed -i '' 's/instantiationDepth === 50 || instantiationCount >= 5000000/false/' 'node_modules/typescript/lib/tsc.js'
$ sed -i '' 's/instantiationDepth === 50 || instantiationCount >= 5000000/false/' 'node_modules/typescript/lib/tsserver.js'

結果など

やったね
image.png

最近話題のTemplate string typesと組み合わせれば既存のTSの型レベルbrainfuckインタプリタにlexerとコードポイント変換処理を追加するだけでbrainfuckのhello world!も実行できます。

image.png

24
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kgtkr

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
24
Help us understand the problem. What is going on with this article?