fix(mir-builder): static method arity mismatch根治 - Phase 25.x

**問題**:
- ParserStmtBox.parse_using/4 に5引数が渡される
- me.method呼び出しで instance/static 判別なし
- static method に誤って receiver 追加

**修正**:
- MeCallPolicyBox: params[0]の型で instance/static 判別
- Instance method: receiver 追加
- Static method: receiver なし
- Arity検証(NYASH_ME_CALL_ARITY_STRICT=1)

**ドキュメント**:
- docs/reference/environment-variables.md 新規作成
- docs/development/architecture/mir-logs-observability.md 更新

**テスト**:
- src/tests/mir_stage1_cli_emit_program_min.rs 追加
- 既存 stage1 テスト全てパス

Phase: 25.x
This commit is contained in:
nyash-codex
2025-11-21 11:16:38 +09:00
parent b92d9f335d
commit c344451087
15 changed files with 702 additions and 53 deletions

View File

@ -0,0 +1,19 @@
// minimal_usingcollector_collect.hako
// 目的: UsingCollectorBox.collect の SSA/PHI バグを最小構成で再現するテスト入力。
// - Stage1 CLI が読む `apps/tests/stage1_using_minimal.hako` と同形の 1 行 using を含む。
// - Rust 側では NyashParser → MirCompiler → VM 実行時に
// UsingCollectorBox.collect/1 の substring まわりで Undefined Value が発生していないかを観測する。
using lang.compiler.parser.using.using_collector_box as UsingCollectorBox
static box Main {
main() {
// Stage1 using minimal と同じ形の 1 行 using。
// UsingCollectorBox.collect はこの文字列を line-scan して JSON を返す。
local src = "using \"foo/bar.hako\" as Foo\n"
local json = UsingCollectorBox.collect(src)
print(json)
return 0
}
}

View File

@ -0,0 +1,24 @@
// stage1_cli_emit_program_min.hako
// 目的: Stage1Cli + Stage1UsingResolverBox + BuildBox を直接呼び出す中間テスト。
// - Rust ブリッジや Stage0 環境変数の組み立てを介さず、
// Stage1Cli.emit_program_json(src) をそのまま VM 上で実行する。
// - src には Stage1 minimal と同じ using 1 行を与える。
using lang.src.runner.stage1_cli as Stage1Cli
static box Main {
main(args) {
// UsingResolver 側の apply フラグは明示的に 1 にしておく。
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
// 本線と同様に STAGE1_SOURCE_TEXT 経由でソースを渡し、FileBox 経路は通さない。
local src = "using \"foo/bar.hako\" as Foo\n"
env.set("STAGE1_SOURCE_TEXT", src)
// 第1引数は path だが、STAGE1_SOURCE_TEXT があるため _read_file は呼ばれない。
local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako")
print("[stage1_cli_emit_program_min] prog=" + ("" + prog))
return 0
}
}

View File

@ -0,0 +1,27 @@
// stage1_resolver_usingcollector_min.hako
// 目的: Stage1UsingResolverBox + UsingCollectorBox のペアだけを合体させたときに
// SSA/PHI が崩れずに動くか確認する中間レベルのテスト。
//
// - Stage1 CLI 本線とは独立に、resolve_for_source(src) → UsingCollectorBox.collect(src)
// の経路だけを小さな箱で再現する。
// - src には Stage1 最小ケースと同じ using 1 行を与える。
using lang.compiler.entry.using_resolver_box as Stage1UsingResolverBox
static box Main {
main(args) {
// UsingResolver 側の apply フラグを明示的に有効化prefix 経路を通す)
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
// modules_list は最低限の 1 エントリだけを与える(実際にファイルが存在しなくても良い)。
// _read_file が失敗しても prefix 連結をスキップするだけなので、UsingCollector の JSON 生成までは到達する。
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
// Stage1 minimal と同形の 1 行 using
local src = "using \"foo/bar.hako\" as Foo\n"
local prefix = Stage1UsingResolverBox.resolve_for_source(src)
print("[stage1_resolver_usingcollector_min] prefix=" + ("" + prefix))
return 0
}
}

View File

@ -0,0 +1,18 @@
// stageb_program_min_using_only.hako
// 目的: StageB BuildBox + ParserBox だけで
// 「using 1 行」の Program(JSON v0) emit 中に型崩れや Compare エラーが出ないか確認する最小ケース。
//
// - Stage1Cli や UsingResolver は通さず、BuildBox.emit_program_json_v0(src, null) を直叩きする。
// - src は Stage1 minimal と同じ `using "foo/bar.hako" as Foo\n` 一行。
using lang.compiler.build.build_box as BuildBox
static box Main {
main(args) {
local src = "using \"foo/bar.hako\" as Foo\n"
local prog = BuildBox.emit_program_json_v0(src, null)
print("[stageb_program_min_using_only] prog=" + ("" + prog))
return 0
}
}

