Files
hakorune/src/backend/mir_interpreter/handlers/calls/global.rs
nyash-codex 63012932eb feat(phase-0): 観測ライン緊急構築完了 - Silent Failure 根絶
Phase 21.7++ Phase 0: 開発者体験を劇的に向上させる3つの改善

## 🎯 実装内容

###  Phase 0.1: populate_from_toml エラー即座表示
**ファイル**: src/runner/pipeline.rs:57-65

**Before**: TOML parse エラーが silent failure
**After**: エラーを即座に警告表示 + デバッグ方法提示

```
⚠️  [using/workspace] Failed to load TOML modules:
    Error: TOML parse error at line 18...
    → All 'using' aliases will be unavailable
    → Fix TOML syntax errors in workspace modules

    💡 Debug: NYASH_DEBUG_USING=1 for detailed logs
```

**効果**: 今回の StringUtils バグなら即発見できた!

###  Phase 0.2: VM 関数ルックアップ常時提案
**ファイル**: src/backend/mir_interpreter/handlers/calls/global.rs:179-206

**Before**: "Unknown: StringUtils.starts_with" だけ
**After**: 類似関数を自動提案 + デバッグ方法提示

```
Function not found: StringUtils.starts_with

💡 Did you mean:
   - StringUtils.starts_with/2
   - StringUtils.ends_with/2

🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace
```

**効果**: arity 問題を即発見!環境変数不要で親切!

###  Phase 0.3: using not found 詳細化
**ファイル**: src/runner/modes/common_util/resolve/strip.rs:354-389

**Before**: "'StringUtils' not found" だけ
**After**: 類似モジュール提案 + 利用可能数 + 修正方法提示

```
using: 'StringUtil' not found in nyash.toml [using]/[modules]

💡 Did you mean:
   - StringUtils
   - JsonUtils

Available modules: 4 aliases

📝 Suggestions:
   - Add an alias in nyash.toml: [using.aliases] YourModule = "path/to/module"
   - Use the alias: using YourModule as YourModule
   - Dev/test mode: NYASH_PREINCLUDE=1

🔍 Debug: NYASH_DEBUG_USING=1 for detailed logs
```

**効果**: タイポを即発見!TOML エラーとの因果関係も提示!

## 📊 Phase 0 成果まとめ

**工数**: 約2.5時間(予想: 2-3時間)
**効果**: Silent Failure 完全根絶 🎉

### Before Phase 0
- TOML エラー: 無言で失敗
- 関数が見つからない: "Unknown" だけ
- using が見つからない: "not found" だけ
- デバッグ方法: 環境変数を知ってる人だけ

### After Phase 0
- TOML エラー: 即座に警告 + 影響範囲説明
- 関数が見つからない: 類似関数提案 + デバッグ方法
- using が見つからない: 類似モジュール提案 + 修正方法 + デバッグ方法
- すべてのエラーが親切 🎊

##  テスト結果

- StringUtils テスト:  PASS
- 既存テスト:  337 passed(16 failed は元々の失敗)
- ビルド:  SUCCESS
- 退行:  なし

## 🎉 開発者体験の改善

今回のような StringUtils using バグが起きても:
1. **TOML エラー**: 即発見(数秒)
2. **arity 問題**: 提案から即解決(数分)
3. **タイポ**: 提案から即修正(数秒)

**Before**: 数時間のデバッグ
**After**: 数分で解決 🚀

次のステップ: Phase 1(基盤整備)に進む準備完了!
2025-11-22 02:13:10 +09:00

210 lines
9.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::*;
impl MirInterpreter {
pub(super) fn execute_global_function(
&mut self,
func_name: &str,
args: &[ValueId],
) -> Result<VMValue, VMError> {
// NamingBox: static box 名の正規化main._nop/0 → Main._nop/0 など)
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 側の正規化に一本化する。
if let Some(func) = self.functions.get(&canonical).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
for a in args {
argv.push(self.reg_load(*a)?);
}
return self.exec_function_inner(&func, Some(&argv));
}
match base {
// Console-like globals
"print" | "nyash.builtin.print" => {
// Reuse extern handler for consistency with other console names
return self.execute_extern_function("print", args);
}
"error" => {
if let Some(arg_id) = args.get(0) {
let val = self.reg_load(*arg_id)?;
eprintln!("Error: {}", val.to_string());
}
return Ok(VMValue::Void);
}
"panic" => {
return self.execute_extern_function("panic", args);
}
"exit" => {
return self.execute_extern_function("exit", args);
}
"env.get" => {
// Route env.get global to extern handler
return self.execute_extern_function("env.get", args);
}
"hostbridge.extern_invoke" => {
// SSOT: delegate to extern dispatcher (provider)
return self.execute_extern_function("hostbridge.extern_invoke", args);
}
// LLVM harness providers (direct)
"env.mirbuilder.emit" | "env.mirbuilder.emit/1" => {
if let Some(a0) = args.get(0) {
let s = self.reg_load(*a0)?.to_string();
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
Ok(out) => Ok(VMValue::String(out)),
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
}
} else {
Err(self.err_invalid("env.mirbuilder.emit expects 1 arg"))
}
}
"env.codegen.emit_object" | "env.codegen.emit_object/1" => {
if let Some(a0) = args.get(0) {
let s = self.reg_load(*a0)?.to_string();
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
.ok()
.or(Some("0".to_string())),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => {
Err(self.err_with_context("env.codegen.emit_object", &e.to_string()))
}
}
} else {
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
}
}
"env.codegen.link_object" | "env.codegen.link_object/3" => {
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
if args.len() < 3 {
return Err(self.err_arg_count("env.codegen.link_object", 3, args.len()));
}
let v = self.reg_load(args[2])?;
let (obj_path, exe_out) = match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else {
(b.to_string_box().value, None)
}
}
_ => (v.to_string(), None),
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
}
}
"nyash.builtin.error" => {
if let Some(arg_id) = args.get(0) {
let val = self.reg_load(*arg_id)?;
eprintln!("Error: {}", val.to_string());
}
Ok(VMValue::Void)
}
_ => {
// ⚠️ Phase 0.2: User-friendly "Did you mean?" suggestions
let prefix = if let Some(idx) = canonical.find('.') {
&canonical[..idx]
} else {
&canonical
};
let similar: Vec<_> = self.functions.keys()
.filter(|k| k.starts_with(prefix))
.take(5)
.collect();
let mut err_msg = format!("Function not found: {}", func_name);
if !similar.is_empty() {
err_msg.push_str("\n\n💡 Did you mean:");
for s in similar {
err_msg.push_str(&format!("\n - {}", s));
}
}
err_msg.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace");
// NamingBox SSOT: ここで canonical に失敗したら素直に Unknown とする。
// レガシーフォールバックfunctions.get(func_name) 再探索)は Phase 25.x で廃止済み。
Err(self.err_with_context("global function", &err_msg))
}
}
}
}