feat(mir): Phase 21.7 Step 1-2 - NamingBox decode & Hotfix 7修正

## 実装内容

### Step 1: NamingBox decode関数追加 (naming.rs)
-  `decode_static_method(func_name) -> Option<(box, method, arity)>`
-  `is_static_method_name(func_name) -> bool`
- 対称性: encode ⇔ decode のペア実装で一貫性確保

### Step 2: unified_emitter Hotfix 7修正 (Lines 267-304)
-  StaticCompiler box kind判定追加
-  static box method は receiver 追加をスキップ
-  instance method(RuntimeData/UserDefined)のみ receiver 追加
-  トレース: NYASH_STATIC_METHOD_TRACE=1 でログ出力

## 判定ロジック
```rust
if box_kind == CalleeBoxKind::StaticCompiler {
    // "BoxName.method/arity" 形式か確認
    let func_name = format!("{}.{}/{}", box_name, method, args.len());
    if is_static_method_name(&func_name) {
        // static box method → receiver 追加しない
    }
}
```

## 検証
 Stage-1 テスト: RC=0 (apps/tests/stage1_skip_ws_repro.hako)
 ビルド成功(0 error)

## 次のステップ
- Step 3: methodization実装 (HAKO_MIR_BUILDER_METHODIZE=1)

Co-Authored-By: ChatGPT5 <chatgpt@openai.com>
This commit is contained in:
nyash-codex
2025-11-21 23:52:10 +09:00
parent 74271f3c5b
commit a13f14cea0
11 changed files with 505 additions and 29 deletions

View File

@ -41,5 +41,19 @@ Rollback
- Disable HAKO_MIR_BUILDER_METHODIZE. Revert to Global("Box.method") resolution path (current 21.6 behavior).
Current notes (Phase 25.x bring-up)
- static box 内のローカル呼び出し(例: `Main.main``me._nop()`)が Global 呼び出しのまま落ちるケースを確認。NamingBox`src/mir/naming.rs`)で `main``Main` 正規化は済み。
- 次のステップ: methodization 時に `ensure_static_box_instance` 経由で receiver を付与し、Global 呼び出しを Method 呼び出しに寄せる(本フェーズの実装タスクとして残置)
- NamingBox / static 名 SSOT
- `src/mir/naming.rs` に NamingBox を実装済み。`Main.main` / `main._nop` などの揺れを `"Main.main/0"` 形式に正規化する経路は Rust VM/LLVM/JSON bridge から既に利用中
- VM 側は `normalize_static_global_name` を通して static box 名を一元化するよう更新済み。
- 既知のギャップ
- static box 内のローカル呼び出し(例: `Main.main``me._nop()`)が Global 呼び出しのまま落ちるケースを確認済み。NamingBox で `main``Main` 正規化は済んでいるが、「Method 化receiver 付与」が未完了。
- MeCallPolicy / method_call_handlers 周りで、static box method に対しても一律で receiver`me`を先頭に追加してしまうパスがあり、arity 不一致や miss-call の温床になり得る。
- このフェーズでやること(具体タスク)
1. `src/mir/builder/method_call_handlers.rs` / MeCallPolicy で「static box method かどうか」を判定し、static のときは receiver を追加しないargs はそのまま Global 互換で流す)ガードを入れる。
2. methodization ロジックHAKO_MIR_BUILDER_METHODIZE=1 有効時で、static box 呼びを `ensure_static_box_instance(Box)` 経由の Method 呼び出しに寄せる:
- Global("Main._nop/0") → Method{ receiver = ensure_static_box_instance("Main"), box="Main", method="_nop", arity=0 }。
3. 最小テストを追加:
- `.hako` 側 minimal: `static box Main { static method _nop() { return 0 } }` を呼ぶケース。
- Rust 側: `mir_stage1_static_main_nop_resolves_and_execs`で、MIR verify + VM 実行が methodization ON/OFF の両方で安定することを確認。
4. docs: 本ファイルに「static box 正規化の完了条件」と「NamingBox / ensure_static_box_instance / MeCallPolicy の責務分担」を 1 ページにまとめ、Phase 21.7 の完了ラインを明文化する。

View File

