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:
142
src/tests/mir_stage1_cli_emit_program_min.rs
Normal file
142
src/tests/mir_stage1_cli_emit_program_min.rs
Normal 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 解決を Stage‑0 ランナーや 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 Stage‑1 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 Stage‑1 CLI path to fail deterministically (type mismatch or missing Stage1Cli); got: {}",
|
||||
msg
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user