refactor: complete error message unification (Phase 3.5)

Migrate remaining 45 error generation patterns to unified helpers:
- calls.rs: 13 sites → 0
- extern_provider.rs: 9 sites → 0
- externals.rs: 5 sites → 0
- boxes_plugin.rs: 5 sites → 0
- boxes.rs: 5 sites → 0
- boxes_string.rs: 4 sites → 0
- boxes_instance.rs: 2 sites → 0
- mod.rs + boxes_array.rs: 2 sites → 0

Error patterns now 100% unified:
- All 80 InvalidInstruction sites use helpers (100%)
- Consistent error formatting across entire codebase
- Single source of truth for error messages

Code reduction:
- Phase 3.5: 50-70 lines saved
- Cumulative (Phase 1+2+3+3.5): 200-267 lines removed (6-8% handlers)
- Total patterns unified: 192 (destination 60 + args 52 + errors 80)

Benefits:
- 100% error message consistency achieved
- Easy to modify error formats globally
- Foundation for i18n support ready
- Improved maintainability and testability

Test results: ✓ Build successful, Phase 21.0 smoke tests passing
Related: Phase 21.0 refactoring milestone complete
Risk: Low (error messages only, behavior preserved)
This commit is contained in:
nyash-codex
2025-11-06 23:34:46 +09:00
parent 0287020a5b
commit 22b668927e
9 changed files with 56 additions and 111 deletions

View File

@ -11,7 +11,7 @@ impl MirInterpreter {
) -> Result<(), VMError> {
// Provider Lock guard (受け口・既定は挙動不変)
if let Err(e) = crate::runtime::provider_lock::guard_before_new_box(box_type) {
return Err(VMError::InvalidInstruction(e));
return Err(self.err_invalid(e));
}
let mut converted: Vec<Box<dyn NyashBox>> = Vec::with_capacity(args.len());
for vid in args {
@ -22,9 +22,7 @@ impl MirInterpreter {
.lock()
.unwrap()
.create_box(box_type, &converted)
.map_err(|e| {
VMError::InvalidInstruction(format!("NewBox {} failed: {}", box_type, e))
})?;
.map_err(|e| self.err_with_context(&format!("NewBox {}", box_type), &e.to_string()))?;
// Store created instance first so 'me' can be passed to birth
let created_vm = VMValue::from_nyash_box(created);
self.regs.insert(dst, created_vm.clone());
@ -74,10 +72,10 @@ impl MirInterpreter {
}
}
Err(e) => {
return Err(VMError::InvalidInstruction(format!(
"PluginInvoke {}.{} failed: {:?}",
p.box_type, method, e
)))
return Err(self.err_with_context(
&format!("PluginInvoke {}.{}", p.box_type, method),
&format!("{:?}", e)
))
}
}
Ok(())
@ -88,11 +86,7 @@ impl MirInterpreter {
}
Ok(())
} else {
Err(VMError::InvalidInstruction(format!(
"PluginInvoke unsupported on {} for method {}",
recv_box.type_name(),
method
)))
Err(self.err_method_not_found(&recv_box.type_name(), method))
}
}
@ -188,7 +182,7 @@ impl MirInterpreter {
}
if user_instance_class.is_some() && !crate::config::env::vm_allow_user_instance_boxcall() {
let cls = user_instance_class.unwrap();
return Err(VMError::InvalidInstruction(format!(
return Err(self.err_invalid(format!(
"User Instance BoxCall disallowed in prod: {}.{} (enable builder rewrite)",
cls, method
)));

View File

@ -32,7 +32,7 @@ pub(super) fn try_handle_array_box(
return Ok(true);
}
"pop" => {
if !args.is_empty() { return Err(VMError::InvalidInstruction("pop expects 0 args".into())); }
if !args.is_empty() { return Err(this.err_invalid("pop expects 0 args")); }
let ret = ab.pop();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);

View File

@ -92,7 +92,7 @@ pub(super) fn try_handle_instance_box(
// Dev assert: forbid birth(me==Void)
if method == "birth" && crate::config::env::using_is_dev() {
if matches!(recv_vm, VMValue::Void) {
return Err(VMError::InvalidInstruction("Dev assert: birth(me==Void) is forbidden".into()));
return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden"));
}
}
argv.push(recv_vm.clone());
@ -127,7 +127,7 @@ pub(super) fn try_handle_instance_box(
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
if method == "birth" && crate::config::env::using_is_dev() {
if matches!(recv_vm, VMValue::Void) {
return Err(VMError::InvalidInstruction("Dev assert: birth(me==Void) is forbidden".into()));
return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden"));
}
}
argv.push(recv_vm.clone());

View File

@ -60,10 +60,10 @@ pub(super) fn invoke_plugin_box(
}
Ok(())
}
Err(e) => Err(VMError::InvalidInstruction(format!(
"BoxCall {}.{} failed: {:?}",
p.box_type, method, e
))),
Err(e) => Err(this.err_with_context(
&format!("BoxCall {}.{}", p.box_type, method),
&format!("{:?}", e)
)),
}
} else if let Some(string_box) = recv_box
.as_any()
@ -80,9 +80,7 @@ pub(super) fn invoke_plugin_box(
}
Ok(())
} else {
Err(VMError::InvalidInstruction(
"lastIndexOf requires 1 argument".into(),
))
Err(this.err_invalid("lastIndexOf requires 1 argument"))
}
}
"indexOf" | "find" => {
@ -94,15 +92,10 @@ pub(super) fn invoke_plugin_box(
}
Ok(())
} else {
Err(VMError::InvalidInstruction(
"indexOf/find requires 1 argument".into(),
))
Err(this.err_invalid("indexOf/find requires 1 argument"))
}
}
_ => Err(VMError::InvalidInstruction(format!(
"BoxCall method {} not supported on StringBox",
method
))),
_ => Err(this.err_method_not_found("StringBox", method)),
}
} else {
// Special-case: minimal runtime fallback for common InstanceBox methods when
@ -209,10 +202,6 @@ pub(super) fn invoke_plugin_box(
_ => {}
}
}
Err(VMError::InvalidInstruction(format!(
"BoxCall unsupported on {}.{}",
recv_box.type_name(),
method
)))
Err(this.err_method_not_found(&recv_box.type_name(), method))
}
}

