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,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");
}
}
/// 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_complex;
pub mod mir_static_box_naming;
pub mod mir_stage1_cli_emit_program_min;
pub mod mir_stage1_using_resolver_verify;
pub mod stage1_cli_entry_ssa_smoke;
pub mod mir_stageb_like_args_length;