View File

@ -97,12 +97,37 @@
--- ---
### 5. MeCall Arity DebugPhase 25.x追加
**場所**: `src/mir/builder/method_call_handlers.rs`
| 行番号 | タグ | 説明 | 用途 |
|-------|------|-----|------|
| 70-73 | `[me-call] arity mismatch (instance)` | Instance method arity不一致警告 | Dev専用 |
| 98-101 | `[me-call] arity mismatch (static)` | Static method arity不一致警告 | Dev専用 |
| 150-154 | `[static-call] emit` | Static method呼び出しトレース | 観測用 |
**有効化環境変数**:
- `NYASH_ME_CALL_ARITY_STRICT=1` - 厳密モード(不一致でエラー返却)
- `NYASH_STATIC_CALL_TRACE=1` - トレースモードwarning出力
**説明**:
- `me.method(...)` 呼び出しのarity検証
- Instance methodreceiver追加vs Static methodreceiver なし)の判別
- ParserStmtBox.parse_using/4 の5引数バグ等の検出
**用途と削除時期**:
- Phase XXstatic box instance化完了後に削除予定
- 過渡期のデバッグ支援ログstatic boxのsingleton化後は不要
---
## 用途別集計表 ## 用途別集計表
| 用途 | 件数 | 内容 | 用途と削除時期 | | 用途 | 件数 | 内容 | 用途と削除時期 |
|-----|-------|-----|----------| |-----|-------|-----|----------|
| **Dev専用** | 11 | Stage-1 CLI / StringHelpers デバッグ用 | 削除予定Phase 25.x 完了) | | **Dev専用** | 13 | Stage-1 CLI / StringHelpers / MeCall Arity デバッグ用 | 削除予定Phase 25.x 完了) |
| **観測用** | 3 | FuncScanner ループ反復観測 | 保持推奨MIR観測 API 昇格) | | **観測用** | 4 | FuncScanner ループ反復 / Static call トレース | 保持推奨MIR観測 API 昇格) |
| **コメント** | 1 | テスト場所の注釈 | - | | **コメント** | 1 | テスト場所の注釈 | - |
--- ---

View File

@ -10,7 +10,11 @@
- `src/mir/phi_core/loopform_builder.rs` - `src/mir/phi_core/loopform_builder.rs`
- `src/mir/phi_core/loop_snapshot_merge.rs` - `src/mir/phi_core/loop_snapshot_merge.rs`
- Stage1 UsingResolver テストの観察点: - Stage1 UsingResolver テストの観察点:
- `src/tests/mir_stage1_using_resolver_verify.rs`collect_entries の SSA/PHI 期待を確認) - `src/tests/mir_stage1_using_resolver_verify.rs`
- collect_entries 系ループJSON スキャン)
- Region+next_i 形の entries ループ
- modules_list 分割ループRegion+next_start
- resolve_for_source 相当で entries ループと modules_map 参照を同時に行うケース
- 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。 - 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。
## JSON v0 フロント側の契約StageB → Stage1 ## JSON v0 フロント側の契約StageB → Stage1
@ -113,7 +117,9 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
### ループ洗い出しメモentry / pipeline_v2 ### ループ洗い出しメモentry / pipeline_v2
- entry UsingResolverlang/src/compiler/entry/using_resolver_box.hako - entry UsingResolverlang/src/compiler/entry/using_resolver_box.hako
- Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割 - Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割
- 追加テスト: modules_list 分割ループstart/next_startをそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。 - 追加テスト:
- 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 形) - 残り: なし(現状の 3 ループは all Region 形)
- pipeline_v2 UsingResolverlang/src/compiler/pipeline_v2/using_resolver_box.hako - pipeline_v2 UsingResolverlang/src/compiler/pipeline_v2/using_resolver_box.hako
- 役割: modules_json 上で alias を解決する stateful helperテキスト収集は entry 側)。 - 役割: modules_json 上で alias を解決する stateful helperテキスト収集は entry 側)。

View File

