diff --git a/src/backend/mir_interpreter/handlers/boxes.rs b/src/backend/mir_interpreter/handlers/boxes.rs index 9569c921..6f36321e 100644 --- a/src/backend/mir_interpreter/handlers/boxes.rs +++ b/src/backend/mir_interpreter/handlers/boxes.rs @@ -179,7 +179,7 @@ impl MirInterpreter { } _ => {} } - if self.try_handle_object_fields(dst, box_val, method, args)? { + if super::boxes_object_fields::try_handle_object_fields(self, dst, box_val, method, args)? { if method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { eprintln!("[vm-trace] length dispatch handler=object_fields"); } @@ -286,366 +286,6 @@ impl MirInterpreter { self.invoke_plugin_box(dst, box_val, method, args) } - fn try_handle_object_fields( - &mut self, - dst: Option, - box_val: ValueId, - method: &str, - args: &[ValueId], - ) -> Result { - // Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields - fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue { - use crate::value::NyashValue as NV; - use super::VMValue as VV; - match v { - VV::Integer(i) => NV::Integer(*i), - VV::Float(f) => NV::Float(*f), - VV::Bool(b) => NV::Bool(*b), - VV::String(s) => NV::String(s.clone()), - VV::Void => NV::Void, - VV::Future(_) => NV::Void, // not expected in fields - VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here - } - } - fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue { - use crate::value::NyashValue as NV; - use super::VMValue as VV; - match v { - NV::Integer(i) => VV::Integer(*i), - NV::Float(f) => VV::Float(*f), - NV::Bool(b) => VV::Bool(*b), - NV::String(s) => VV::String(s.clone()), - NV::Null | NV::Void => VV::Void, - NV::Array(_) | NV::Map(_) | NV::Box(_) | NV::WeakBox(_) => VV::Void, - } - } - - match method { - "getField" => { - if args.len() != 1 { - return Err(VMError::InvalidInstruction("getField expects 1 arg".into())); - } - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - let rk = match self.reg_load(box_val) { - Ok(VMValue::BoxRef(ref b)) => format!("BoxRef({})", b.type_name()), - Ok(VMValue::Integer(_)) => "Integer".to_string(), - Ok(VMValue::Float(_)) => "Float".to_string(), - Ok(VMValue::Bool(_)) => "Bool".to_string(), - Ok(VMValue::String(_)) => "String".to_string(), - Ok(VMValue::Void) => "Void".to_string(), - Ok(VMValue::Future(_)) => "Future".to_string(), - Err(_) => "".to_string(), - }; - eprintln!("[vm-trace] getField recv_kind={}", rk); - } - let fname = match self.reg_load(args[0])? { - VMValue::String(s) => s, - v => v.to_string(), - }; - // Prefer InstanceBox internal storage (structural correctness) - if let VMValue::BoxRef(bref) = self.reg_load(box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField instance class={}", inst.class_name); - } - // Special-case bridge: JsonParser.length -> tokens.length() - if inst.class_name == "JsonParser" && fname == "length" { - if let Some(tokens_shared) = inst.get_field("tokens") { - let tokens_box: Box = tokens_shared.share_box(); - if let Some(arr) = tokens_box.as_any().downcast_ref::() { - let len_box = arr.length(); - if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(len_box)); } - return Ok(true); - } - } - } - // First: prefer fields_ng (NyashValue) when present - if let Some(nv) = inst.get_field_ng(&fname) { - // Dev trace: JsonToken field get - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" { - eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv); - } - // Treat complex Box-like values as "missing" for internal storage so that - // legacy obj_fields (which stores BoxRef) is used instead. - // This avoids NV::Box/Array/Map being converted to Void by nv_to_vm. - let is_missing = matches!( - nv, - crate::value::NyashValue::Null - | crate::value::NyashValue::Void - | crate::value::NyashValue::Array(_) - | crate::value::NyashValue::Map(_) - | crate::value::NyashValue::Box(_) - | crate::value::NyashValue::WeakBox(_) - ); - if !is_missing { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv); - } - if let Some(d) = dst { - // Special-case: NV::Box should surface as VMValue::BoxRef - if let crate::value::NyashValue::Box(ref arc_m) = nv { - if let Ok(guard) = arc_m.lock() { - let cloned: Box = guard.clone_box(); - let arc: std::sync::Arc = std::sync::Arc::from(cloned); - self.regs.insert(d, VMValue::BoxRef(arc)); - } else { - self.regs.insert(d, VMValue::Void); - } - } else { - self.regs.insert(d, nv_to_vm(&nv)); - } - } - // Trace get - if Self::box_trace_enabled() { - let kind = match &nv { - crate::value::NyashValue::Integer(_) => "Integer", - crate::value::NyashValue::Float(_) => "Float", - crate::value::NyashValue::Bool(_) => "Bool", - crate::value::NyashValue::String(_) => "String", - crate::value::NyashValue::Null => "Null", - crate::value::NyashValue::Void => "Void", - crate::value::NyashValue::Array(_) => "Array", - crate::value::NyashValue::Map(_) => "Map", - crate::value::NyashValue::Box(_) => "Box", - crate::value::NyashValue::WeakBox(_) => "WeakBox", - }; - self.box_trace_emit_get(&inst.class_name, &fname, kind); - } - return Ok(true); - } else { - // Provide pragmatic defaults for JsonScanner numeric fields - if inst.class_name == "JsonScanner" { - let def = match fname.as_str() { - "position" | "length" => Some(VMValue::Integer(0)), - "line" | "column" => Some(VMValue::Integer(1)), - "text" => Some(VMValue::String(String::new())), - _ => None, - }; - if let Some(v) = def { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v); - } - if let Some(d) = dst { self.regs.insert(d, v); } - return Ok(true); - } - } - } - } else { - // fields_ng missing entirely → try JsonScanner defaults next, otherwise fallback to legacy/opfields - if inst.class_name == "JsonScanner" { - let def = match fname.as_str() { - "position" | "length" => Some(VMValue::Integer(0)), - "line" | "column" => Some(VMValue::Integer(1)), - "text" => Some(VMValue::String(String::new())), - _ => None, - }; - if let Some(v) = def { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField default(JsonScanner missing) {} -> {:?}", fname, v); - } - if let Some(d) = dst { self.regs.insert(d, v.clone()); } - if Self::box_trace_enabled() { - let kind = match &v { - VMValue::Integer(_) => "Integer", - VMValue::Float(_) => "Float", - VMValue::Bool(_) => "Bool", - VMValue::String(_) => "String", - VMValue::BoxRef(_) => "BoxRef", - VMValue::Void => "Void", - VMValue::Future(_) => "Future", - }; - self.box_trace_emit_get(&inst.class_name, &fname, kind); - } - return Ok(true); - } - } - } - // Finally: legacy fields (SharedNyashBox) for complex values - if let Some(shared) = inst.get_field(&fname) { - if let Some(d) = dst { self.regs.insert(d, VMValue::BoxRef(shared.clone())); } - if Self::box_trace_enabled() { - self.box_trace_emit_get(&inst.class_name, &fname, "BoxRef"); - } - return Ok(true); - } - } - } - let key = self.object_key_for(box_val); - let mut v = self - .obj_fields - .get(&key) - .and_then(|m| m.get(&fname)) - .cloned() - .unwrap_or(VMValue::Void); - // Final safety (dev-only, narrow): if legacy path yields Void for well-known - // JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide - // pragmatic defaults to avoid Void comparisons during bring-up. - if let VMValue::Void = v { - let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1"); - let fn_ctx = self.cur_fn.as_deref().unwrap_or(""); - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname); - } - if guard_on { - let fn_ctx = self.cur_fn.as_deref().unwrap_or(""); - let is_scanner_ctx = matches!( - fn_ctx, - "JsonScanner.is_eof/0" | "JsonScanner.current/0" | "JsonScanner.advance/0" - ); - if is_scanner_ctx { - // Try class-aware default first - if let Ok(VMValue::BoxRef(bref2)) = self.reg_load(box_val) { - if let Some(inst2) = bref2.as_any().downcast_ref::() { - if inst2.class_name == "JsonScanner" { - let fallback = match fname.as_str() { - "position" | "length" => Some(VMValue::Integer(0)), - "line" | "column" => Some(VMValue::Integer(1)), - "text" => Some(VMValue::String(String::new())), - _ => None, - }; - if let Some(val) = fallback { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val); - } - v = val; - } - } - } - } - // Class nameが取得できなかった場合でも、フィールド名で限定的に適用 - if matches!(v, VMValue::Void) { - let fallback2 = match fname.as_str() { - "position" | "length" => Some(VMValue::Integer(0)), - "line" | "column" => Some(VMValue::Integer(1)), - "text" => Some(VMValue::String(String::new())), - _ => None, - }; - if let Some(val2) = fallback2 { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField final_default(class-agnostic) {} -> {:?}", fname, val2); - } - v = val2; - } - } - } - } - } - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - if let VMValue::BoxRef(b) = &v { - eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name()); - } else { - eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v); - } - } - if let Some(d) = dst { self.regs.insert(d, v.clone()); } - if Self::box_trace_enabled() { - let kind = match &v { - VMValue::Integer(_) => "Integer", - VMValue::Float(_) => "Float", - VMValue::Bool(_) => "Bool", - VMValue::String(_) => "String", - VMValue::BoxRef(b) => b.type_name(), - VMValue::Void => "Void", - VMValue::Future(_) => "Future", - }; - // class name unknown here; use receiver type name if possible - let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) { - VMValue::BoxRef(b) => { - if let Some(inst) = b.as_any().downcast_ref::() { - inst.class_name.clone() - } else { b.type_name().to_string() } - } - _ => "".to_string(), - }; - self.box_trace_emit_get(&cls, &fname, kind); - } - Ok(true) - } - "setField" => { - if args.len() != 2 { - return Err(VMError::InvalidInstruction( - "setField expects 2 args".into(), - )); - } - let fname = match self.reg_load(args[0])? { - VMValue::String(s) => s, - v => v.to_string(), - }; - let valv = self.reg_load(args[1])?; - // Dev trace: JsonToken field set - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - if let VMValue::BoxRef(bref) = self.reg_load(box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { - if inst.class_name == "JsonToken" { - eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv); - } - } - } - } - if Self::box_trace_enabled() { - let vkind = match &valv { - VMValue::Integer(_) => "Integer", - VMValue::Float(_) => "Float", - VMValue::Bool(_) => "Bool", - VMValue::String(_) => "String", - VMValue::BoxRef(b) => b.type_name(), - VMValue::Void => "Void", - VMValue::Future(_) => "Future", - }; - let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) { - VMValue::BoxRef(b) => { - if let Some(inst) = b.as_any().downcast_ref::() { - inst.class_name.clone() - } else { b.type_name().to_string() } - } - _ => "".to_string(), - }; - self.box_trace_emit_set(&cls, &fname, vkind); - } - // Prefer InstanceBox internal storage - if let VMValue::BoxRef(bref) = self.reg_load(box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { - // Primitives → 内部保存 - if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) { - let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv)); - return Ok(true); - } - // BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存 - if let VMValue::BoxRef(bx) = &valv { - if let Some(ib) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value)); - return Ok(true); - } - if let Some(fb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value)); - return Ok(true); - } - if let Some(bb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value)); - return Ok(true); - } - if let Some(sb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone())); - return Ok(true); - } - // For complex Box values (InstanceBox/MapBox/ArrayBox...), store into - // legacy fields to preserve identity across clones/gets. - let _ = inst.set_field(fname.as_str(), std::sync::Arc::clone(bx)); - return Ok(true); - } - } - } - let key = self.object_key_for(box_val); - self.obj_fields - .entry(key) - .or_default() - .insert(fname, valv); - Ok(true) - } - _ => Ok(false), - } - } - // moved: try_handle_map_box → handlers/boxes_map.rs fn try_handle_map_box( &mut self, diff --git a/src/backend/mir_interpreter/handlers/boxes_object_fields.rs b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs new file mode 100644 index 00000000..c34a870d --- /dev/null +++ b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs @@ -0,0 +1,362 @@ +use super::*; +use crate::box_trait::NyashBox; + +pub(super) fn try_handle_object_fields( + this: &mut MirInterpreter, + dst: Option, + box_val: ValueId, + method: &str, + args: &[ValueId], +) -> Result { + // Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields + fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue { + use crate::value::NyashValue as NV; + use super::VMValue as VV; + match v { + VV::Integer(i) => NV::Integer(*i), + VV::Float(f) => NV::Float(*f), + VV::Bool(b) => NV::Bool(*b), + VV::String(s) => NV::String(s.clone()), + VV::Void => NV::Void, + VV::Future(_) => NV::Void, // not expected in fields + VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here + } + } + fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue { + use crate::value::NyashValue as NV; + use super::VMValue as VV; + match v { + NV::Integer(i) => VV::Integer(*i), + NV::Float(f) => VV::Float(*f), + NV::Bool(b) => VV::Bool(*b), + NV::String(s) => VV::String(s.clone()), + NV::Null | NV::Void => VV::Void, + NV::Array(_) | NV::Map(_) | NV::Box(_) | NV::WeakBox(_) => VV::Void, + } + } + + match method { + "getField" => { + if args.len() != 1 { + return Err(VMError::InvalidInstruction("getField expects 1 arg".into())); + } + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + let rk = match this.reg_load(box_val) { + Ok(VMValue::BoxRef(ref b)) => format!("BoxRef({})", b.type_name()), + Ok(VMValue::Integer(_)) => "Integer".to_string(), + Ok(VMValue::Float(_)) => "Float".to_string(), + Ok(VMValue::Bool(_)) => "Bool".to_string(), + Ok(VMValue::String(_)) => "String".to_string(), + Ok(VMValue::Void) => "Void".to_string(), + Ok(VMValue::Future(_)) => "Future".to_string(), + Err(_) => "".to_string(), + }; + eprintln!("[vm-trace] getField recv_kind={}", rk); + } + let fname = match this.reg_load(args[0])? { + VMValue::String(s) => s, + v => v.to_string(), + }; + // Prefer InstanceBox internal storage (structural correctness) + if let VMValue::BoxRef(bref) = this.reg_load(box_val)? { + if let Some(inst) = bref.as_any().downcast_ref::() { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField instance class={}", inst.class_name); + } + // Special-case bridge: JsonParser.length -> tokens.length() + if inst.class_name == "JsonParser" && fname == "length" { + if let Some(tokens_shared) = inst.get_field("tokens") { + let tokens_box: Box = tokens_shared.share_box(); + if let Some(arr) = tokens_box.as_any().downcast_ref::() { + let len_box = arr.length(); + if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(len_box)); } + return Ok(true); + } + } + } + // First: prefer fields_ng (NyashValue) when present + if let Some(nv) = inst.get_field_ng(&fname) { + // Dev trace: JsonToken field get + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" { + eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv); + } + // Treat complex Box-like values as "missing" for internal storage so that + // legacy obj_fields (which stores BoxRef) is used instead. + // This avoids NV::Box/Array/Map being converted to Void by nv_to_vm. + let is_missing = matches!( + nv, + crate::value::NyashValue::Null + | crate::value::NyashValue::Void + | crate::value::NyashValue::Array(_) + | crate::value::NyashValue::Map(_) + | crate::value::NyashValue::Box(_) + | crate::value::NyashValue::WeakBox(_) + ); + if !is_missing { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv); + } + if let Some(d) = dst { + // Special-case: NV::Box should surface as VMValue::BoxRef + if let crate::value::NyashValue::Box(ref arc_m) = nv { + if let Ok(guard) = arc_m.lock() { + let cloned: Box = guard.clone_box(); + let arc: std::sync::Arc = std::sync::Arc::from(cloned); + this.regs.insert(d, VMValue::BoxRef(arc)); + } else { + this.regs.insert(d, VMValue::Void); + } + } else { + this.regs.insert(d, nv_to_vm(&nv)); + } + } + // Trace get + if MirInterpreter::box_trace_enabled() { + let kind = match &nv { + crate::value::NyashValue::Integer(_) => "Integer", + crate::value::NyashValue::Float(_) => "Float", + crate::value::NyashValue::Bool(_) => "Bool", + crate::value::NyashValue::String(_) => "String", + crate::value::NyashValue::Null => "Null", + crate::value::NyashValue::Void => "Void", + crate::value::NyashValue::Array(_) => "Array", + crate::value::NyashValue::Map(_) => "Map", + crate::value::NyashValue::Box(_) => "Box", + crate::value::NyashValue::WeakBox(_) => "WeakBox", + }; + this.box_trace_emit_get(&inst.class_name, &fname, kind); + } + return Ok(true); + } else { + // Provide pragmatic defaults for JsonScanner numeric fields + if inst.class_name == "JsonScanner" { + let def = match fname.as_str() { + "position" | "length" => Some(VMValue::Integer(0)), + "line" | "column" => Some(VMValue::Integer(1)), + "text" => Some(VMValue::String(String::new())), + _ => None, + }; + if let Some(v) = def { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v); + } + if let Some(d) = dst { this.regs.insert(d, v); } + return Ok(true); + } + } + } + } else { + // fields_ng missing entirely → try JsonScanner defaults next, otherwise fallback to legacy/opfields + if inst.class_name == "JsonScanner" { + let def = match fname.as_str() { + "position" | "length" => Some(VMValue::Integer(0)), + "line" | "column" => Some(VMValue::Integer(1)), + "text" => Some(VMValue::String(String::new())), + _ => None, + }; + if let Some(v) = def { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField default(JsonScanner missing) {} -> {:?}", fname, v); + } + if let Some(d) = dst { this.regs.insert(d, v.clone()); } + if MirInterpreter::box_trace_enabled() { + let kind = match &v { + VMValue::Integer(_) => "Integer", + VMValue::Float(_) => "Float", + VMValue::Bool(_) => "Bool", + VMValue::String(_) => "String", + VMValue::BoxRef(_) => "BoxRef", + VMValue::Void => "Void", + VMValue::Future(_) => "Future", + }; + this.box_trace_emit_get(&inst.class_name, &fname, kind); + } + return Ok(true); + } + } + } + // Finally: legacy fields (SharedNyashBox) for complex values + if let Some(shared) = inst.get_field(&fname) { + if let Some(d) = dst { this.regs.insert(d, VMValue::BoxRef(shared.clone())); } + if MirInterpreter::box_trace_enabled() { + this.box_trace_emit_get(&inst.class_name, &fname, "BoxRef"); + } + return Ok(true); + } + } + } + let key = this.object_key_for(box_val); + let mut v = this + .obj_fields + .get(&key) + .and_then(|m| m.get(&fname)) + .cloned() + .unwrap_or(VMValue::Void); + // Final safety (dev-only, narrow): if legacy path yields Void for well-known + // JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide + // pragmatic defaults to avoid Void comparisons during bring-up. + if let VMValue::Void = v { + let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1"); + let fn_ctx = this.cur_fn.as_deref().unwrap_or(""); + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname); + } + if guard_on { + let fn_ctx = this.cur_fn.as_deref().unwrap_or(""); + let is_scanner_ctx = matches!( + fn_ctx, + "JsonScanner.is_eof/0" | "JsonScanner.current/0" | "JsonScanner.advance/0" + ); + if is_scanner_ctx { + // Try class-aware default first + if let Ok(VMValue::BoxRef(bref2)) = this.reg_load(box_val) { + if let Some(inst2) = bref2.as_any().downcast_ref::() { + if inst2.class_name == "JsonScanner" { + let fallback = match fname.as_str() { + "position" | "length" => Some(VMValue::Integer(0)), + "line" | "column" => Some(VMValue::Integer(1)), + "text" => Some(VMValue::String(String::new())), + _ => None, + }; + if let Some(val) = fallback { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val); + } + v = val; + } + } + } + } + // Class nameが取得できなかった場合でも、フィールド名で限定的に適用 + if matches!(v, VMValue::Void) { + let fallback2 = match fname.as_str() { + "position" | "length" => Some(VMValue::Integer(0)), + "line" | "column" => Some(VMValue::Integer(1)), + "text" => Some(VMValue::String(String::new())), + _ => None, + }; + if let Some(val2) = fallback2 { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] getField final_default(class-agnostic) {} -> {:?}", fname, val2); + } + v = val2; + } + } + } + } + } + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + if let VMValue::BoxRef(b) = &v { + eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name()); + } else { + eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v); + } + } + if let Some(d) = dst { this.regs.insert(d, v.clone()); } + if MirInterpreter::box_trace_enabled() { + let kind = match &v { + VMValue::Integer(_) => "Integer", + VMValue::Float(_) => "Float", + VMValue::Bool(_) => "Bool", + VMValue::String(_) => "String", + VMValue::BoxRef(b) => b.type_name(), + VMValue::Void => "Void", + VMValue::Future(_) => "Future", + }; + // class name unknown here; use receiver type name if possible + let cls = match this.reg_load(box_val).unwrap_or(VMValue::Void) { + VMValue::BoxRef(b) => { + if let Some(inst) = b.as_any().downcast_ref::() { + inst.class_name.clone() + } else { b.type_name().to_string() } + } + _ => "".to_string(), + }; + this.box_trace_emit_get(&cls, &fname, kind); + } + Ok(true) + } + "setField" => { + if args.len() != 2 { + return Err(VMError::InvalidInstruction( + "setField expects 2 args".into(), + )); + } + let fname = match this.reg_load(args[0])? { + VMValue::String(s) => s, + v => v.to_string(), + }; + let valv = this.reg_load(args[1])?; + // Dev trace: JsonToken field set + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + if let VMValue::BoxRef(bref) = this.reg_load(box_val)? { + if let Some(inst) = bref.as_any().downcast_ref::() { + if inst.class_name == "JsonToken" { + eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv); + } + } + } + } + if MirInterpreter::box_trace_enabled() { + let vkind = match &valv { + VMValue::Integer(_) => "Integer", + VMValue::Float(_) => "Float", + VMValue::Bool(_) => "Bool", + VMValue::String(_) => "String", + VMValue::BoxRef(b) => b.type_name(), + VMValue::Void => "Void", + VMValue::Future(_) => "Future", + }; + let cls = match this.reg_load(box_val).unwrap_or(VMValue::Void) { + VMValue::BoxRef(b) => { + if let Some(inst) = b.as_any().downcast_ref::() { + inst.class_name.clone() + } else { b.type_name().to_string() } + } + _ => "".to_string(), + }; + this.box_trace_emit_set(&cls, &fname, vkind); + } + // Prefer InstanceBox internal storage + if let VMValue::BoxRef(bref) = this.reg_load(box_val)? { + if let Some(inst) = bref.as_any().downcast_ref::() { + // Primitives → 内部保存 + if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) { + let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv)); + return Ok(true); + } + // BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存 + if let VMValue::BoxRef(bx) = &valv { + if let Some(ib) = bx.as_any().downcast_ref::() { + let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value)); + return Ok(true); + } + if let Some(fb) = bx.as_any().downcast_ref::() { + let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value)); + return Ok(true); + } + if let Some(bb) = bx.as_any().downcast_ref::() { + let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value)); + return Ok(true); + } + if let Some(sb) = bx.as_any().downcast_ref::() { + let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone())); + return Ok(true); + } + // For complex Box values (InstanceBox/MapBox/ArrayBox...), store into + // legacy fields to preserve identity across clones/gets. + let _ = inst.set_field(fname.as_str(), std::sync::Arc::clone(bx)); + return Ok(true); + } + } + } + let key = this.object_key_for(box_val); + this.obj_fields + .entry(key) + .or_default() + .insert(fname, valv); + Ok(true) + } + _ => Ok(false), + } +} diff --git a/src/backend/mir_interpreter/handlers/mod.rs b/src/backend/mir_interpreter/handlers/mod.rs index db4450ea..ecc231d7 100644 --- a/src/backend/mir_interpreter/handlers/mod.rs +++ b/src/backend/mir_interpreter/handlers/mod.rs @@ -5,6 +5,7 @@ mod boxes; mod boxes_array; mod boxes_string; mod boxes_map; +mod boxes_object_fields; mod calls; mod externals; mod memory;