@ -52,6 +52,7 @@
- Stage1 UsingResolver:
- Program(JSON) を入力に using/extern を解決(今は apply_usings=0 でバイパス多め。defs/body の構造はそのまま Rust LoopForm v2 に渡る前提。
- region+next_i 形ループで JSON スキャン・modules_map を決定、prefix 結合するだけのテキスト担当箱。
- using 解決の意味論そのものは `UsingResolveSSOTBox` に委譲するStage1 は「SSOT に渡す JSON/front を整える箱」)。
- Rust bridge (Stage0):
- Program(JSON v0) → json_v0_bridge lowering → LoopForm v2 → MIR → VM/LLVM
- Loop/PHI/SSA の SSOT は Rust 側。Stage1/.hako は「正しい形の Program(JSON) を渡す」責務に徹する。
@ -121,6 +122,22 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
- modules_list 分割ループstart/next_startをそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。
- resolve_for_source 相当で entries ループと modules_map 参照MapBox.has/getを同時に行うケースを `mir_stage1_using_resolver_resolve_with_modules_map_verifies` で固定。
- 残り: なし(現状の 3 ループは all Region 形)
### UsingResolveSSOTBox 境界メモ
- ファイル: `lang/src/using/resolve_ssot_box.hako`
- 責務SSOT 箱):
- IO 禁止。modules_json / using_entries_json / ctx(map) だけを入力に取り、解決結果path/prefix/modules_json_resolvedを返す純粋関数群。
- 主な I/F:
- `resolve(name, ctx)`
- ctx.modules / ctx.using_paths / ctx.cwd から純粋に path を合成MVP では modules の厳密一致+相対ヒントのみ)。
- `resolve_modules(modules_json, using_entries_json, ctx)`
- nyash.toml 相当の modules_json と UsingCollector 出力を突き合わせ、競合解決済み modules_json を返す予定MVP では echo-back
- `resolve_prefix(using_entries_json, modules_json, ctx)`
- using_entries_json と modules_json から prefix 文字列を構成MVP では空文字、Stage1 entry 側で prefix 結合だけを担当)。
- Stage1 UsingResolverBox との分担:
- Stage1 UsingResolverBox は「JSON フロントから using_entries / modules_map を Region+next_i で収集・整形」する箱。
- UsingResolveSSOTBox は「その結果をもとに最終的な modules/prefix を決める唯一の箱SSOT」として、IO なしで名前解決の意味論を持つ。
- pipeline_v2 UsingResolverlang/src/compiler/pipeline_v2/using_resolver_box.hako
- 役割: modules_json 上で alias を解決する stateful helperテキスト収集は entry 側)。
- ループ: `loop(true)` で RegexFlow.find_from を使い key/tail マッチを走査する単一路。continue/backedge の多経路は無く、Region+next_i へのリライトは不要と判断。
@ -132,6 +149,13 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
- 「pos/next_pos が forward するだけの region」で PHI が揺れないこと。
- `using` 収集で early-exit しても merge 後の `pos` が決定的になること。
- いずれも LoopForm v2 経路JSON front→Rustで MirVerifier 緑を確認するスモークとして追加。
- 追加済みテストRust `src/tests/mir_stage1_using_resolver_verify.rs`)でカバーする LoopForm パターン:
- `mir_stage1_using_resolver_modules_map_early_exit_verifies`: Region+next_i + breakname=="" で早期終了)
- `mir_stage1_using_resolver_modules_map_continue_and_break_verifies`: Region+next_i + continue/break 混在("" を skip, "STOP" で break
- `mir_stage1_using_resolver_resolve_with_modules_map_verifies`: entries ループmodules_map 参照を同時に行う本線形
- `mir_stage1_using_resolver_collect_entries_early_exit_verifies`: JSON スキャンearly-exit sentinel で next_pos を決める Region+next_pos 形
- `mir_stage1_using_resolver_module_map_regionized_verifies`: modules_list 分割start/next_startで MapBox set を行う Region 形
- `mir_stage1_using_resolver_modules_map_continue_break_with_lookup_verifies`: entries ループmodules_map.has/getcontinue/break が同居する本線寄りパターン
- Rust 側テストの取り扱い:
- `src/tests/mir_stage1_using_resolver_verify.rs` に追加した構造テストは cargo test 経路で維持する。v2 quick スモークへの昇格は実行時間とノイズを見つつ後続フェーズで再検討(今回の設計タスクでは据え置き)。
- 観測ログ: MIR dump を残す場合は dev オンリー(`NYASH_LOOPFORM_DEBUG` / `HAKO_LOOP_PHI_TRACE`)に限定し、ログ経路は docs にも記載しておく。