Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【TSKaigi2026登壇資料】決定論的な型チェックへ Go 製コンパイラによる10倍速の裏...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

【TSKaigi2026登壇資料】決定論的な型チェックへ Go 製コンパイラによる10倍速の裏側で stableTypeOrdering から見える並列化への挑戦

More Decks by ディップ株式会社

Transcript

  1. 目次 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
  2. Project Corsa とは 概要 TypeScript コンパイラを Go へ移植 する 公式プロジェクト

    (2025 年 Anders Hejlsberg 発表) ビルド時間 約 10 倍 高速化、エディタ起 動・メモリ使用量も 大幅改善 バージョンの位置づけ TypeScript 7.0 としてリリース予定 その直前、最後の JavaScript ベース版 が今日の主役の TypeScript 6.0 Copyright dip.inc All Rights Reserved. 5 / 33
  3. TypeScript 6.0 で導入された新しいコンパイルフラグ。7.0 への移行をスムーズにするた めの仕掛け の一つ。 進め方 本日の主題 --stableTypeOrdering TypeScript

    6.0 の振る舞い をソースから読み解く 1 並列化 × type ID で何が起きるか を考える 2 --stableTypeOrdering の正体 をソースから読み解く 3 Copyright dip.inc All Rights Reserved. 6 / 33
  4. 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
  5. 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
  6. 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
  7. 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
  8. 逆転現象の正体 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
  9. シングルスレッドだから安定していた いまの TypeScript (JS 版) JavaScript はシングルスレッド 型が作られる順序は 毎回同じ だから

    ID も毎回同じ → 出力も安定 TypeScript 7.0 (Go 版) 軽量な並行モデル (goroutine) 既存 TS 実装から ほぼ行単位で移植 で きる構造 複数のファイルを 並列にチェック Copyright dip.inc All Rights Reserved. 15 / 33
  10. その結果 並列化 × type ID で起きること どの goroutine が先に ID

    を取るかは 実行順序が非決定的 実行のたびに 同じ型に違う ID が振られる 結果として .d.ts の出力順が 毎回変わる ビルドの 非決定性: 同じソースから違う成果物 公開済み .d.ts に意味のない diff が出続ける Copyright dip.inc All Rights Reserved. 16 / 33
  11. 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
  12. compareTypes : コンテンツベースの総合比較 TypeScript 7.0 で新しく追加された関数 任意の 2 つの型を受け取って、どちらが先かを決める Union

    構成要素の並べ替えなど、出力順に関わる場面で広く使用 採番順ではなく 型そのものの「内容」 を見て決定論的に並び替える Copyright dip.inc All Rights Reserved. 19 / 33
  13. 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
  14. // 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
  15. 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
  16. // 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
  17. TypeScript 7.0 で採用される並び順を、ひと足先に 6.0 で体験できる。事前に有効化す れば、7.0 移行時に並びが突然変わる事故を防げる。 フラグの正体 true のとき

    キーセレクタ: identity (= 型そのもの) 比較関数: compareTypes → コンテンツベース のソート false のとき キーセレクタ: getTypeId 比較関数: compareValues → 従来通りの ID ベース のソート Copyright dip.inc All Rights Reserved. 25 / 33
  18. 型エラーは 明示的な型注釈 で潰す 注意点と対処 コスト 最大 25% のチェック速度低下 (コンテンツソート +

    6.0 は並列化なし) 公式: 「常時使うことは推奨しない」 → 一時利用 前提 対処 Copyright dip.inc All Rights Reserved. 27 / 33
  19. まとめ: 今日の 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
  20. 参考文献: ドキュメント 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
  21. 参考文献: 型定義 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