diff --git a/apps/examples/json_lint/nyash.toml b/apps/examples/json_lint/nyash.toml index 60b64111..f8d3255b 100644 --- a/apps/examples/json_lint/nyash.toml +++ b/apps/examples/json_lint/nyash.toml @@ -2,6 +2,9 @@ path = "apps/lib/json_native/" main = "parser/parser.hako" +[using.string_utils] +path = "apps/lib/json_native/utils/string.hako" + [using.aliases] json = "json_native" - +StringUtils = "string_utils" diff --git a/apps/tests/json_lint_stringutils_min.hako b/apps/tests/json_lint_stringutils_min.hako new file mode 100644 index 00000000..4b75b6ab --- /dev/null +++ b/apps/tests/json_lint_stringutils_min.hako @@ -0,0 +1,23 @@ +// Minimal repro for JSON lint StringUtils.starts_with/2 resolution +// +// 目的: +// - using StringUtils as StringUtils 経由で static box StringUtils を参照する最小ケース。 +// - 現状 VM では Unknown: StringUtils.starts_with/2 となる既知バグの切り出し用。 +// +// 将来: +// - Stage‑B / UsingResolver が string_utils モジュールを正しく Program(JSON)/MIR に連結できるようになったら +// このケースで OK / ERROR 判定が正しく動くことを確認する。 + +using StringUtils as StringUtils + +static box Main { + main() { + local ok = 0 + if StringUtils.starts_with("abc", "a") and StringUtils.ends_with("abc", "c") { + ok = 1 + } + if ok == 1 { print("OK") } else { print("ERROR") } + return 0 + } +} + diff --git a/lang/src/llvm_ir/hako_module.toml b/lang/src/llvm_ir/hako_module.toml index 433fc1bc..a9319a2b 100644 --- a/lang/src/llvm_ir/hako_module.toml +++ b/lang/src/llvm_ir/hako_module.toml @@ -15,7 +15,7 @@ boxes = [ normalize.print = "boxes/normalize/normalize_print.hako" normalize.ref = "boxes/normalize/normalize_ref.hako" normalize.array_legacy = "boxes/normalize/normalize_array_legacy.hako" -aot_prep = "boxes/aot_prep.hako" +# aot_prep = "boxes/aot_prep.hako" # Commented out: conflicts with aot_prep.passes.* below aot_prep.passes.strlen = "boxes/aot_prep/passes/strlen.hako" aot_prep.passes.loop_hoist = "boxes/aot_prep/passes/loop_hoist.hako" aot_prep.passes.const_dedup = "boxes/aot_prep/passes/const_dedup.hako" diff --git a/lang/src/mir/builder/MirBuilderBox.hako b/lang/src/mir/builder/MirBuilderBox.hako index 4e5b5141..b0e9904a 100644 --- a/lang/src/mir/builder/MirBuilderBox.hako +++ b/lang/src/mir/builder/MirBuilderBox.hako @@ -32,7 +32,9 @@ static box MirBuilderBox { method _norm_if_apply(m, func_defs_mir) { if m == null { return null } local result = FuncLoweringBox.inject_funcs(m, func_defs_mir) - if env.get("HAKO_MIR_BUILDER_METHODIZE") == "1" { + // methodization は既定ON。明示的に "0" が指定された場合のみ無効化。 + local methodize = env.get("HAKO_MIR_BUILDER_METHODIZE") + if methodize == null || ("" + methodize) != "0" { result = FuncLoweringBox.methodize_calls_in_mir(result) } local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE") diff --git a/lang/src/mir/builder/func_lowering.hako b/lang/src/mir/builder/func_lowering.hako index 95cbd07f..a17f8789 100644 --- a/lang/src/mir/builder/func_lowering.hako +++ b/lang/src/mir/builder/func_lowering.hako @@ -243,7 +243,9 @@ static box FuncLoweringBox { // Heuristic: find call {func:K,args:[..],dst:R} and match preceding const {dst:K,value:"Box.method/N"} // Receiverは省略(VM 側で静的シングルトンを補完)。LLVM 経路はPoCの範囲外。 method methodize_calls_in_mir(mir_json) { - if env.get("HAKO_MIR_BUILDER_METHODIZE") != "1" { return mir_json } + // methodization は既定ON。明示的に "0" のときだけ無効化。 + local dm = env.get("HAKO_MIR_BUILDER_METHODIZE") + if dm != null && ("" + dm) == "0" { return mir_json } if mir_json == null { return mir_json } local s = "" + mir_json local out = "" diff --git a/src/backend/mir_interpreter/handlers/calls/global.rs b/src/backend/mir_interpreter/handlers/calls/global.rs index 64a76700..c9a1319e 100644 --- a/src/backend/mir_interpreter/handlers/calls/global.rs +++ b/src/backend/mir_interpreter/handlers/calls/global.rs @@ -7,12 +7,46 @@ impl MirInterpreter { args: &[ValueId], ) -> Result { // NamingBox: static box 名の正規化(main._nop/0 → Main._nop/0 など) - let canonical = crate::mir::naming::normalize_static_global_name(func_name); + let mut canonical = crate::mir::naming::normalize_static_global_name(func_name); + + // 🎯 Phase 21.7++: If function name doesn't have arity, add it from args.len() + // MIR functions are stored as "BoxName.method/arity" but calls may come without arity + if !canonical.contains('/') { + canonical = format!("{}/{}", canonical, args.len()); + } // Normalize arity suffix for extern-like dispatch, but keep canonical/original name // for module-local function table lookup (functions may carry arity suffix). let base = super::super::utils::normalize_arity_suffix(&canonical); + // 🔍 Debug: Check function lookup + if std::env::var("NYASH_DEBUG_FUNCTION_LOOKUP").ok().as_deref() == Some("1") { + eprintln!("[DEBUG/vm] Looking up function: '{}'", func_name); + eprintln!("[DEBUG/vm] canonical: '{}'", canonical); + eprintln!("[DEBUG/vm] base: '{}'", base); + eprintln!("[DEBUG/vm] Available functions: {}", self.functions.len()); + if !self.functions.contains_key(&canonical) { + eprintln!("[DEBUG/vm] ❌ '{}' NOT found in functions", canonical); + // List functions starting with same prefix + let prefix = if let Some(idx) = canonical.find('.') { + &canonical[..idx] + } else { + &canonical + }; + let matching: Vec<_> = self.functions.keys() + .filter(|k| k.starts_with(prefix)) + .collect(); + if !matching.is_empty() { + eprintln!("[DEBUG/vm] Similar functions:"); + for k in matching.iter().take(10) { + eprintln!("[DEBUG/vm] - {}", k); + } + } + } else { + eprintln!("[DEBUG/vm] ✅ '{}' found", canonical); + } + } + // Module-local/global function: execute by function table if present. // まず canonical 名で探す(Main._nop/0 など)。Phase 25.x 時点では // レガシー名での再探索は廃止し、NamingBox 側の正規化に一本化する。 diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index 4dfb74d2..c23918c7 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -175,7 +175,15 @@ impl UnifiedCallEmitterBox { // 🎯 Phase 21.7: Methodization (HAKO_MIR_BUILDER_METHODIZE=1) // Convert Global("BoxName.method/arity") → Method{receiver=static singleton} - if std::env::var("HAKO_MIR_BUILDER_METHODIZE").ok().as_deref() == Some("1") { + let methodize_on = match std::env::var("HAKO_MIR_BUILDER_METHODIZE") + .ok() + .as_deref() + { + // 明示的に "0" が指定されたときだけ無効化。 + Some("0") => false, + _ => true, + }; + if methodize_on { if let Callee::Global(ref name) = callee { let name_clone = name.clone(); // Clone to avoid borrow checker issues // Try to decode as static box method diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs index ca50c8dc..04e0a751 100644 --- a/src/runner/pipeline.rs +++ b/src/runner/pipeline.rs @@ -34,13 +34,26 @@ impl NyashRunner { using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string())); // nyash.toml: delegate to using resolver (keeps existing behavior) - let _ = crate::using::resolver::populate_from_toml( + let toml_result = crate::using::resolver::populate_from_toml( &mut using_paths, &mut pending_modules, &mut aliases, &mut packages, ); + // 🔍 Debug: Check if aliases are loaded + if std::env::var("NYASH_DEBUG_USING").ok().as_deref() == Some("1") { + eprintln!("[DEBUG/using] populate_from_toml result: {:?}", toml_result); + eprintln!("[DEBUG/using] Loaded {} aliases", aliases.len()); + for (k, v) in aliases.iter() { + eprintln!("[DEBUG/using] alias: '{}' => '{}'", k, v); + } + eprintln!("[DEBUG/using] Loaded {} packages", packages.len()); + for (k, v) in packages.iter() { + eprintln!("[DEBUG/using] package: '{}' => path='{}'", k, v.path); + } + } + // Env overrides: modules and using paths if let Ok(ms) = std::env::var("NYASH_MODULES") { for ent in ms.split(',') { diff --git a/src/tests/json_lint_stringutils_min_vm.rs b/src/tests/json_lint_stringutils_min_vm.rs new file mode 100644 index 00000000..eb163e7e --- /dev/null +++ b/src/tests/json_lint_stringutils_min_vm.rs @@ -0,0 +1,109 @@ +/*! + * StringUtils arity suffix 自動補完テスト(Phase 21.7++) + * + * 目的: + * - VM の execute_global_function で arity が欠落している場合に + * args.len() から自動補完される機能を検証する。 + * + * 背景: + * - MIR 関数は "BoxName.method/arity" 形式で格納される + * - 呼び出し側が arity なしで "BoxName.method" を指定した場合、 + * 自動的に "/arity" を追加して検索する + * + * 修正内容(2025-11-21): + * 1. lang/src/llvm_ir/hako_module.toml の TOML パースエラーを修正 + * 2. src/backend/mir_interpreter/handlers/calls/global.rs で arity 自動補完実装 + * + * 注意: + * - このテストは using 解決をテストするものではなく、arity 自動補完のみをテストする + * - using 解決のテストは CLI 経由で実施(apps/tests/json_lint_stringutils_min.hako) + */ + +use crate::ast::ASTNode; +use crate::backend::VM; +use crate::mir::MirCompiler; +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_PARSER_ALLOW_SEMICOLON", "1"); + std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); + std::env::set_var("HAKO_MIR_BUILDER_METHODIZE", "0"); +} + +#[test] +fn json_lint_stringutils_min_vm() { + ensure_stage3_env(); + + // arity 自動補完をテストするため、using を使わずに static box で直接実装 + let src = r#" +static box StringUtils { + starts_with(text, prefix) { + local text_len = text.length() + local prefix_len = prefix.length() + if prefix_len > text_len { return 0 } + local i = 0 + loop(i < prefix_len) { + if text.substring(i, i + 1) != prefix.substring(i, i + 1) { + return 0 + } + i = i + 1 + } + return 1 + } + + ends_with(text, suffix) { + local text_len = text.length() + local suffix_len = suffix.length() + if suffix_len > text_len { return 0 } + local offset = text_len - suffix_len + local i = 0 + loop(i < suffix_len) { + if text.substring(offset + i, offset + i + 1) != suffix.substring(i, i + 1) { + return 0 + } + i = i + 1 + } + return 1 + } +} + +static box Main { + main() { + if StringUtils.starts_with("abc", "a") and StringUtils.ends_with("abc", "c") { + print("OK") + } else { + print("ERROR") + } + return 0 + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + let mut vm = VM::new(); + let result = vm.execute_module(&cr.module); + + // ✅ arity 自動補完により StringUtils.starts_with → StringUtils.starts_with/2 に解決されることを確認 + match result { + Ok(_v) => { + eprintln!("[json_lint_stringutils_min] VM executed successfully"); + // Success - arity auto-completion worked! + } + Err(e) => { + panic!("VM should execute successfully, but got error: {:?}", e); + } + } + + // cleanup + std::env::remove_var("NYASH_PARSER_STAGE3"); + std::env::remove_var("HAKO_PARSER_STAGE3"); + std::env::remove_var("NYASH_PARSER_ALLOW_SEMICOLON"); + std::env::remove_var("NYASH_DISABLE_PLUGINS"); + std::env::remove_var("HAKO_MIR_BUILDER_METHODIZE"); +} + diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 4fe97a28..a9988e9d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -17,6 +17,7 @@ pub mod mir_static_box_naming; pub mod mir_stage1_cli_emit_program_min; pub mod mir_stage1_staticcompiler_receiver; // Phase 25.1: StaticCompiler receiver型推論バグ回帰防止 pub mod mir_stage1_using_resolver_verify; +pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix pub mod stage1_cli_entry_ssa_smoke; pub mod mir_stageb_like_args_length; pub mod mir_stageb_loop_break_continue;