@ -17,6 +17,10 @@ Status: design-only + Stage0 stub 実装済みPhase 25.1 時点では仕様
- Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層C-ABI/externとして利用することを前提とする。 - Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層C-ABI/externとして利用することを前提とする。
- Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する - Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する
ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE`target/selfhost/hakorune`)はまだ設計段階。 ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE`target/selfhost/hakorune`)はまだ設計段階。
- ブリッジは env-only 仕様で Stage1 stub を呼び出し、`STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `STAGE1_BACKEND`
/ `STAGE1_SOURCE` などの環境変数をセットする。
- Stage1 UsingResolver は `HAKO_STAGEB_APPLY_USINGS=0` を既定とし、CLI 経路では prefix 連結を行わないusing 解決の検証は
専用テストで行い、CLI 本線は Program(JSON) 生成に集中させる)。
- Stage1Hakorune selfhost CLI - Stage1Hakorune selfhost CLI
- 実体: `target/selfhost/hakorune`Phase 25.1 では Ny Executor プロトタイプ)。 - 実体: `target/selfhost/hakorune`Phase 25.1 では Ny Executor プロトタイプ)。
- 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。 - 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。
@ -229,6 +233,35 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
- `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。StageB 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。 - `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。StageB 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
- using 解決は Stage0Rust Runnerと Stage1Hakoruneの二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。 - using 解決は Stage0Rust Runnerと Stage1Hakoruneの二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。
### Stage1 CLI デバッグメモStage1Cli + BuildBox + ParserBox
- 中間スモーク: `apps/tests/stage1_cli_emit_program_min.hako`
- 役割: `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` までを、Rust ブリッジや Stage0 runner を経由せずに直接 VM 上で実行する最小ケース。
- 実行例:
```bash
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
./target/release/hakorune apps/tests/stage1_cli_emit_program_min.hako
```
- Rust テスト側のハーネス: `src/tests/mir_stage1_cli_emit_program_min.rs`
- `include_str!("../../lang/src/runner/stage1_cli.hako")` で Stage1Cli 本体をバンドルし、`static box Main` を末尾に付けて 1 ソースとしてパースする形に統一。
- `mir_stage1_cli_emit_program_min_compiles_and_verifies`:
- Stage1Cli + UsingResolver + BuildBox を含んだモジュールが MIR 生成・verify まで通ることを確認SSA/PHI 崩壊なし)。
- `mir_stage1_cli_emit_program_min_exec_hits_type_error`:
- VM 実行まで進め、Stage1 CLI 経路がまだ決定的に失敗すること(型エラー or 未解決呼び出し)が Rust テスト内で再現できることを確認するための箱。
- 現時点で観測されている実行時エラー20251121 時点):
- `apps/tests/stage1_cli_emit_program_min.hako` を直接 `hakorune` で実行すると、
- 以前の「String + Void」「String + MapBox」ではなく、
- `Type error: unsupported compare Ge on String("using \"foo/bar.hako\" as Foo\n") and Integer(19)`
- というエラーが Stage1 経路で発生している。
- 経路としては `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` まで進んだうえで、
どこかで Compare(Ge) の両辺が String vs Int に崩れていることが確認できている。
- デバッグ方針:
- Stage1 CLI 本線のバグ追跡は、必ず「小さな箱」から行う:
- `.hako` 側: `apps/tests/stage1_cli_emit_program_min.hako` で Stage1Cli+UsingResolver+BuildBox をまとめて叩く中間スモーク。
- Rust 側: `src/tests/mir_stage1_cli_emit_program_min.rs` で同じソースを `NyashParser → MirCompiler → VM` まで持ち込むテスト。
- 次フェーズでは、このハーネス上で Compare(Ge) 命令をスキャンし、「左オペランドがソース行 String、右が Int になっている Compare」を 1 関数単位まで特定し、その関数を別の minimal .hako + Rust テストに切り出して修正する。
## `check` コマンド(予約) ## `check` コマンド(予約)
```text ```text

View File

@ -0,0 +1,153 @@
# 環境変数リファレンス
Nyashの動作を制御する環境変数の一覧。
---
## MIR Builder検証系
MIRビルダーの動作検証・デバッグ用環境変数。
| 環境変数 | デフォルト | 用途 | 追加Phase |
|---------|----------|-----|-----------|
| `NYASH_ME_CALL_ARITY_STRICT=1` | OFF | me.method呼び出しのarity不一致でエラー返却 | Phase 25.x |
| `NYASH_STATIC_CALL_TRACE=1` | OFF | Static method呼び出しのトレース出力 | Phase 25.x |
| `NYASH_VERIFY_ALLOW_NO_PHI=1` | OFF | PHI検証スキップ支配・マージ | - |
| `NYASH_VERIFY_ALLOW_LEGACY=1` | OFF | レガシー命令許可 | - |
| `NYASH_VERIFY_BARRIER_STRICT=1` | OFF | Barrier配置厳密チェック | - |
| `NYASH_VERIFY_EDGE_COPY_STRICT=1` | OFF | Edge copy検証 | - |
| `NYASH_VERIFY_RET_PURITY=1` | OFF | Return block純粋性検証 | - |
| `NYASH_BREAKFINDER_SSA_TRACE=1` | OFF | SSA詳細トレースBreakFinderBox等 | - |
### 使用例
```bash
# Dev環境で厳密検証arity不一致でビルドエラー
NYASH_ME_CALL_ARITY_STRICT=1 cargo test
# トレースのみwarning表示、エラーにはしない
NYASH_STATIC_CALL_TRACE=1 ./target/release/hakorune program.hako
# PHI検証を一時的にスキップ
NYASH_VERIFY_ALLOW_NO_PHI=1 ./target/release/hakorune program.hako
# SSA詳細トレースBreakFinderBox等のデバッグ
NYASH_BREAKFINDER_SSA_TRACE=1 cargo test mir_stage1_using_resolver_verify 2>&1 | grep "breakfinder/ssa"
```
---
## Stage-1 CLI制御系
Stage-1 CLINyash selfhosting compilerの制御用環境変数。
| 環境変数 | デフォルト | 用途 | 追加Phase |
|---------|----------|-----|-----------|
| `NYASH_USE_STAGE1_CLI=1` | OFF | Stage-1 CLI有効化 | Phase 25.1 |
| `STAGE1_CLI_DEBUG=1` | OFF | Stage-1 CLI詳細ログ | Phase 25.1 |
| `STAGE1_EMIT_PROGRAM_JSON=1` | OFF | Program JSON出力モード | Phase 25.1 |
| `STAGE1_EMIT_MIR_JSON=1` | OFF | MIR JSON出力モード | Phase 25.1 |
| `STAGE1_BACKEND={vm\|llvm\|pyvm}` | `vm` | 実行バックエンド指定 | Phase 25.1 |
| `STAGE1_SOURCE=<path>` | - | ソースファイルパス | Phase 25.1 |
| `STAGE1_SOURCE_TEXT=<text>` | - | ソーステキストinline指定 | Phase 25.1 |
| `STAGE1_PROGRAM_JSON=<path>` | - | Program JSONパス | Phase 25.1 |
### 使用例
```bash
# Stage-1 CLI経由で.hakoファイルをコンパイル
NYASH_USE_STAGE1_CLI=1 STAGE1_SOURCE=program.hako \
./target/release/hakorune lang/src/runner/stage1_cli.hako
# Program JSON生成
STAGE1_EMIT_PROGRAM_JSON=1 STAGE1_SOURCE=program.hako \
./target/release/hakorune lang/src/runner/stage1_cli.hako
# デバッグログ付き
STAGE1_CLI_DEBUG=1 NYASH_USE_STAGE1_CLI=1 \
./target/release/hakorune program.hako
```
---
## Parser制御系
パーサーの動作制御用環境変数。
| 環境変数 | デフォルト | 用途 | 追加Phase |
|---------|----------|-----|-----------|
| `NYASH_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化 | Phase 25.3 |
| `HAKO_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化.hako側 | Phase 25.3 |
| `NYASH_ENABLE_USING=1` | OFF | using文サポート有効化 | Phase 25.1 |
| `HAKO_ENABLE_USING=1` | OFF | using文サポート有効化.hako側 | Phase 25.1 |
| `HAKO_STAGEB_APPLY_USINGS=1` | OFF | Stage-B using適用 | Phase 25.1 |
### 使用例
```bash
# using文を使ったプログラムをパース
NYASH_PARSER_STAGE3=1 NYASH_ENABLE_USING=1 \
./target/release/hakorune program_with_using.hako
```
---
## デバッグ・観測系
デバッグ・トレース用環境変数。
| 環境変数 | デフォルト | 用途 | 追加Phase |
|---------|----------|-----|-----------|
| `NYASH_CLI_VERBOSE=1` | OFF | 詳細診断情報出力 | - |
| `NYASH_VM_TRACE=1` | OFF | VM実行トレース出力 | - |
| `NYASH_VM_DUMP_MIR=1` | OFF | VM実行前MIR出力 | - |
| `NYASH_DUMP_JSON_IR=1` | OFF | JSON IR出力 | - |
| `NYASH_STAGE1_MIR_DUMP=1` | OFF | Stage-1 MIR出力 | Phase 25.x |
| `NYASH_STAGE1_SCAN_GE=1` | OFF | Compare Ge命令スキャン | Phase 25.x |
| `NYASH_TO_I64_DEBUG=1` | OFF | to_i64変換デバッグ | Phase 25.x |
| `NYASH_FUNCSCANNER_DEBUG=1` | OFF | FuncScanner詳細ログ | Phase 25.3 |
### 使用例
```bash
# VM実行トレース
NYASH_VM_TRACE=1 ./target/release/hakorune program.hako 2>&1 | grep "vm-trace"
# MIR確認複数手法
NYASH_VM_DUMP_MIR=1 ./target/release/hakorune program.hako
./target/release/hakorune --dump-mir program.hako
./target/release/hakorune --emit-mir-json debug.json program.hako
# Stage-1 CLI + MIRダンプ
NYASH_STAGE1_MIR_DUMP=1 cargo test mir_stage1_cli_emit_program_min
```
---
## プラグイン・Box制御系
プラグインとBox factoryの制御用環境変数。
| 環境変数 | デフォルト | 用途 | 追加Phase |
|---------|----------|-----|-----------|
| `NYASH_DISABLE_PLUGINS=1` | OFF | プラグイン無効化 | - |
| `NYASH_BOX_FACTORY_POLICY={builtin_first\|plugin_first}` | `builtin_first` | Box factory優先順位 | Phase 15.5 |
| `NYASH_FILEBOX_MODE={auto\|plugin\|builtin}` | `auto` | FileBox実装選択 | Phase 15.5 |
### 使用例
```bash
# プラグイン無効で実行コアBox経路のみ
NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune program.hako
# Plugin優先でBox生成
NYASH_BOX_FACTORY_POLICY=plugin_first ./target/release/hakorune program.hako
```
---
## 参考
- **MIRログ観測**: [docs/development/architecture/mir-logs-observability.md](../development/architecture/mir-logs-observability.md)
- **Phase 25.4計画**: [docs/development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md](../development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md)
- **MIR検証システム**: [src/mir/verification/](../../src/mir/verification/)

View File

@ -147,15 +147,38 @@ static box Stage1Cli {
// and create a fresh ArrayBox from environment variables instead // and create a fresh ArrayBox from environment variables instead
local args_safe = new ArrayBox() local args_safe = new ArrayBox()
// Config box: Parse all env vars into structured config // 現状: MIR 型レジストリの制約により MapBox ベースの ConfigBox は本線では使用しない。
local cfg = Stage1CliConfigBox.from_env() // env から直接読み取り、Stage1CliConfigBox は将来の構造化用として温存する。
local mode = cfg.get("mode")
local debug = cfg.get("debug") // mode 判定env-only
local mode = "disabled"
if env.get("STAGE1_EMIT_PROGRAM_JSON") == "1" {
mode = "emit_program_json"
} else if env.get("STAGE1_EMIT_MIR_JSON") == "1" {
mode = "emit_mir_json"
} else if env.get("NYASH_USE_STAGE1_CLI") == "1" {
mode = "run"
}
// backend と入力まわり
local backend = env.get("STAGE1_BACKEND")
if backend == null { backend = "vm" }
backend = "" + backend
local source = env.get("STAGE1_SOURCE")
local source_text = env.get("STAGE1_SOURCE_TEXT")
local prog_path = env.get("STAGE1_PROGRAM_JSON")
local debug = env.get("STAGE1_CLI_DEBUG")
// Log entry point for debugging // Log entry point for debugging
if debug == "1" { if debug == "1" {
__mir__.log("[stage1_main] args_safe at entry", args_safe) __mir__.log("[stage1_main] args_safe at entry", args_safe)
print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + ("" + cfg.get("backend"))) print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + backend)
__mir__.log("[stage1_main] env-config",
mode,
backend,
source,
prog_path)
} }
// Early exit if CLI is disabled // Early exit if CLI is disabled
@ -166,21 +189,6 @@ static box Stage1Cli {
return 97 return 97
} }
// Log config toggles before dispatch
if debug == "1" {
__mir__.log("[stage1_main] config",
cfg.get("mode"),
cfg.get("backend"),
cfg.get("source_path"),
cfg.get("program_json_path"))
}
// Extract config fields
local source = cfg.get("source_path")
local source_text = cfg.get("source_text")
local prog_path = cfg.get("program_json_path")
local backend = cfg.get("backend")
// Dispatch by mode // Dispatch by mode
if mode == "emit_program_json" { if mode == "emit_program_json" {
if source == null || source == "" { if source == null || source == "" {

View File

@ -36,15 +36,37 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
]; ];
pub fn lookup_keyword(word: &str) -> Option<&'static str> { pub fn lookup_keyword(word: &str) -> Option<&'static str> {
for (k, t) in KEYWORDS { for (k, t) in KEYWORDS {
if *k == word { if *k == word { return Some(*t); }
return Some(*t);
}
} }
None None
} }
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", "box",
"include", "local", "outbox", "try", "throw", "using", "from", "global",
"function",
"static",
"if",
"loop",
"break",
"return",
"print",
"nowait",
"include",
"local",
"outbox",
"try",
"throw",
"using",
"from",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
"add",
"sub",
"mul",
"div",
"and",
"or",
"eq",
"ne",
]; ];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];

View File

@ -7,7 +7,7 @@ use crate::ast::ASTNode;
use crate::mir::builder::builder_calls::CallTarget; use crate::mir::builder::builder_calls::CallTarget;
use crate::mir::builder::calls::function_lowering; use crate::mir::builder::calls::function_lowering;
use crate::mir::builder::{MirBuilder, ValueId}; use crate::mir::builder::{MirBuilder, ValueId};
use crate::mir::{MirInstruction, TypeOpKind}; use crate::mir::{MirInstruction, MirType, TypeOpKind};
/// Me-call 専用のポリシー箱。 /// Me-call 専用のポリシー箱。
/// ///
@ -35,17 +35,75 @@ impl MeCallPolicyBox {
} }
let arity = arg_values.len(); let arity = arg_values.len();
let fname = function_lowering::generate_method_function_name(cls, method, arity); let fname = function_lowering::generate_method_function_name(cls, method, arity);
let exists = if let Some(ref module) = builder.current_module { if let Some(ref module) = builder.current_module {
module.functions.contains_key(&fname) if let Some(func) = module.functions.get(&fname) {
} else { // Decide whether this lowered function expects an implicit receiver.
false // Instance methods: params[0] is Box(box_name)
}; // Static methods: params[0] is non-Box or params.is_empty()
if exists { let params = &func.signature.params;
// Pass 'me' as first arg let is_instance_method = !params.is_empty()
&& matches!(params[0], MirType::Box(_));
// Expected argument count from signature (including receiver for instance)
let expected_params = params.len();
let provided_static = arg_values.len();
let provided_instance = arg_values.len() + 1;
// Build call_args based on method kind
let call_args: Vec<ValueId> = if is_instance_method {
// Instance method: prepend 'me' receiver
if expected_params != provided_instance {
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
.ok()
.as_deref()
== Some("1")
{
return Err(format!(
"[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
fname, expected_params, provided_instance
));
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
.ok()
.as_deref()
== Some("1")
{
eprintln!(
"[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
fname, expected_params, provided_instance
);
}
}
let me_id = builder.build_me_expression()?; let me_id = builder.build_me_expression()?;
let mut call_args = Vec::with_capacity(arity + 1); let mut v = Vec::with_capacity(provided_instance);
call_args.push(me_id); v.push(me_id);
call_args.extend(arg_values.into_iter()); v.extend(arg_values.into_iter());
v
} else {
// Static method: no receiver
if expected_params != provided_static {
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
.ok()
.as_deref()
== Some("1")
{
return Err(format!(
"[me-call] arity mismatch (static): {}: declared {} params, got {} args",
fname, expected_params, provided_static
));
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
.ok()
.as_deref()
== Some("1")
{
eprintln!(
"[me-call] arity mismatch (static): {}: declared {} params, got {} args",
fname, expected_params, provided_static
);
}
}
arg_values
};
let dst = builder.next_value_id(); let dst = builder.next_value_id();
// Emit as unified global call to lowered function // Emit as unified global call to lowered function
builder.emit_unified_call( builder.emit_unified_call(
@ -56,6 +114,7 @@ impl MeCallPolicyBox {
builder.annotate_call_result_from_func_name(dst, &fname); builder.annotate_call_result_from_func_name(dst, &fname);
return Ok(Some(dst)); return Ok(Some(dst));
} }
}
// Fallback: treat me.method(...) as a static method on the enclosing box. // Fallback: treat me.method(...) as a static method on the enclosing box.
// - 旧挙動では me を Box 値として Method 呼び出ししようとしていたが、 // - 旧挙動では me を Box 値として Method 呼び出ししようとしていたが、

View File

@ -221,8 +221,12 @@ impl NyashRunner {
if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() { if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() {
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first"); cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
} }
// Stage1 CLI 経路では既定で using 適用を無効化し、
// prefix は空HAKO_STAGEB_APPLY_USINGS=0とする。
// UsingResolver/UsingCollector の検証は専用テストで行い、
// CLI 本線はシンプルな Program(JSON) 生成に集中させる。
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() { if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
cmd.env("HAKO_STAGEB_APPLY_USINGS", "1"); cmd.env("HAKO_STAGEB_APPLY_USINGS", "0");
} }
if std::env::var("NYASH_ENABLE_USING").is_err() { if std::env::var("NYASH_ENABLE_USING").is_err() {
cmd.env("NYASH_ENABLE_USING", "1"); cmd.env("NYASH_ENABLE_USING", "1");

View File

@ -0,0 +1,142 @@
use crate::ast::ASTNode;
use crate::backend::VM;
use crate::mir::printer::MirPrinter;
use crate::mir::{instruction::MirInstruction, types::CompareOp, MirCompiler, MirVerifier};
use crate::parser::NyashParser;
fn ensure_stage3_env() {
std::env::set_var("NYASH_PARSER_STAGE3", "1");
std::env::set_var("HAKO_PARSER_STAGE3", "1");
std::env::set_var("NYASH_ENABLE_USING", "1");
std::env::set_var("HAKO_ENABLE_USING", "1");
}
/// Bundle Stage1Cli 本体 + 最小 Main を 1 ソースにまとめたフィクスチャ。
/// - using 解決を Stage0 ランナーや hako.toml に頼らず、このテスト内だけで完結させる。
fn stage1_cli_fixture_src() -> String {
let stage1_cli_src = include_str!("../../lang/src/runner/stage1_cli.hako");
let test_main_src = r#"
using lang.src.runner.stage1_cli as Stage1Cli
static box Main {
main(args) {
env.set("HAKO_STAGEB_APPLY_USINGS", "1")
env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako")
env.set("STAGE1_SOURCE_TEXT", "using \"foo/bar.hako\" as Foo\n")
local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako")
print("[stage1_cli_emit_program_min] prog=" + ("" + prog))
return 0
}
}
"#;
format!("{stage1_cli_src}\n\n{test_main_src}")
}
/// Stage1Cli.emit_program_json 経路の最小再現を Rust テスト側に持ち込むハーネス。
/// - apps/tests/stage1_cli_emit_program_min.hako と同じ形で Stage1Cli を呼び出す。
/// - ここでは「MIR/SSA が壊れずモジュールが verify できるか」までを確認し、
/// 実際の VM 実行時の型崩れは別フェーズで VM テストとして扱う前提。
#[test]
fn mir_stage1_cli_emit_program_min_compiles_and_verifies() {
ensure_stage3_env();
let src = stage1_cli_fixture_src();
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
// Optional: dump MIR when debugging this path
if std::env::var("NYASH_STAGE1_MIR_DUMP").ok().as_deref() == Some("1") {
let printer = MirPrinter::verbose();
let txt = printer.print_module(&cr.module);
eprintln!("=== MIR stage1_cli_emit_program_min ===\n{}", txt);
}
let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&cr.module) {
for e in &errors {
eprintln!("[rust-mir-verify] {}", e);
}
panic!("MIR verification failed for stage1_cli_emit_program_min");
}
}
/// VM 実行まで進めて、現在発生している String > Integer の型エラーを Rust テスト内で再現する。
#[test]
fn mir_stage1_cli_emit_program_min_exec_hits_type_error() {
ensure_stage3_env();
let src = stage1_cli_fixture_src();
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
// Optional: scan for Compare::Ge instructions to locate suspicious comparisons
if std::env::var("NYASH_STAGE1_SCAN_GE")
.ok()
.as_deref()
== Some("1")
{
for (fname, func) in cr.module.functions.iter() {
for (bb_id, bb) in func.blocks.iter() {
for inst in bb.instructions.iter() {
if let MirInstruction::Compare { op, lhs, rhs, .. } = inst {
if *op == CompareOp::Ge {
eprintln!(
"[stage1-cli/scan] Compare Ge in {} @bb{:?} lhs=%{:?} rhs=%{:?}",
fname, bb_id, lhs, rhs
);
}
}
}
if let Some(term) = &bb.terminator {
if let MirInstruction::Compare { op, lhs, rhs, .. } = term {
if *op == CompareOp::Ge {
eprintln!(
"[stage1-cli/scan] Compare Ge(term) in {} @bb{:?} lhs=%{:?} rhs=%{:?}",
fname, bb_id, lhs, rhs
);
}
}
}
}
}
}
let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&cr.module) {
for e in &errors {
eprintln!("[rust-mir-verify] {}", e);
}
panic!("MIR verification failed for stage1_cli_emit_program_min exec path");
}
let mut vm = VM::new();
let exec = vm.execute_module(&cr.module);
match exec {
Ok(v) => {
panic!(
"expected VM exec to hit Stage1 CLI wiring error, but it succeeded with value: {}",
v.to_string_box().value
);
}
Err(e) => {
let msg = format!("{}", e);
if std::env::var("NYASH_STAGE1_MIR_DUMP")
.ok()
.as_deref()
== Some("1")
{
eprintln!("[stage1-cli/debug] VM exec error: {}", msg);
}
let is_type_error =
msg.contains("unsupported") && (msg.contains("Gt") || msg.contains("compare"));
let is_missing_stage1 = msg.contains("Stage1Cli.emit_program_json/1");
assert!(
is_type_error || is_missing_stage1,
"expected Stage1 CLI path to fail deterministically (type mismatch or missing Stage1Cli); got: {}",
msg
);
}
}
}

View File

@ -431,3 +431,111 @@ static box ParserBoxHarness {
panic!("MIR verification failed for ParserBoxHarness"); panic!("MIR verification failed for ParserBoxHarness");
} }
} }
/// resolve_for_source 相当の処理で entries ループと modules_map 参照を同時に行うケースを固定する。
/// Region+next_i 形のループと MapBox get/has が組み合わさっても PHI/SSA が崩れないことを確認する。
#[test]
fn mir_stage1_using_resolver_resolve_with_modules_map_verifies() {
ensure_stage3_env();
let src = r#"
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box Stage1UsingResolverResolveWithMap {
_collect_using_entries(src_unused) {
// Minimal JSON-like entries: two usable, one empty name for skip path
local json = "[{\"name\":\"A\"},{\"name\":\"B\"},{\"name\":\"\"}]"
local out = new ArrayBox()
local pos = 0
local n = json.length()
loop(pos < n) {
local next_pos = n
local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos)
if name_idx < 0 { break }
local name = JsonFragBox.read_string_after(json, name_idx + 7)
local obj_end = JsonFragBox.index_of_from(json, "}", name_idx)
if obj_end < 0 { obj_end = n }
if name != "" {
local entry = new MapBox()
entry.set("name", name)
out.push(entry)
}
next_pos = obj_end + 1
pos = next_pos
}
return out
}
_build_module_map(raw) {
// Delimiter分割で key=val を MapBox に積む Region+next_start 形。
local map = new MapBox()
if raw == null { return map }
local delim = "|||"
local start = 0
local cont = 1
loop(cont == 1) {
local next_start = raw.length()
local next = JsonFragBox.index_of_from(raw, delim, start)
local seg = ""
if next >= 0 {
seg = raw.substring(start, next)
next_start = next + delim.length()
} else {
seg = raw.substring(start, raw.length())
cont = 0
}
if seg.length() > 0 {
local eq_idx = JsonFragBox.index_of_from(seg, "=", 0)
if eq_idx >= 0 {
local key = seg.substring(0, eq_idx)
local val = seg.substring(eq_idx + 1, seg.length())
if key != "" && val != "" {
map.set(key, val)
}
}
}
start = next_start
}
return map
}
resolve_for_source(src_unused, modules_raw) {
local entries = me._collect_using_entries(src_unused)
if entries == null { return 0 }
local modules_map = me._build_module_map(modules_raw)
local prefix = ""
local i = 0
local n = entries.length()
loop(i < n) {
local next_i = i + 1
local entry = entries.get(i)
local name = "" + entry.get("name")
if name != "" {
prefix = prefix + name
if modules_map.has(name) {
prefix = prefix + modules_map.get(name)
}
}
i = next_i
}
return prefix.length()
}
main() {
// modules_map: A→/a, B→/b, 末尾デリミタ付き
return me.resolve_for_source("unused", "A=/a|||B=/b|||")
}
}
"#;
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&cr.module) {
for e in &errors {
eprintln!("[rust-mir-verify] {}", e);
}
panic!("MIR verification failed for Stage1UsingResolverResolveWithMap");
}
}

View File

@ -14,6 +14,7 @@ pub mod mir_loopform_conditional_reassign;
pub mod mir_loopform_exit_phi; pub mod mir_loopform_exit_phi;
pub mod mir_loopform_complex; pub mod mir_loopform_complex;
pub mod mir_static_box_naming; pub mod mir_static_box_naming;
pub mod mir_stage1_cli_emit_program_min;
pub mod mir_stage1_using_resolver_verify; pub mod mir_stage1_using_resolver_verify;
pub mod stage1_cli_entry_ssa_smoke; pub mod stage1_cli_entry_ssa_smoke;
pub mod mir_stageb_like_args_length; pub mod mir_stageb_like_args_length;