diff --git a/src/backend/mir_interpreter/handlers/boxes.rs b/src/backend/mir_interpreter/handlers/boxes.rs index 6f36321e..2738534d 100644 --- a/src/backend/mir_interpreter/handlers/boxes.rs +++ b/src/backend/mir_interpreter/handlers/boxes.rs @@ -212,7 +212,7 @@ impl MirInterpreter { ); } } - if self.try_handle_instance_box(dst, box_val, method, args)? { + if super::boxes_instance::try_handle_instance_box(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=instance_box"); } @@ -308,157 +308,6 @@ impl MirInterpreter { super::boxes_string::try_handle_string_box(self, dst, box_val, method, args) } - fn try_handle_instance_box( - &mut self, - dst: Option, - box_val: ValueId, - method: &str, - args: &[ValueId], - ) -> Result { - let recv_vm = self.reg_load(box_val)?; - let recv_box_any: Box = match recv_vm.clone() { - VMValue::BoxRef(b) => b.share_box(), - other => other.to_nyash_box(), - }; - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" { - eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len()); - } - if let Some(inst) = recv_box_any.as_any().downcast_ref::() { - // Development guard: ensure JsonScanner core fields have sensible defaults - if inst.class_name == "JsonScanner" { - // populate missing fields to avoid Void in comparisons inside is_eof/advance - if inst.get_field_ng("position").is_none() { - let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0)); - } - if inst.get_field_ng("length").is_none() { - let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0)); - } - if inst.get_field_ng("line").is_none() { - let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1)); - } - if inst.get_field_ng("column").is_none() { - let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1)); - } - if inst.get_field_ng("text").is_none() { - let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new())); - } - } - // JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch - // birth: do not short-circuit; allow dispatch to lowered function "Class.birth/arity" - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" { - eprintln!( - "[vm-trace] instance-check downcast=ok class={} stringify_present={{class:{}, alt:{}}}", - inst.class_name, - self.functions.contains_key(&format!("{}.stringify/0", inst.class_name)), - self.functions.contains_key(&format!("{}Instance.stringify/0", inst.class_name)) - ); - } - // Resolve lowered method function: "Class.method/arity" - let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len())); - // Alternate naming: "ClassInstance.method/arity" - let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len())); - // Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)" - let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1)); - // Special-case: toString() → stringify/0 if present - // Prefer base class (strip trailing "Instance") stringify when available. - let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() { - let base = inst - .class_name - .strip_suffix("Instance") - .map(|s| s.to_string()); - let base_name = base.unwrap_or_else(|| inst.class_name.clone()); - ( - Some(format!("{}.stringify/0", base_name)), - Some(format!("{}.stringify/0", inst.class_name)), - ) - } else { (None, None) }; - - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]", - inst.class_name, method, args.len(), primary, alt, static_variant - ); - } - - // Prefer stringify for toString() if present (semantic alias). Try instance first, then base. - let func_opt = if let Some(ref sname) = stringify_inst { - self.functions.get(sname).cloned() - } else { None } - .or_else(|| stringify_base.as_ref().and_then(|n| self.functions.get(n).cloned())) - .or_else(|| self.functions.get(&primary).cloned()) - .or_else(|| self.functions.get(&alt).cloned()) - .or_else(|| self.functions.get(&static_variant).cloned()); - - if let Some(func) = func_opt { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name); - } - // Build argv: me + args (works for both instance and static(me, ...)) - let mut argv: Vec = Vec::with_capacity(1 + args.len()); - // 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())); - } - } - argv.push(recv_vm.clone()); - for a in args { argv.push(self.reg_load(*a)?); } - let ret = self.exec_function_inner(&func, Some(&argv))?; - if let Some(d) = dst { self.regs.insert(d, ret); } - return Ok(true); - } else { - // Conservative fallback: search unique function by name tail ".method/arity" - let tail = format!(".{}{}", method, format!("/{}", args.len())); - let mut cands: Vec = self - .functions - .keys() - .filter(|k| k.ends_with(&tail)) - .cloned() - .collect(); - if !cands.is_empty() { - // Always narrow by receiver class prefix (and optional "Instance" suffix) - let recv_cls = inst.class_name.clone(); - let pref1 = format!("{}.", recv_cls); - let pref2 = format!("{}Instance.", recv_cls); - let filtered: Vec = cands - .into_iter() - .filter(|k| k.starts_with(&pref1) || k.starts_with(&pref2)) - .collect(); - if filtered.len() == 1 { - let fname = &filtered[0]; - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname); - } - if let Some(func) = self.functions.get(fname).cloned() { - let mut argv: Vec = 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())); - } - } - argv.push(recv_vm.clone()); - for a in args { argv.push(self.reg_load(*a)?); } - let ret = self.exec_function_inner(&func, Some(&argv))?; - if let Some(d) = dst { self.regs.insert(d, ret); } - return Ok(true); - } - } else if filtered.len() > 1 { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch multiple candidates after narrowing: {:?}", filtered); - } - // Ambiguous: do not dispatch cross-class - } else { - // No same-class candidate: do not dispatch cross-class - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len())); - } - } - } - } - } - Ok(false) - } - // moved: try_handle_array_box → handlers/boxes_array.rs fn try_handle_array_box( &mut self, diff --git a/src/backend/mir_interpreter/handlers/boxes_instance.rs b/src/backend/mir_interpreter/handlers/boxes_instance.rs new file mode 100644 index 00000000..0c8fec9a --- /dev/null +++ b/src/backend/mir_interpreter/handlers/boxes_instance.rs @@ -0,0 +1,153 @@ +use super::*; +use crate::box_trait::NyashBox; + +pub(super) fn try_handle_instance_box( + this: &mut MirInterpreter, + dst: Option, + box_val: ValueId, + method: &str, + args: &[ValueId], +) -> Result { + let recv_vm = this.reg_load(box_val)?; + let recv_box_any: Box = match recv_vm.clone() { + VMValue::BoxRef(b) => b.share_box(), + other => other.to_nyash_box(), + }; + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" { + eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len()); + } + if let Some(inst) = recv_box_any.as_any().downcast_ref::() { + // Development guard: ensure JsonScanner core fields have sensible defaults + if inst.class_name == "JsonScanner" { + // populate missing fields to avoid Void in comparisons inside is_eof/advance + if inst.get_field_ng("position").is_none() { + let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0)); + } + if inst.get_field_ng("length").is_none() { + let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0)); + } + if inst.get_field_ng("line").is_none() { + let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1)); + } + if inst.get_field_ng("column").is_none() { + let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1)); + } + if inst.get_field_ng("text").is_none() { + let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new())); + } + } + // JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch + // birth: do not short-circuit; allow dispatch to lowered function "Class.birth/arity" + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" { + eprintln!( + "[vm-trace] instance-check downcast=ok class={} stringify_present={{class:{}, alt:{}}}", + inst.class_name, + this.functions.contains_key(&format!("{}.stringify/0", inst.class_name)), + this.functions.contains_key(&format!("{}Instance.stringify/0", inst.class_name)) + ); + } + // Resolve lowered method function: "Class.method/arity" + let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len())); + // Alternate naming: "ClassInstance.method/arity" + let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len())); + // Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)" + let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1)); + // Special-case: toString() → stringify/0 if present + // Prefer base class (strip trailing "Instance") stringify when available. + let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() { + let base = inst + .class_name + .strip_suffix("Instance") + .map(|s| s.to_string()); + let base_name = base.unwrap_or_else(|| inst.class_name.clone()); + ( + Some(format!("{}.stringify/0", base_name)), + Some(format!("{}.stringify/0", inst.class_name)), + ) + } else { (None, None) }; + + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!( + "[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]", + inst.class_name, method, args.len(), primary, alt, static_variant + ); + } + + // Prefer stringify for toString() if present (semantic alias). Try instance first, then base. + let func_opt = if let Some(ref sname) = stringify_inst { + this.functions.get(sname).cloned() + } else { None } + .or_else(|| stringify_base.as_ref().and_then(|n| this.functions.get(n).cloned())) + .or_else(|| this.functions.get(&primary).cloned()) + .or_else(|| this.functions.get(&alt).cloned()) + .or_else(|| this.functions.get(&static_variant).cloned()); + + if let Some(func) = func_opt { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name); + } + // Build argv: me + args (works for both instance and static(me, ...)) + let mut argv: Vec = Vec::with_capacity(1 + args.len()); + // 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())); + } + } + argv.push(recv_vm.clone()); + for a in args { argv.push(this.reg_load(*a)?); } + let ret = this.exec_function_inner(&func, Some(&argv))?; + if let Some(d) = dst { this.regs.insert(d, ret); } + return Ok(true); + } else { + // Conservative fallback: search unique function by name tail ".method/arity" + let tail = format!(".{}{}", method, format!("/{}", args.len())); + let mut cands: Vec = this + .functions + .keys() + .filter(|k| k.ends_with(&tail)) + .cloned() + .collect(); + if !cands.is_empty() { + // Always narrow by receiver class prefix (and optional "Instance" suffix) + let recv_cls = inst.class_name.clone(); + let pref1 = format!("{}.", recv_cls); + let pref2 = format!("{}Instance.", recv_cls); + let filtered: Vec = cands + .into_iter() + .filter(|k| k.starts_with(&pref1) || k.starts_with(&pref2)) + .collect(); + if filtered.len() == 1 { + let fname = &filtered[0]; + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname); + } + if let Some(func) = this.functions.get(fname).cloned() { + let mut argv: Vec = 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())); + } + } + argv.push(recv_vm.clone()); + for a in args { argv.push(this.reg_load(*a)?); } + let ret = this.exec_function_inner(&func, Some(&argv))?; + if let Some(d) = dst { this.regs.insert(d, ret); } + return Ok(true); + } + } else if filtered.len() > 1 { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] instance-dispatch multiple candidates after narrowing: {:?}", filtered); + } + // Ambiguous: do not dispatch cross-class + } else { + // No same-class candidate: do not dispatch cross-class + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len())); + } + } + } + } + } + Ok(false) +} diff --git a/src/backend/mir_interpreter/handlers/mod.rs b/src/backend/mir_interpreter/handlers/mod.rs index ecc231d7..928911d1 100644 --- a/src/backend/mir_interpreter/handlers/mod.rs +++ b/src/backend/mir_interpreter/handlers/mod.rs @@ -6,6 +6,7 @@ mod boxes_array; mod boxes_string; mod boxes_map; mod boxes_object_fields; +mod boxes_instance; mod calls; mod externals; mod memory;