🎯 箱理論の実践: 「境界を作る」原則による構造レベル分離 ## 問題 - StageBArgsBox.resolve_src内のargs.get(i)が Stage1UsingResolverBox.getに化ける(静的Box名混入) - 未定義ValueIdエラー発生(receiver定義なし) ## 解決策(構造ガード) ✅ CalleeBoxKind enum追加 - StaticCompiler: Stage-B/Stage-1コンパイラBox - RuntimeData: MapBox/ArrayBox等ランタイムBox - UserDefined: ユーザー定義Box ✅ classify_box_kind(): Box名から種別判定 - 静的Box群を明示的に列挙(1箇所に集約) - ランタイムBox群を明示的に列挙 - 将来の拡張も容易 ✅ apply_static_runtime_guard(): 混線検出・正規化 - me-call判定(receiver型==box_name → 静的降下に委ねる) - 真の混線検出(receiver型≠box_name → 正規化) - トレースログで可視化 ## 効果 - 修正前: Invalid value ValueId(150/187) - 修正後: Unknown method 'is_space' (別issue、StringBox実装不足) - → 静的Box名混入問題を根絶! ## 箱理論原則 - ✅ 境界を作る: Static/Runtime/UserDefinedを構造的に分離 - ✅ Fail-Fast: フォールバックより明示的エラー - ✅ 箱にする: CalleeBoxKindでBox種類を1箇所に集約 ## ファイル - src/mir/definitions/call_unified.rs: CalleeBoxKind enum - src/mir/builder/calls/call_unified.rs: classify_box_kind() - src/mir/builder/calls/emit.rs: apply_static_runtime_guard() - docs/development/roadmap/phases/phase-25.1d/README.md: 箱化メモ更新 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Phase 25.1d — Rust MIR SSA / PHI Smokes
Status: planning(構造バグ切り出しフェーズ・挙動は変えない/Rust側のみ)
ゴール
- Rust MIR builder(
MirBuilder+LoopBuilder+ IfForm)の SSA / PHI 周りのバグを「Rust テスト/スモーク」で淡々と炙り出して潰すフェーズ。 - Stage‑B / Stage‑1 / selfhost で見えている ValueId 未定義問題を、Rust 側の最小ケースに還元してから直す。
- Nyash 側 MirBuilder(.hako 実装)は Phase 25.1c / 25.1e 以降に扱い、まずは Rust 層の PHI 不整合を止血する。
方針
- 新機能追加ではなく テスト+バグ修正のみ。
- 1バグ1テストの原則で、「再現用 Hako もしくは AST 構築 → MirCompiler → MirVerifier」のパターンを増やしていく。
- 既に報告されている Undefined Value / non‑dominating use / Phi 不足を、そのまま Rust テストケースに落とし込む。
タスク粒度(やることリスト)
-
Stage‑B 最小ハーネスの Rust テスト化
- 既存:
lang/src/compiler/tests/stageb_min_sample.hako+tools/test_stageb_min.sh。 - やること:
- Rust 側に小さなテストを追加(例:
src/tests/stageb_min_mir_verify.rs):Hako→AST→MirCompiler::compile(ast)→MirVerifier::verify_module。- 期待: Stage‑B 最小サンプルのみを対象に Undefined Value が 0 件であること。
- Rust 側に小さなテストを追加(例:
- 目的: shell スクリプトに依存せず、
cargo testベースで Stage‑B 由来の MIR を検証できる足場を作る。
- 既存:
-
単一関数向け PHI スモークの追加
- 対象関数(Rust 側で直接 AST を組む/Hako を読む):
TestArgs.process(args)型:if args != null { local n = args.length(); loop(i < n) { ... } }TestNested.complex(data)型: if + nested loop + method call。
- やること:
- 簡単な Hako を
tests/mir_phi_*ディレクトリかsrc/tests/*に置き、MirCompiler でコンパイルして verifier を通すテストを書く。 - ここでは Stage‑B を通さず、直接 Rust MirBuilder に食わせて PHI / recv の挙動を見る。
- 簡単な Hako を
- 対象関数(Rust 側で直接 AST を組む/Hako を読む):
-
LoopBuilder / IfForm の PHI 不整合の切り出し
- すでに verifier が報告している場所:
JsonScanBox.seek_array_end/2の non‑dominating use。Stage1UsingResolverBox._collect_using_entries/1/resolve_for_source/1の Phi 不足。ParserBox.parse_program2/1の merge block Phi 不足。
- やること:
- 各関数について「最小に削った MIR 再現ケース」を Rust テストとして切り出し(AST 直書きでもよい)、
MirVerifierが通るように LoopBuilder / IfForm / PHI 挿入コードを修正する。
- 各関数について「最小に削った MIR 再現ケース」を Rust テストとして切り出し(AST 直書きでもよい)、
- ポイント:
- 1 関数ずつ、小さなテスト+小さな修正で前に進める(大量に一気にいじらない)。
- すでに verifier が報告している場所:
-
Stage‑B 関数群の Rust スモーク
compiler_stageb.hakoから抜き出された関数:StageBArgsBox.resolve_src/1StageBBodyExtractorBox.build_body_src/2StageBDriverBox.main/1Stage1UsingResolverBox.resolve_for_source/1(Stage‑1 using 解決)
- やること:
- AST もしくは Hako→AST 変換経由で、これらの関数だけを MirCompiler にかけるテストを用意。
- 各テストで
MirVerifier::verify_functionを呼び、Undefined Value / Phi 不足が無い状態を目標に、Loop/If lowering を順番に修正していく。 - 特に
StageBArgsBox.resolve_src/1については、args.get(i)のような Map/Array に対する.getが unified call 経由で誤ってStage1UsingResolverBox.getの Method callee に化けないこと(box_nameが UnknownBox/MapBox のまま、receiver が常に定義済み ValueId であること)を Rust テストで固定する。
-
Verifier 強化(Method recv / PHI に特化したチェック)
- 追加したいチェック:
MirInstruction::Callでcallee = Method{receiver: Some(r)}のとき、rがその関数内で一度以上dstとして定義されているか。- Merge block で predecessor 定義値をそのまま読む場合に「Phi が必須」な箇所を強制エラーにする。
- これを入れた上で、上記の小さなテストが全部緑になるように MirBuilder 側を直す。
- 追加したいチェック:
箱化メモ(Stage‑B / Stage‑1 の責務分離)
- 観測されたバグ(
StageBArgsBox.resolve_src/1内でStage1UsingResolverBox.getに化ける / 未定義 receiver)が示す通り、「Stage‑B CLI 引数処理」と「Stage‑1 using 解決」が Rust 側の型メタデータで混線している。 - Phase 25.1d 以降の設計メモとして、以下の箱化方針を採用する:
- Stage‑B:
StageBArgsBox: CLI 引数 (args/ env) から純粋な文字列srcを決めるだけの箱(Map/Array などの runtime 依存を最小化)。StageBBodyExtractorBox:src文字列からbox Main { method main(...) { ... } }の本文を抜き出す箱(コメント除去・バランスチェック専任)。StageBDriverBox: 上記 2 箱+ ParserBox / FuncScannerBox を束ねて Program(JSON v0) を出すオーケストレータ。
- Stage‑1:
Stage1UsingResolverBox:[modules]とHAKO_STAGEB_MODULES_LISTのみを入力に、using 展開済みソース文字列を返す箱。- Stage‑B からは「文字列 API(
resolve_for_source(src))」でのみアクセスし、Map/Array/JsonFragBox などの構造体を直接渡さない。
- Stage‑B:
- Rust MirBuilder 側では:
- static box ごとに
BoxCompilationContextを必ず張る(variable_map / value_origin_newbox / value_typesを box 単位で完全分離)。 - ✅ 構造ガード実装済み(2025-11-17):
CalleeBoxKindenum追加:StaticCompiler/RuntimeData/UserDefinedの3種別で Box種類を構造的に分類。classify_box_kind(): Box名から種別を判定(Stage-B/Stage-1コンパイラBox、ランタイムDataBox、ユーザー定義Boxを明示的に列挙)。convert_target_to_callee(): Callee::Method生成時にbox_kindを付与。apply_static_runtime_guard(): 静的Box名とランタイム値の混線を検出・正規化:- box_kind==StaticCompiler かつ receiver型==同一Box名 → me-call判定、静的メソッド降下に委ねる。
- box_kind==StaticCompiler かつ receiver型==異なるランタイムBox → 正規化(MapBox/ArrayBoxなど実際のruntime型に修正)。
- 効果:
StageBArgsBox.resolve_src/1内のargs.get(i)がStage1UsingResolverBox.getに化ける問題を根絶。 - ファイル:
src/mir/definitions/call_unified.rs,src/mir/builder/calls/call_unified.rs,src/mir/builder/calls/emit.rs
- static box ごとに
スコープ外
- Nyash 側 MirBuilder(
lang/src/mir/builder/*.hako)の本格リファクタ。- ここは Phase 25.1c / 25.1e で箱化・モジュール化しつつ直す想定(receiver=0 ハードコード撤去など)。
- 新しい MIR 命令追加や意味論変更。
- 既存の MIR 命令セットの範囲で SSA / PHI の整合性を取る。
まとめ
- Phase 25.1d は「Rust MIR SSA / PHI のスモークを増やしてコツコツ直す」フェーズ。
- やることは単純で、やる量は多い:
- 小さいテストを書く → verifier で赤を出す → LoopBuilder / IfForm / MirBuilder を直す → 緑になるまで繰り返す。
- これにより、Stage‑B / Stage‑1 / selfhost の土台となる Rust MIR 層が安定し、その上に Nyash selfhost 側の MirBuilder を載せやすくする。
- なお、Stage‑B 最小ハーネス(
stageb_min_sample.hako)については、Rust MIR builder 経由の直接 VM / MIR verify は既に緑であり、残っている stack overflow はcompiler_stageb.hako側の Nyash ボックス連鎖に起因するものと考えられる。Rust 層ではemit_unified_call/ BoxCall / legacy 警戒の再入防止フラグと再帰深度カウンタを導入済みであり、以降は Nyash 側に浅い再帰ガードを置いて原因ボックスを特定するフェーズへ引き継ぐ。