View File

@ -72,8 +72,8 @@ pub(super) fn try_handle_string_box(
(n, from.max(0) as usize)
}
_ => {
return Err(VMError::InvalidInstruction(
"indexOf expects 1 or 2 args (search [, fromIndex])".into(),
return Err(this.err_invalid(
"indexOf expects 1 or 2 args (search [, fromIndex])"
));
}
};
@ -144,8 +144,8 @@ pub(super) fn try_handle_string_box(
(s, e)
}
_ => {
return Err(VMError::InvalidInstruction(
"substring expects 1 or 2 args (start [, end])".into(),
return Err(this.err_invalid(
"substring expects 1 or 2 args (start [, end])"
));
}
};
@ -172,7 +172,7 @@ pub(super) fn try_handle_string_box(
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_digit_char expects 0 or 1 arg".into()));
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_digit));
@ -185,7 +185,7 @@ pub(super) fn try_handle_string_box(
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_hex_digit_char expects 0 or 1 arg".into()));
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_hex));

View File

@ -424,15 +424,10 @@ impl MirInterpreter {
if let Some(s) = first_arg_str {
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
Ok(out) => Ok(VMValue::String(out)),
Err(e) => Err(VMError::InvalidInstruction(format!(
"env.mirbuilder.emit: {}",
e
))),
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.mirbuilder.emit expects 1 arg".into(),
))
Err(self.err_invalid("extern_invoke env.mirbuilder.emit expects 1 arg"))
}
}
("env.codegen", "emit_object") => {
@ -445,15 +440,10 @@ impl MirInterpreter {
};
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(VMError::InvalidInstruction(format!(
"env.codegen.emit_object: {}",
e
))),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.codegen.emit_object expects 1 arg".into(),
))
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
}
}
("env.codegen", "link_object") => {
@ -611,7 +601,7 @@ impl MirInterpreter {
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
eprintln!("[hb:unsupported:calls] {}.{}", name, method);
}
Err(VMError::InvalidInstruction(format!(
Err(self.err_invalid(format!(
"hostbridge.extern_invoke unsupported for {}.{} [calls]",
name, method
)))
@ -705,9 +695,7 @@ impl MirInterpreter {
let new_str = format!("{}{}", s, arg_val.to_string());
Ok(VMValue::String(new_str))
} else {
Err(VMError::InvalidInstruction(
"concat requires 1 argument".into(),
))
Err(self.err_invalid("concat requires 1 argument"))
}
}
"replace" => {
@ -716,9 +704,7 @@ impl MirInterpreter {
let new = self.reg_load(args[1])?.to_string();
Ok(VMValue::String(s.replace(&old, &new)))
} else {
Err(VMError::InvalidInstruction(
"replace requires 2 arguments".into(),
))
Err(self.err_invalid("replace requires 2 arguments"))
}
}
"indexOf" => {
@ -727,9 +713,7 @@ impl MirInterpreter {
let idx = s.find(&needle).map(|i| i as i64).unwrap_or(-1);
Ok(VMValue::Integer(idx))
} else {
Err(VMError::InvalidInstruction(
"indexOf requires 1 argument".into(),
))
Err(self.err_invalid("indexOf requires 1 argument"))
}
}
"lastIndexOf" => {
@ -738,9 +722,7 @@ impl MirInterpreter {
let idx = s.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
Ok(VMValue::Integer(idx))
} else {
Err(VMError::InvalidInstruction(
"lastIndexOf requires 1 argument".into(),
))
Err(self.err_invalid("lastIndexOf requires 1 argument"))
}
}
"substring" => {
@ -774,9 +756,7 @@ impl MirInterpreter {
let result_box = string_box.lastIndexOf(&needle);
Ok(VMValue::from_nyash_box(result_box))
} else {
Err(VMError::InvalidInstruction(
"lastIndexOf requires 1 argument".into(),
))
Err(self.err_invalid("lastIndexOf requires 1 argument"))
}
}
"indexOf" | "find" => {
@ -785,9 +765,7 @@ impl MirInterpreter {
let result_box = string_box.find(&needle);
Ok(VMValue::from_nyash_box(result_box))
} else {
Err(VMError::InvalidInstruction(
"indexOf/find requires 1 argument".into(),
))
Err(self.err_invalid("indexOf/find requires 1 argument"))
}
}
_ => Err(self.err_method_not_found("StringBox", method)),
@ -811,17 +789,13 @@ impl MirInterpreter {
) {
Ok(Some(ret)) => Ok(VMValue::from_nyash_box(ret)),
Ok(None) => Ok(VMValue::Void),
Err(e) => Err(VMError::InvalidInstruction(format!(
"Plugin method {}.{} failed: {:?}",
p.box_type, method, e
))),
Err(e) => Err(self.err_with_context(
&format!("Plugin method {}.{}", p.box_type, method),
&format!("{:?}", e)
)),
}
} else {
Err(VMError::InvalidInstruction(format!(
"Method {} not supported on BoxRef({})",
method,
box_ref.type_name()
)))
Err(self.err_method_not_found(&box_ref.type_name(), method))
}
}
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),

