Files
hakorune/src/backend/mir_interpreter/method_router.rs
nyash-codex 510f4cf523 builder/vm: stabilize json_lint_vm under unified calls
- Fix condition_fn resolution: Value call path + dev safety + stub injection
- VM bridge: handle Method::birth via BoxCall; ArrayBox push/get/length/set direct bridge
- Receiver safety: pin receiver in method_call_handlers to avoid undefined use across blocks
- Local vars: materialize on declaration (use init ValueId; void for uninit)
- Prefer legacy BoxCall for Array/Map/String/user boxes in emit_box_or_plugin_call (stability-first)
- Test runner: update LLVM hint to llvmlite harness (remove LLVM_SYS_180_PREFIX guidance)
- Docs/roadmap: update CURRENT_TASK with unified default-ON + guards

Note: NYASH_DEV_BIRTH_INJECT_BUILTINS=1 can re-enable builtin birth() injection during migration.
2025-09-28 12:19:49 +09:00

192 lines
7.7 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.

/*!
* Method router for MirInterpreter — centralized cross-class reroute and
* narrow special-method fallbacks. Phase 1: minimal extraction from exec.rs
* to keep behavior unchanged while making execution flow easier to reason about.
*/
use super::{MirFunction, MirInterpreter};
use serde_json::json;
use crate::backend::vm::{VMError, VMValue};
#[derive(Debug, Clone)]
struct ParsedSig<'a> {
class: &'a str,
method: &'a str,
arity_str: &'a str,
}
fn parse_method_signature(name: &str) -> Option<ParsedSig<'_>> {
let dot = name.find('.')?;
let slash = name.rfind('/')?;
if dot >= slash { return None; }
let class = &name[..dot];
let method = &name[dot + 1..slash];
let arity_str = &name[slash + 1..];
Some(ParsedSig { class, method, arity_str })
}
fn extract_instance_box_class(arg0: &VMValue) -> Option<String> {
if let VMValue::BoxRef(bx) = arg0 {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
return Some(inst.class_name.clone());
}
}
None
}
fn reroute_to_correct_method(
interp: &mut MirInterpreter,
recv_cls: &str,
parsed: &ParsedSig<'_>,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let target = format!("{}.{}{}", recv_cls, parsed.method, format!("/{}", parsed.arity_str));
if let Some(f) = interp.functions.get(&target).cloned() {
// Debug: emit class-reroute event (dev-only)
crate::debug::hub::emit(
"resolve",
"class-reroute",
interp.cur_fn.as_deref(),
None,
json!({
"recv_cls": recv_cls,
"orig_class": parsed.class,
"method": parsed.method,
"arity": parsed.arity_str,
"target": target,
}),
);
return Some(interp.exec_function_inner(&f, arg_vals));
}
None
}
/// Try mapping special methods to canonical targets (table-driven).
/// Example: toString/0 → str/0互換: stringify/0(prefer instance class, then base class without "Instance" suffix).
fn try_special_reroute(
interp: &mut MirInterpreter,
recv_cls: &str,
parsed: &ParsedSig<'_>,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
// toString → str互換: stringify
if parsed.method == "toString" && parsed.arity_str == "0" {
// Prefer instance class 'str' first, then basestrip trailing "Instance")。なければ 'stringify' を互換で探す
let base = recv_cls.strip_suffix("Instance").unwrap_or(recv_cls);
let candidates = [
format!("{}.str/0", recv_cls),
format!("{}.str/0", base),
format!("{}.stringify/0", recv_cls),
format!("{}.stringify/0", base),
];
for name in candidates.iter() {
if let Some(f) = interp.functions.get(name).cloned() {
// Debug: emit special-reroute event (dev-only)
crate::debug::hub::emit(
"resolve",
"special-reroute",
interp.cur_fn.as_deref(),
None,
json!({
"recv_cls": recv_cls,
"orig_class": parsed.class,
"method": parsed.method,
"arity": parsed.arity_str,
"target": name,
"reason": if name.ends_with(".str/0") { "toString->str" } else { "toString->stringify" },
}),
);
return Some(interp.exec_function_inner(&f, arg_vals));
}
}
}
// equals passthrough (instance/base)
// In some user setups, only base class provides equals(other).
// Try instance first, then base (strip trailing "Instance").
if parsed.method == "equals" && parsed.arity_str == "1" {
let base = recv_cls.strip_suffix("Instance").unwrap_or(recv_cls);
let candidates = [
format!("{}.equals/1", recv_cls),
format!("{}.equals/1", base),
];
for name in candidates.iter() {
if let Some(f) = interp.functions.get(name).cloned() {
crate::debug::hub::emit(
"resolve",
"special-reroute",
interp.cur_fn.as_deref(),
None,
json!({
"recv_cls": recv_cls,
"orig_class": parsed.class,
"method": parsed.method,
"arity": parsed.arity_str,
"target": name,
"reason": "equals-fallback",
}),
);
return Some(interp.exec_function_inner(&f, arg_vals));
}
}
}
None
}
fn try_special_method(
recv_cls: &str,
parsed: &ParsedSig<'_>,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
// Keep narrow fallbacks minimal, deterministic, and cheap.
if parsed.method == "is_eof" && parsed.arity_str == "0" {
if let Some(args) = arg_vals {
if let VMValue::BoxRef(bx) = &args[0] {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if recv_cls == "JsonToken" {
let is = match inst.get_field_ng("type") {
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
_ => false,
};
return Some(Ok(VMValue::Bool(is)));
}
if recv_cls == "JsonScanner" {
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
return Some(Ok(VMValue::Bool(pos >= len)));
}
}
}
}
}
None
}
/// Pre-execution reroute/short-circuit.
///
/// When a direct Call to "Class.method/N" is about to execute, verify that the
/// first argument ('me') actually belongs to the same InstanceBox class. If it
/// does not, try rerouting to the matching class method. If no matching method
/// exists, apply a very narrow fallback for well-known methods (dev-oriented,
/// but safe and deterministic) and return a value. Returning Some(Result<..>)
/// indicates that the router handled the call (rerouted or short-circuited).
/// Returning None means normal execution should continue.
pub(super) fn pre_exec_reroute(
interp: &mut MirInterpreter,
func: &MirFunction,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let args = match arg_vals { Some(a) => a, None => return None };
if args.is_empty() { return None; }
let parsed = match parse_method_signature(func.signature.name.as_str()) { Some(p) => p, None => return None };
let recv_cls = match extract_instance_box_class(&args[0]) { Some(c) => c, None => return None };
// Always consider special re-routes (e.g., toString→stringify) even when class matches
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
if recv_cls == parsed.class { return None; }
// Class mismatch: reroute to same method on the receiver's class
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
// Narrow special fallback (e.g., is_eof)
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); }
None
}