Added conversion_helpers.rs with unified type conversion: - load_as_box() for reg_load().to_nyash_box() - load_as_string() for reg_load().to_string() - load_as_int/bool() for type-checked loads - load_args_as_boxes/values() for bulk conversion Files updated (partial): - boxes.rs: 2 sites (-6 lines) - calls.rs: 5 sites (-10 lines) - boxes_plugin.rs: 1 site (-3 lines) - externals.rs: 3 sites (-4 lines) Next: boxes_array, boxes_map, boxes_object_fields, boxes_instance Tests: Phase 21.0 PASS (2/2, 100%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
290 lines
12 KiB
Rust
290 lines
12 KiB
Rust
use super::*;
|
|
use super::super::utils::*;
|
|
use crate::box_trait::NyashBox;
|
|
|
|
impl MirInterpreter {
|
|
pub(super) fn handle_new_box(
|
|
&mut self,
|
|
dst: ValueId,
|
|
box_type: &str,
|
|
args: &[ValueId],
|
|
) -> Result<(), VMError> {
|
|
// Provider Lock guard (受け口・既定は挙動不変)
|
|
if let Err(e) = crate::runtime::provider_lock::guard_before_new_box(box_type) {
|
|
return Err(self.err_invalid(e));
|
|
}
|
|
let converted = self.load_args_as_boxes(args)?;
|
|
let reg = crate::runtime::unified_registry::get_global_unified_registry();
|
|
let created = reg
|
|
.lock()
|
|
.unwrap()
|
|
.create_box(box_type, &converted)
|
|
.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());
|
|
|
|
// Trace: new box event (dev-only)
|
|
if Self::box_trace_enabled() {
|
|
self.box_trace_emit_new(box_type, args.len());
|
|
}
|
|
|
|
// Note: birth の自動呼び出しは削除。
|
|
// 正しい設計は Builder が NewBox 後に明示的に birth 呼び出しを生成すること。
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn handle_plugin_invoke(
|
|
&mut self,
|
|
dst: Option<ValueId>,
|
|
box_val: ValueId,
|
|
method: &str,
|
|
args: &[ValueId],
|
|
) -> Result<(), VMError> {
|
|
let recv = self.reg_load(box_val)?;
|
|
let recv_box: Box<dyn NyashBox> = match recv.clone() {
|
|
VMValue::BoxRef(b) => b.share_box(),
|
|
other => other.to_nyash_box(),
|
|
};
|
|
|
|
if let Some(p) = recv_box
|
|
.as_any()
|
|
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
|
|
{
|
|
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
|
let host = host.read().unwrap();
|
|
let argv = self.load_args_as_boxes(args)?;
|
|
match host.invoke_instance_method(&p.box_type, method, p.inner.instance_id, &argv) {
|
|
Ok(Some(ret)) => {
|
|
self.write_from_box(dst, ret);
|
|
}
|
|
Ok(None) => {
|
|
self.write_void(dst);
|
|
}
|
|
Err(e) => {
|
|
return Err(self.err_with_context(
|
|
&format!("PluginInvoke {}.{}", p.box_type, method),
|
|
&format!("{:?}", e)
|
|
))
|
|
}
|
|
}
|
|
Ok(())
|
|
} else if method == "toString" {
|
|
self.write_string(dst, recv_box.to_string_box().value);
|
|
Ok(())
|
|
} else {
|
|
Err(self.err_method_not_found(&recv_box.type_name(), method))
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_box_call(
|
|
&mut self,
|
|
dst: Option<ValueId>,
|
|
box_val: ValueId,
|
|
method: &str,
|
|
args: &[ValueId],
|
|
) -> Result<(), VMError> {
|
|
// Dev-safe: stringify(Void) → "null" (最小安全弁)
|
|
if method == "stringify" {
|
|
if let VMValue::Void = self.reg_load(box_val)? {
|
|
self.write_string(dst, "null".to_string());
|
|
return Ok(());
|
|
}
|
|
if let VMValue::BoxRef(b) = self.reg_load(box_val)? {
|
|
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
|
self.write_string(dst, "null".to_string());
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
// Trace: method call (class inferred from receiver)
|
|
if Self::box_trace_enabled() {
|
|
let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) {
|
|
VMValue::BoxRef(b) => {
|
|
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
|
inst.class_name.clone()
|
|
} else {
|
|
b.type_name().to_string()
|
|
}
|
|
}
|
|
VMValue::String(_) => "StringBox".to_string(),
|
|
VMValue::Integer(_) => "IntegerBox".to_string(),
|
|
VMValue::Float(_) => "FloatBox".to_string(),
|
|
VMValue::Bool(_) => "BoolBox".to_string(),
|
|
VMValue::Void => "<Void>".to_string(),
|
|
VMValue::Future(_) => "<Future>".to_string(),
|
|
};
|
|
self.box_trace_emit_call(&cls, method, args.len());
|
|
}
|
|
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
|
|
eprintln!("[vm-trace] handle_box_call: method=trim (pre-dispatch)");
|
|
}
|
|
// Debug: trace length dispatch receiver type before any handler resolution
|
|
if method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
|
let recv = self.reg_load(box_val).unwrap_or(VMValue::Void);
|
|
let type_name = match recv.clone() {
|
|
VMValue::BoxRef(b) => b.type_name().to_string(),
|
|
VMValue::Integer(_) => "Integer".to_string(),
|
|
VMValue::Float(_) => "Float".to_string(),
|
|
VMValue::Bool(_) => "Bool".to_string(),
|
|
VMValue::String(_) => "String".to_string(),
|
|
VMValue::Void => "Void".to_string(),
|
|
VMValue::Future(_) => "Future".to_string(),
|
|
};
|
|
eprintln!("[vm-trace] length dispatch recv_type={}", type_name);
|
|
}
|
|
// Graceful void guard for common short-circuit patterns in user code
|
|
// e.g., `A or not last.is_eof()` should not crash when last is absent.
|
|
match self.reg_load(box_val)? {
|
|
VMValue::Void => {
|
|
if let Some(val) = super::boxes_void_guards::handle_void_method(method) {
|
|
self.write_result(dst, val);
|
|
return Ok(());
|
|
}
|
|
}
|
|
VMValue::BoxRef(ref b) => {
|
|
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
|
if let Some(val) = super::boxes_void_guards::handle_void_method(method) {
|
|
self.write_result(dst, val);
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
if super::boxes_object_fields::try_handle_object_fields(self, dst, box_val, method, args)? {
|
|
trace_dispatch!(method, "object_fields");
|
|
return Ok(());
|
|
}
|
|
// Policy gate: user InstanceBox BoxCall runtime fallback
|
|
// - Prod: disallowed (builder must have rewritten obj.m(...) to a
|
|
// function call). Error here indicates a builder/using materialize
|
|
// miss.
|
|
// - Dev/CI: allowed with WARN to aid diagnosis.
|
|
let mut user_instance_class: Option<String> = None;
|
|
if let VMValue::BoxRef(ref b) = self.reg_load(box_val)? {
|
|
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
|
user_instance_class = Some(inst.class_name.clone());
|
|
}
|
|
}
|
|
if user_instance_class.is_some() && !crate::config::env::vm_allow_user_instance_boxcall() {
|
|
let cls = user_instance_class.unwrap();
|
|
return Err(self.err_invalid(format!(
|
|
"User Instance BoxCall disallowed in prod: {}.{} (enable builder rewrite)",
|
|
cls, method
|
|
)));
|
|
}
|
|
if user_instance_class.is_some() && crate::config::env::vm_allow_user_instance_boxcall() {
|
|
if crate::config::env::cli_verbose() {
|
|
eprintln!(
|
|
"[warn] dev fallback: user instance BoxCall {}.{} routed via VM instance-dispatch",
|
|
user_instance_class.as_ref().unwrap(),
|
|
method
|
|
);
|
|
}
|
|
}
|
|
if super::boxes_instance::try_handle_instance_box(self, dst, box_val, method, args)? {
|
|
trace_dispatch!(method, "instance_box");
|
|
return Ok(());
|
|
}
|
|
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
|
|
eprintln!("[vm-trace] dispatch trying boxes_string");
|
|
}
|
|
if super::boxes_string::try_handle_string_box(self, dst, box_val, method, args)? {
|
|
trace_dispatch!(method, "string_box");
|
|
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
|
|
eprintln!("[vm-trace] dispatch handled by boxes_string");
|
|
}
|
|
return Ok(());
|
|
}
|
|
if super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)? {
|
|
trace_dispatch!(method, "array_box");
|
|
return Ok(());
|
|
}
|
|
if super::boxes_map::try_handle_map_box(self, dst, box_val, method, args)? {
|
|
trace_dispatch!(method, "map_box");
|
|
return Ok(());
|
|
}
|
|
// Narrow safety valve: if 'length' wasn't handled by any box-specific path,
|
|
// treat it as 0 (avoids Lt on Void in common loops). This is a dev-time
|
|
// robustness measure; precise behavior should be provided by concrete boxes.
|
|
if method == "length" {
|
|
trace_dispatch!(method, "fallback(length=0)");
|
|
self.write_result(dst, VMValue::Integer(0));
|
|
return Ok(());
|
|
}
|
|
// Fallback: unique-tail dynamic resolution for user-defined methods
|
|
// Narrowing: restrict to receiver's class when available to avoid
|
|
// accidentally binding methods from unrelated boxes that happen to
|
|
// share the same method name/arity (e.g., JsonScanner.is_eof vs JsonToken.is_eof).
|
|
if let Some(func) = {
|
|
let tail = format!(".{}{}", method, format!("/{}", args.len()));
|
|
let mut cands: Vec<String> = self
|
|
.functions
|
|
.keys()
|
|
.filter(|k| k.ends_with(&tail))
|
|
.cloned()
|
|
.collect();
|
|
// Determine receiver class name when possible
|
|
let recv_cls: Option<String> = match self.reg_load(box_val).ok() {
|
|
Some(VMValue::BoxRef(b)) => {
|
|
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
|
Some(inst.class_name.clone())
|
|
} else { None }
|
|
}
|
|
_ => None,
|
|
};
|
|
if let Some(ref want) = recv_cls {
|
|
let prefix = format!("{}.", want);
|
|
cands.retain(|k| k.starts_with(&prefix));
|
|
}
|
|
if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None }
|
|
} {
|
|
// Build argv: pass receiver as first arg (me)
|
|
let recv_vm = self.reg_load(box_val)?;
|
|
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
|
argv.push(recv_vm);
|
|
for a in args { argv.push(self.reg_load(*a)?); }
|
|
let ret = self.exec_function_inner(&func, Some(&argv))?;
|
|
self.write_result(dst, ret);
|
|
return Ok(());
|
|
}
|
|
|
|
super::boxes_plugin::invoke_plugin_box(self, dst, box_val, method, args)
|
|
}
|
|
|
|
// moved: try_handle_map_box → handlers/boxes_map.rs
|
|
fn try_handle_map_box(
|
|
&mut self,
|
|
dst: Option<ValueId>,
|
|
box_val: ValueId,
|
|
method: &str,
|
|
args: &[ValueId],
|
|
) -> Result<bool, VMError> {
|
|
super::boxes_map::try_handle_map_box(self, dst, box_val, method, args)
|
|
}
|
|
|
|
// moved: try_handle_string_box → handlers/boxes_string.rs
|
|
fn try_handle_string_box(
|
|
&mut self,
|
|
dst: Option<ValueId>,
|
|
box_val: ValueId,
|
|
method: &str,
|
|
args: &[ValueId],
|
|
) -> Result<bool, VMError> {
|
|
super::boxes_string::try_handle_string_box(self, dst, box_val, method, args)
|
|
}
|
|
|
|
// moved: try_handle_array_box → handlers/boxes_array.rs
|
|
fn try_handle_array_box(
|
|
&mut self,
|
|
dst: Option<ValueId>,
|
|
box_val: ValueId,
|
|
method: &str,
|
|
args: &[ValueId],
|
|
) -> Result<bool, VMError> {
|
|
super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)
|
|
}
|
|
|
|
}
|