Architecture
この文書はコードベースの責務分離と、解析結果が CLI / LSP / playground / test を通る経路を示す。 個別の最適化履歴や一時的な調査結果は記録しない。
解析の流れ
Ruby / RBS / RBI / project files
↓
Prism parse + annotation / project facts
↓
FileAnalysisSnapshot / FileFacts
↓
WorkspaceState + typed dependency graph
↓
registry / inference / query
↓
RBS render / diagnostics / LSP displayCLI、LSP、MCP、WASM / playground、scenario test は入力の与え方と解決 profile が違うだけで、意味論の中心は WorkspaceState と query backend を共有する。単一ファイルを完全解決して表示する入口は analysis::analyze_source_for_display に集約し、LSP、playground、詳細 CLI、通常の Ruby scenario は同じ snapshot / registry / query 経路を使う。
source method の戻り値に method 参照が残り、block receiver にも未解決 method 参照がある場合は、 receiver の解決後に該当する method body だけを backward propagation で再推論する。戻り値または receiver 候補が ivar 参照だけなら再推論を起動しない。receiver 候補は最初の一件をインライン保持し、 重複を複製前に除外する。再推論対象の解決判定では訪問集合を再利用する。receiver が unresolved のままなら 再走査せず、block parameter も推測で具体化しない。
主要コンポーネント
| 層・ファイル | 責務 |
|---|---|
analysis.rs / parser.rs | Prism 解析、annotation 抽出、共通 snapshot 作成、表示整形 |
inference/ | Ruby の式・method・receiver・block・flow・DSL の推論 |
inference/plugins/ | gem / framework ごとの DSL 展開。Plugin / PluginManifest を 1 plugin 1 file で登録 |
registry.rs / registry/ | class、method、constant、ivar、mixin、call site と型宣言の索引 |
types.rs | Type と未解決参照の表現、union / generic / complexity の制御 |
query.rs | snapshot と registry に対する Hover、completion、definition などの query |
workspace_state.rs | snapshot、export fingerprint、dirty state、workspace registry の管理 |
dep_graph.rs | superclass / mixin / method call など種別付き依存と差分伝播 |
rbs/ | inline RBS、.rbs、stdlib の import / lazy load / render |
sorbet/ | sig、.rbi、Sorbet comment の実験的な import |
rails/ | project 検出、schema / routes / inflector、Rails 共通情報 |
diagnostics.rs | missing method、unresolved constant、argument / return type mismatch などの判定 |
lsp.rs | LSP protocol、document / file cache、CodeLens、Hover、refresh |
mcp.rs | stdio MCP transport と source-position inference tool |
main.rs | CLI 入力展開、batch projection、RBS / diagnostics の出力 |
Sorbet sig { ... } のブロック本体は型 DSL として扱い、通常コードとして推論しない。ブロック内の returns / void / params を enclosing class のメソッド呼び出しとして診断しない(実行時の self は T::Private::Methods::DeclBuilder であるため)。
Semantic backend
WorkspaceState
ファイルごとの FileAnalysisSnapshot、export fingerprint、依存 edge、workspace registry を 保持する。export が変わらない body-only edit は依存先を再計算せず、変更した symbol から reverse edge へ dirty を伝播する。
解決 profile は次の二つだけに分ける。
Batch: CLI の一括解析。targets と context をまとめて projection し、決定的に render する。Interactive: LSP の長寿命 workspace。open file の最新 snapshot と dirty state を使い、必要な query だけを再評価する。
scenario test は小さな workspace を case ごとに作り、同じ backend の projection を検証する。 表示用の hover / definition 索引は意味解析後の一時 registry で収集し、遅延ロードした外部型や 探索用の事実が意味解析結果へ混ざらないようにする。入口ごとに別の型解決を追加しない。
知識源の合成
Ruby source、RBS、RBI、schema、plugin の知識は同じ registry に合成する。優先順位は 「実体のある source / 宣言を保ち、未解決の空 stub で権威ある定義を隠さない」ことを基本とする。 メソッドの型情報を選択するときは、inline RBS comment(#:)、Sorbet sig、.rbs、.rbi、 Ruby code inference の順に優先する。これは query / projection の型情報選択であり、Ruby の定義・参照から 作る依存 graph の優先順ではない。合成規則を変更したときは、該当する registry unit test と scenario を 同じ変更で更新する。
ユーザー定義クラスでも superclass が未設定なら、外部宣言の superclass を暗黙の継承として補完する。 Ruby source に明示された class X < Y は外部宣言で上書きしない。
framework DSL は library-scoped に有効化し、plugin のフックは PluginCx を通す。個別 repository の runtime DSL 登録を解析コアへ持ち込まず、動的な API は外部 RBS / RBI を入力する。block の self を runtime semantics から補う必要がある場合も plugin の block-self hook を通し、source / RBS に明示された self を優先して、未解決時だけ plugin の補完を適用する。
Ruby の ancestor order
ClassData.mixins は merge と render の決定性を保つため、ソース上の適用順で保持する。 Ruby の method lookup ではその配列を mixin 種別ごとに後ろから探索し、後から適用した include / prepend / extend を先に解決する。extend はクラスオブジェクト側だけに 現れ、インスタンス側の ancestors には含めない。
静的に解決できる mixin edge には、Ruby が実行時に呼ぶ included / extended / prepended callback の call site も対応付ける。callback の第一引数には mixin の適用先を singleton(...) として渡し、既存の call site ベースの引数型推論で同じ hook の複数の適用先を union にまとめる。適用先や hook の定義を解決できない場合は、推測で補わず untyped に縮退する。
静的に class / module、mixin、superclass の全 edge を確認でき、かつ上限内に収まる Module#ancestors だけは探索順の Tuple として返す。未解決 edge、循環、深すぎる chain では Array[Module] へ縮退し、推測した順序を型として固定しない。
外部型
stdlib RBS と project の RBS / RBI は必要な file / class を lazy に読み込む。parse 結果は 共有可能な形に変換し、file 固有の推論状態と混ぜない。外部定義の変更は LSP の reload で 関連 cache を無効化する。
CLI batch
通常の RBS 出力は指定された Ruby ファイルを解析する。--diagnostics では診断対象と workspace context を分ける。
- targets: CLI 引数から展開したファイル。診断と詳細 snapshot を保持する。
- context: targets 外の Ruby ファイル。定義・superclass・mixin・定数などの骨格だけを読む。
- チャンク単位で並列解析し、
workspace_state::BatchProjectionBuilderでチャンクが出来上がるたび 即時に registry へ merge する(全ファイルの snapshot を同時に保持しない)。順序はチャンク順、 チャンク内はファイル順を保つ。Batchresolution は全ファイル投入後に一度だけ行う。 - RBS 出力ではソースもチャンク単位で読み、merge 後に破棄する。preload の並列読み込みは DSL 検出だけを行い本文を残さない。
--diagnosticsは message 生成が target の本文を再度 使うため、targets のソースだけ保持する。 - diagnostics は targets にだけ出力する。
この分離で、診断のために workspace 全体の詳細 body / call site を常駐させない。
LSP
lsp.rs は protocol と表示を担当し、意味論は query.rs / WorkspaceState を使う。
initialize、TCP 起動、full / incrementaldidChange、watched file 更新を受け付ける。- Hover、CodeLens、definition、typeDefinition、completion、diagnostics は共通の query 経路を使う。
- open document は
didOpen/didChangeで snapshot を更新し、request 時に全ファイルを読み直さない。 - 初回 workspace scan が終わるまで、未確定の missing method / constant を早期に報告しない。
- refresh は変更をまとめ、保存されていない入力でも CodeLens / Hover が最新 snapshot を見る。
didChangeの diagnostics 発行は debounce する(CodeLens refresh の 75ms と同じ形。document ごとの generation を持ち、新しい change が来た時点で pending を捨てる)。didOpenと scan 完了 後の再発行は即時。facts 更新(start_document_cache_update_if_needed)は非同期で即時に走る。
method hover の signature は、scan で保存した receiver / result 型を起点に、呼び出し位置に対応する 引数型(splat / forwarding / **kwargs は確定部分だけ)と block の有無・shape で RBS overload を 絞り込む。method の generic は引数、block の戻り値、呼び出し結果を RBS 型構造に沿って再帰的に unify し、Array / Hash / tuple / record / union / intersection などのネストした型引数も具体化 する。block の入力型は、receiver の型変数が結果にも残る場合は literal の精度を保ち、block の処理で 結果が widening された場合は dispatch と同じ広い型を表示する。 hover 専用の block shape 絞り込みと implicit-self の追加 generic block 解決は、hover snapshot を記録する Record profile だけで有効にする。既存の receiver 付き block 推論は通常解析にも必要なため維持し、 表示用の推論を広げても、CLI batch / coverage の facts-only profile の RBS 結果と基準値を変えない。 引数型と block の戻り値は既存の scan site を共有して hover snapshot に複製せず、scan で得た戻り値を 再計算しないため、通常の hover は候補数と型構造に比例する bounded な query になる。候補を一意に 確定できない場合は情報を捨てず、元の overload 群を表示する。Playground は Ruby / RBS の編集ごとに 新しい registry を構築するため、ユーザーが入力した RBS の alias / generic / overload も次の hover に 反映される。
TypeProf VSCode 拡張との互換性は protocol boundary に限る。推論結果の parity 表は維持しない。
MCP
mcp.rs は MCP stdio transport の境界を担当し、analysis::hover_at_with_analysis_options を呼んで LSP と同じ query backend から位置指定の型情報を返す。現在は tool caller が渡した Ruby source と 同梱 stdlib RBS だけを使い、workspace のファイル走査や project 固有 RBS の読み込みはしない。 詳細は mcp.md を参照する。
有界性と拡張点
- 型の深さ、union の要素数、式木の fuel、collection shape の成長に上限を設ける。
- 上限に達した場合は panic / hang / OOM を避けて
untypedへ縮退する。 - 名前だけから戻り値を補う恒久テーブルは少数箇所に集約し、追加前に RBS / plugin で表現できないか確認する。
- framework DSL は中央の巨大な if-chain ではなく plugin registry に追加する。
- 個別リポジトリの動的 DSL 登録を解析コアへ持ち込まない。
性能の基準値は performance.md、未完了の設計作業は roadmap.md に置く。