Share
Embed
【TSKaigi2026登壇資料】決定論的な型チェックへ Go 製コンパイラによる10倍速の裏側で stableTypeOrdering から見える並列化への挑戦
Transcript
33 slides
-
1 / 33 決定論的な型チェックへ Go 製コンパイラによる10倍速の裏側で --stableTypeOrdering から見える並列化への挑戦 TSKaigi 2026
Copyright dip.inc All Rights Reserved. -
自己紹介 えーたろー ディップ株式会社 / バックエンド・SRE X: @komugi8448 最近のハマり事: たべること Copyright
dip.inc All Rights Reserved. 2 / 33 -
目次 Project Corsa と TypeScript 6.0 / 7.0 1 .d.ts
の順序問題 2 コンパイラ内部を覗く 3 並列化下の type ID 採番 4 --stableTypeOrdering と compareTypes 5 使うときの注意と移行手順 6 まとめ 7 Copyright dip.inc All Rights Reserved. 3 / 33 -
Project Corsa とは 概要 TypeScript コンパイラを Go へ移植 する 公式プロジェクト
(2025 年 Anders Hejlsberg 発表) ビルド時間 約 10 倍 高速化、エディタ起 動・メモリ使用量も 大幅改善 バージョンの位置づけ TypeScript 7.0 としてリリース予定 その直前、最後の JavaScript ベース版 が今日の主役の TypeScript 6.0 Copyright dip.inc All Rights Reserved. 5 / 33 -
TypeScript 6.0 で導入された新しいコンパイルフラグ。7.0 への移行をスムーズにするた めの仕掛け の一つ。 進め方 本日の主題 --stableTypeOrdering TypeScript
6.0 の振る舞い をソースから読み解く 1 並列化 × type ID で何が起きるか を考える 2 --stableTypeOrdering の正体 をソースから読み解く 3 Copyright dip.inc All Rights Reserved. 6 / 33 -
export function foo(condition: boolean) { return condition ? 100 :
500; } tsc --declaration --emitDeclarationOnly で型定義を生成 export declare function foo(condition: boolean): 100 | 500; 既存コンパイラの特徴 Copyright dip.inc All Rights Reserved. 8 / 33 -
const x = 500; // foo とは何の関係もない宣⾔ export function foo(condition:
boolean) { return condition ? 100 : 500; } export declare function foo(condition: boolean): 500 | 100; Union の順序が逆転 const を一行追加してみる Copyright dip.inc All Rights Reserved. 9 / 33 -
checker.ts より: function createType(flags: TypeFlags): Type { const result =
new Type(checker, flags); typeCount++; result.id = typeCount; // ... } createType : 型を作るたびに ID を採番 新しい型を作るたびに typeCount をインクリメント → その値をそのまま型の ID に 生成順 = ID の昇順 Copyright dip.inc All Rights Reserved. 11 / 33 -
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { // ...
'never' 判定や flag 集計などは省略 ... const index = binarySearch(typeSet, type, getTypeId, compareValues); if (index < 0) { typeSet.splice(~index, 0, type); } return includes; } addTypeToUnion : Union に要素を追加する binarySearch = 二分探索 (O(log n) で挿入位置を発見) 比較キー: getTypeId / 比較関数: compareValues つまり「type ID を取り出して、数値として比較」 Copyright dip.inc All Rights Reserved. 12 / 33 -
逆転現象の正体 const x = 500; を一行追加すると その行を処理する過程で 500 のリテラル型が先に生成され ID
を先取り 結果、リテラル型 100 と 500 の ID の大小関係が逆転 ( ID(500) < ID(100) ) ID 昇順ソートの Union の並びも逆転 ( 100 | 500 → 500 | 100 ) Copyright dip.inc All Rights Reserved. 13 / 33 -
シングルスレッドだから安定していた いまの TypeScript (JS 版) JavaScript はシングルスレッド 型が作られる順序は 毎回同じ だから
ID も毎回同じ → 出力も安定 TypeScript 7.0 (Go 版) 軽量な並行モデル (goroutine) 既存 TS 実装から ほぼ行単位で移植 で きる構造 複数のファイルを 並列にチェック Copyright dip.inc All Rights Reserved. 15 / 33 -
その結果 並列化 × type ID で起きること どの goroutine が先に ID
を取るかは 実行順序が非決定的 実行のたびに 同じ型に違う ID が振られる 結果として .d.ts の出力順が 毎回変わる ビルドの 非決定性: 同じソースから違う成果物 公開済み .d.ts に意味のない diff が出続ける Copyright dip.inc All Rights Reserved. 16 / 33 -
const index = stableTypeOrdering ? binarySearch(typeSet, type, identity, compareTypes) //
内容ベース : (len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues)); // ID ベース if (index < 0) { typeSet.splice(~index, 0, type); } addTypeToUnion 内のフラグ分岐 Copyright dip.inc All Rights Reserved. 18 / 33 -
compareTypes : コンテンツベースの総合比較 TypeScript 7.0 で新しく追加された関数 任意の 2 つの型を受け取って、どちらが先かを決める Union
構成要素の並べ替えなど、出力順に関わる場面で広く使用 採番順ではなく 型そのものの「内容」 を見て決定論的に並び替える Copyright dip.inc All Rights Reserved. 19 / 33 -
複数のキーを優先度の高い順に見ていって、決着がついたところで止まる 優先度 段階的なキー比較 型のフラグ (種類) 1 型の名前 2 型ごとの固有情報 3
(最後の手段) type ID 4 Copyright dip.inc All Rights Reserved. 20 / 33 -
function compareTypes(t1, t2): number { if (t1 === t2) return
0; if (t1 === undefined) return -1; if (t2 === undefined) return 1; // First sort in order of increasing type flags values. let c = getSortOrderFlags(t1) - getSortOrderFlags(t2); if (c !== 0) return c; // 以降、キー 2 以降の⽐較が続く } TypeFlags.String / Object / Union … の数値を引き算。差がついたら即 return 。 キー 1: 型のフラグ (種類) Copyright dip.inc All Rights Reserved. 21 / 33 -
// Order named types by name and, in the case
of aliased types, // by alias type arguments. c = compareTypeNames(t1, t2); if (c !== 0) return c; // 以降、キー 3 以降の⽐較が続く キー 2: 型の「名前」 型エイリアスならその名前、クラス / インターフェースならシンボル名 文字列として比較 同じ「型の種類」の中をさらに名前で並べる Copyright dip.inc All Rights Reserved. 22 / 33 -
else if (t1.flags & TypeFlags.Object) { const c = compareSymbols(t1.symbol,
t2.symbol); // 宣⾔位置 / 型引数 // 以降、型引数列など追加の⽐較が続く } else if (t1.flags & TypeFlags.Union) { // 構成要素のリストを再帰的に⽐較 } else if (t1.flags & TypeFlags.StringLiteral) { // リテラルの値そのもので⽐較 } // 他にも Intersection / Enum / NumberLiteral / TypeParameter などのケースが続く それぞれの型の 「中身」 にまで踏み込んで比較。 キー 3: 型ごとの「固有情報」 Copyright dip.inc All Rights Reserved. 23 / 33 -
// Fall back to type IDs. // This results in
type creation order for built-in types. return t1.id - t2.id; 最後の手段: type ID へのフォールバック ここまで差がつかなかったときだけ ID を使う 主に any / unknown など組み込み型を安定化するためのフォールバック ユーザー定義の型同士でここまで落ちてくることはほぼない Copyright dip.inc All Rights Reserved. 24 / 33 -
TypeScript 7.0 で採用される並び順を、ひと足先に 6.0 で体験できる。事前に有効化す れば、7.0 移行時に並びが突然変わる事故を防げる。 フラグの正体 true のとき
キーセレクタ: identity (= 型そのもの) 比較関数: compareTypes → コンテンツベース のソート false のとき キーセレクタ: getTypeId 比較関数: compareValues → 従来通りの ID ベース のソート Copyright dip.inc All Rights Reserved. 25 / 33 -
型エラーは 明示的な型注釈 で潰す 注意点と対処 コスト 最大 25% のチェック速度低下 (コンテンツソート +
6.0 は並列化なし) 公式: 「常時使うことは推奨しない」 → 一時利用 前提 対処 Copyright dip.inc All Rights Reserved. 27 / 33 -
まとめ: 今日の 3 ポイント TypeScript コンパイラは 型ごとに一意な数値の ID で管理し、Union の並び順は採番
順に依存 1 Go 移植版 (TS 7.0) では 並列化により ID 採番順が非決定的 になる 2 これを解決するため compareTypes による コンテンツベースの決定論的な多段キー 比較 に切り替え、TS 6.0 では --stableTypeOrdering フラグで先取り体験できる 3 Copyright dip.inc All Rights Reserved. 29 / 33 -
参考文献: ドキュメント A 10x Faster TypeScript https://devblogs.microsoft.com/typescript/typescript-native-port/ TypeScript 6.0 リリースノート
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html Why Go? https://github.com/microsoft/typescript-go/discussions/411#discussioncomment- 12466988 Copyright dip.inc All Rights Reserved. 30 / 33 -
参考文献: 型定義 publish 関連 TS Handbook: Publishing https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html TS tsconfig:
declaration option https://www.typescriptlang.org/tsconfig/#declaration Copyright dip.inc All Rights Reserved. 32 / 33