View File

@ -82,7 +82,7 @@ impl MirInterpreter {
// Only supported on C-API route; expect 1 or 2 args: obj_path [, exe_out]
let obj_path = match args.get(0) {
Some(v) => match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) },
None => return Some(Err(VMError::InvalidInstruction("env.codegen.link_object expects 1+ args".into()))),
None => return Some(Err(self.err_invalid("env.codegen.link_object expects 1+ args"))),
};
let exe_out = match args.get(1) {
Some(v) => Some(match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }),
@ -114,9 +114,7 @@ impl MirInterpreter {
eprintln!("[hb:entry:provider] hostbridge.extern_invoke");
}
if args.len() < 2 {
return Some(Err(VMError::InvalidInstruction(
"extern_invoke expects at least 2 args".into(),
)));
return Some(Err(self.err_invalid("extern_invoke expects at least 2 args")));
}
let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
@ -186,7 +184,7 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Some(Err(VMError::InvalidInstruction("extern_invoke env.codegen.link_object expects args array".into())));
return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array")));
};
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") {
@ -204,15 +202,10 @@ impl MirInterpreter {
if let Some(s) = first_arg_str {
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
Ok(out) => Ok(VMValue::String(Self::patch_mir_json_version(&out))),
Err(e) => Err(VMError::InvalidInstruction(format!(
"env.mirbuilder.emit: {}",
e
))),
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.mirbuilder.emit expects 1 arg".into(),
))
Err(self.err_invalid("extern_invoke env.mirbuilder.emit expects 1 arg"))
}
}
("env.codegen", "emit_object") => {
@ -225,15 +218,10 @@ impl MirInterpreter {
};
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(VMError::InvalidInstruction(format!(
"env.codegen.emit_object: {}",
e
))),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.codegen.emit_object expects 1 arg".into(),
))
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
}
}
("env.codegen", "link_object") => {
@ -267,7 +255,7 @@ impl MirInterpreter {
}
let objs = match obj_s {
Some(s) => s,
None => return Some(Err(VMError::InvalidInstruction("extern_invoke env.codegen.link_object expects args".into()))),
None => return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args"))),
};
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") {
@ -285,7 +273,7 @@ impl MirInterpreter {
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
eprintln!("[hb:unsupported:provider] {}.{}", name, method);
}
Err(VMError::InvalidInstruction(format!(
Err(self.err_invalid(format!(
"hostbridge.extern_invoke unsupported for {}.{} [provider]",
name, method
)))

View File

@ -155,7 +155,7 @@ impl MirInterpreter {
// Note: This branch is used for ExternCall form; provider toggles must be ON.
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(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into()));
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
// Extract array payload
let (obj_path, exe_out) = if let Some(a2) = args.get(2) {
@ -177,13 +177,13 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Err(VMError::InvalidInstruction("extern_invoke env.codegen.link_object expects args array".into()));
return Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array"));
};
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"));
crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref())
.map_err(|e| VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e)))?;
.map_err(|e| self.err_with_context("env.codegen.link_object", &e.to_string()))?;
self.write_result(dst, VMValue::String(exe.to_string_lossy().into_owned()));
Ok(())
}
@ -195,9 +195,9 @@ impl MirInterpreter {
}
return Ok(());
}
return Err(VMError::InvalidInstruction("hostbridge.extern_invoke unsupported [externals]".into()));
return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]"));
}
_ => Err(VMError::InvalidInstruction(format!(
_ => Err(self.err_invalid(format!(
"ExternCall {}.{} not supported",
iface, method
))),

View File

@ -96,7 +96,7 @@ impl MirInterpreter {
| MirInstruction::Safepoint
| MirInstruction::Nop => {}
other => {
return Err(VMError::InvalidInstruction(format!(
return Err(self.err_invalid(format!(
"MIR interp: unimplemented instruction: {:?}",
other
)))