vm/router: minimal special-method extension (equals/1); toString mapping kept
mir: add TypeCertainty to Callee::Method (diagnostic only); plumb through builder/JSON/printer; backends ignore behaviorally using: confirm unified prelude resolver entry for all runner modes docs: update Callee architecture with certainty; update call-instructions; CURRENT_TASK note tests: quick 40/40 PASS; integration (LLVM) 17/17 PASS
This commit is contained in:
@ -50,10 +50,18 @@ pub fn convert_target_to_callee(
|
||||
.unwrap_or_else(|| "UnknownBox".to_string())
|
||||
});
|
||||
|
||||
// Certainty is Known when origin propagation provides a concrete class name
|
||||
let certainty = if value_origin_newbox.contains_key(&receiver) {
|
||||
crate::mir::definitions::call_unified::TypeCertainty::Known
|
||||
} else {
|
||||
crate::mir::definitions::call_unified::TypeCertainty::Union
|
||||
};
|
||||
|
||||
Ok(Callee::Method {
|
||||
box_name: inferred_box_type,
|
||||
method,
|
||||
receiver: Some(receiver),
|
||||
certainty,
|
||||
})
|
||||
},
|
||||
|
||||
@ -195,4 +203,4 @@ pub fn validate_call_args(
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ pub fn resolve_call_target(
|
||||
box_name: box_name.clone(),
|
||||
method: name.to_string(),
|
||||
receiver: None, // Static method call
|
||||
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ impl MirBuilder {
|
||||
then_branch: ASTNode,
|
||||
else_branch: Option<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Reserve a deterministic join id for debug region labeling
|
||||
let join_id = self.debug_next_join_id();
|
||||
// Heuristic pre-pin: if condition is a comparison, evaluate its operands now and pin them
|
||||
// so that subsequent branches can safely reuse these values across blocks.
|
||||
// This leverages existing variable_map merges (PHI) at the merge block.
|
||||
@ -57,6 +59,8 @@ impl MirBuilder {
|
||||
|
||||
// then
|
||||
self.start_new_block(then_block)?;
|
||||
// Debug region: join then-branch
|
||||
self.debug_push_region(format!("join#{}", join_id) + "/then");
|
||||
// Scope enter for then-branch
|
||||
self.hint_scope_enter(0);
|
||||
let then_ast_for_analysis = then_branch.clone();
|
||||
@ -82,9 +86,13 @@ impl MirBuilder {
|
||||
self.hint_scope_leave(0);
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
// Pop then-branch debug region
|
||||
self.debug_pop_region();
|
||||
|
||||
// else
|
||||
self.start_new_block(else_block)?;
|
||||
// Debug region: join else-branch
|
||||
self.debug_push_region(format!("join#{}", join_id) + "/else");
|
||||
// Scope enter for else-branch
|
||||
self.hint_scope_enter(0);
|
||||
// Materialize all variables at block entry via single-pred Phi (correctness-first)
|
||||
@ -115,11 +123,15 @@ impl MirBuilder {
|
||||
self.hint_scope_leave(0);
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
// Pop else-branch debug region
|
||||
self.debug_pop_region();
|
||||
|
||||
// merge: primary result via helper, then delta-based variable merges
|
||||
// Ensure PHIs are first in the block by suppressing entry pin copies here
|
||||
self.suppress_next_entry_pin_copy();
|
||||
self.start_new_block(merge_block)?;
|
||||
// Debug region: join merge
|
||||
self.debug_push_region(format!("join#{}", join_id) + "/join");
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Pre-analysis: identify then/else assigned var for skip and hints
|
||||
@ -175,6 +187,8 @@ impl MirBuilder {
|
||||
)?;
|
||||
|
||||
self.pop_if_merge();
|
||||
// Pop merge debug region
|
||||
self.debug_pop_region();
|
||||
Ok(result_val)
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,6 +215,54 @@ impl super::MirBuilder {
|
||||
function.signature.return_type = mt;
|
||||
}
|
||||
}
|
||||
// Dev-only verify: NewBox → birth() invariant (warn if missing)
|
||||
if crate::config::env::using_is_dev() {
|
||||
let mut warn_count = 0usize;
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
let insns = &bb.instructions;
|
||||
let mut idx = 0usize;
|
||||
while idx < insns.len() {
|
||||
if let MirInstruction::NewBox { dst, box_type, args } = &insns[idx] {
|
||||
// Skip StringBox (literal optimization path)
|
||||
if box_type != "StringBox" {
|
||||
let expect_tail = format!("{}.birth/{}", box_type, args.len());
|
||||
// Look ahead up to 3 instructions for either BoxCall("birth") on dst or Global(expect_tail)
|
||||
let mut ok = false;
|
||||
let mut j = idx + 1;
|
||||
let mut last_const_name: Option<String> = None;
|
||||
while j < insns.len() && j <= idx + 3 {
|
||||
match &insns[j] {
|
||||
MirInstruction::BoxCall { box_val, method, .. } => {
|
||||
if method == "birth" && box_val == dst { ok = true; break; }
|
||||
}
|
||||
MirInstruction::Const { value, .. } => {
|
||||
if let super::ConstValue::String(s) = value { last_const_name = Some(s.clone()); }
|
||||
}
|
||||
MirInstruction::Call { func, .. } => {
|
||||
// If immediately preceded by matching Const String, accept
|
||||
if let Some(prev) = last_const_name.as_ref() {
|
||||
if prev == &expect_tail { ok = true; break; }
|
||||
}
|
||||
// Heuristic: in some forms, builder may reuse a shared const; best-effort only
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if !ok {
|
||||
eprintln!("[warn] dev verify: NewBox {} at v{} not followed by birth() call (expect {})", box_type, dst, expect_tail);
|
||||
warn_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
if warn_count > 0 {
|
||||
eprintln!("[warn] dev verify: NewBox→birth invariant warnings: {}", warn_count);
|
||||
}
|
||||
}
|
||||
|
||||
module.add_function(function);
|
||||
|
||||
Ok(module)
|
||||
|
||||
@ -125,8 +125,15 @@ impl MirBuilder {
|
||||
if let MirType::Box(bn) = t { class_name_opt = Some(bn.clone()); }
|
||||
}
|
||||
}
|
||||
// Optional dev/ci gate: enable builder-side instance→function rewrite by default
|
||||
// in dev/ci profiles, keep OFF in prod. Allow explicit override via env:
|
||||
// Instance→Function rewrite (obj.m(a) → Box.m/Arity(obj,a))
|
||||
// Phase 2 policy: Only rewrite when receiver class is Known (from origin propagation).
|
||||
let class_known = self.value_origin_newbox.get(&object_value).is_some();
|
||||
// Rationale:
|
||||
// - Keep language surface idiomatic (obj.method()), while executing
|
||||
// deterministically as a direct function call.
|
||||
// - Prod VM forbids user Instance BoxCall fallback by policy; this
|
||||
// rewrite guarantees prod runs without runtime instance-dispatch.
|
||||
// Control:
|
||||
// NYASH_BUILDER_REWRITE_INSTANCE={1|true|on} → force enable
|
||||
// NYASH_BUILDER_REWRITE_INSTANCE={0|false|off} → force disable
|
||||
let rewrite_enabled = {
|
||||
@ -139,27 +146,120 @@ impl MirBuilder {
|
||||
}
|
||||
}
|
||||
};
|
||||
// Emit resolve.try event (dev-only) before making a decision
|
||||
if rewrite_enabled {
|
||||
if let Some(cls) = class_name_opt.clone() {
|
||||
if self.user_defined_boxes.contains(&cls) {
|
||||
let arity = arg_values.len(); // function name arity excludes 'me'
|
||||
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, &method, arity);
|
||||
// Gate: only rewrite when the lowered function actually exists (prevents false rewrites like JsonScanner.length/0)
|
||||
let exists = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&fname)
|
||||
} else { false };
|
||||
if exists {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", method, format!("/{}", arguments.len()));
|
||||
let candidates: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
let recv_cls = class_name_opt.clone().or_else(|| self.value_origin_newbox.get(&object_value).cloned()).unwrap_or_default();
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": recv_cls,
|
||||
"method": method,
|
||||
"arity": arguments.len(),
|
||||
"candidates": candidates,
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"try",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Early special-case: toString → stringify mapping when user function exists
|
||||
if method == "toString" && arguments.len() == 0 {
|
||||
if let Some(ref module) = self.current_module {
|
||||
// Prefer class-qualified stringify if we can infer class
|
||||
if let Some(cls_ts) = class_name_opt.clone() {
|
||||
let stringify_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls_ts, "stringify", 0);
|
||||
if module.functions.contains_key(&stringify_name) {
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("(early) toString→stringify cls={} fname={}", cls_ts, stringify_name));
|
||||
}
|
||||
// DebugHub emit: resolve.choose (early, class)
|
||||
{
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": cls_ts,
|
||||
"method": "toString",
|
||||
"arity": 0,
|
||||
"chosen": stringify_name,
|
||||
"reason": "toString-early-class",
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"choose",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(stringify_name.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &stringify_name);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
// Fallback: unique suffix ".stringify/0" in module
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(".stringify/0"))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let fname = cands.remove(0);
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call cls={} method={} fname={}", cls, method, fname));
|
||||
super::utils::builder_debug_log(&format!("(early) toString→stringify unique-suffix fname={}", fname));
|
||||
}
|
||||
// DebugHub emit: resolve.choose (early, unique)
|
||||
{
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": class_name_opt.clone().unwrap_or_default(),
|
||||
"method": "toString",
|
||||
"arity": 0,
|
||||
"chosen": fname,
|
||||
"reason": "toString-early-unique",
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"choose",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
@ -168,95 +268,41 @@ impl MirBuilder {
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate return type/origin from lowered function signature
|
||||
self.annotate_call_result_from_func_name(dst, &format!("{}.{}{}", cls, method, format!("/{}", arity)));
|
||||
self.annotate_call_result_from_func_name(dst, &fname);
|
||||
return Ok(dst);
|
||||
} else {
|
||||
// Special-case: treat toString as stringify when method not present
|
||||
if method == "toString" && arity == 0 {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let stringify_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "stringify", 0);
|
||||
if module.functions.contains_key(&stringify_name) {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(stringify_name.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &stringify_name);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try alternate naming: <Class>Instance.method/Arity
|
||||
let alt_cls = format!("{}Instance", cls);
|
||||
let alt_fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(&alt_cls, &method, arity);
|
||||
let alt_exists = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&alt_fname)
|
||||
} else { false };
|
||||
if alt_exists {
|
||||
} else if cands.len() > 1 {
|
||||
// Deterministic tie-breaker: prefer JsonNode.stringify/0 over JsonNodeInstance.stringify/0
|
||||
if let Some(pos) = cands.iter().position(|n| n == "JsonNode.stringify/0") {
|
||||
let fname = cands.remove(pos);
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call alt cls={} method={} fname={}", alt_cls, method, alt_fname));
|
||||
super::utils::builder_debug_log(&format!("(early) toString→stringify prefer JsonNode fname={}", fname));
|
||||
}
|
||||
// DebugHub emit: resolve.choose (early, prefer-JsonNode)
|
||||
{
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": class_name_opt.clone().unwrap_or_default(),
|
||||
"method": "toString",
|
||||
"arity": 0,
|
||||
"chosen": fname,
|
||||
"reason": "toString-early-prefer-JsonNode",
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"choose",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(alt_fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &alt_fname);
|
||||
return Ok(dst);
|
||||
} else if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("skip rewrite (no fn): cls={} method={} fname={} alt={} (missing)", cls, method, fname, alt_fname));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback (narrowed): only when receiver class is known, and exactly one
|
||||
// user-defined method matches by name/arity across module, resolve to that.
|
||||
if rewrite_enabled && class_name_opt.is_some() {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", method, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let fname = cands.remove(0);
|
||||
// sanity: ensure the box prefix looks like a user-defined box
|
||||
if let Some((bx, _)) = fname.split_once('.') {
|
||||
if self.user_defined_boxes.contains(bx) {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
@ -265,13 +311,173 @@ impl MirBuilder {
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate from signature if present
|
||||
self.annotate_call_result_from_func_name(dst, &fname);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rewrite_enabled && class_known {
|
||||
if let Some(cls) = class_name_opt.clone() {
|
||||
let from_new_origin = self.value_origin_newbox.get(&object_value).is_some();
|
||||
let allow_new_origin = std::env::var("NYASH_DEV_REWRITE_NEW_ORIGIN").ok().as_deref() == Some("1");
|
||||
let is_user_box = self.user_defined_boxes.contains(&cls);
|
||||
let fname = {
|
||||
let arity = arg_values.len();
|
||||
crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, &method, arity)
|
||||
};
|
||||
let module_has = if let Some(ref module) = self.current_module { module.functions.contains_key(&fname) } else { false };
|
||||
let allow_userbox_rewrite = std::env::var("NYASH_DEV_REWRITE_USERBOX").ok().as_deref() == Some("1");
|
||||
if (is_user_box && (module_has || allow_userbox_rewrite)) || (from_new_origin && allow_new_origin) {
|
||||
let arity = arg_values.len(); // function name arity excludes 'me'
|
||||
// Special-case: toString → stringify mapping (only when present)
|
||||
if method == "toString" && arity == 0 {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let stringify_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "stringify", 0);
|
||||
if module.functions.contains_key(&stringify_name) {
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox toString→stringify cls={} fname={}", cls, stringify_name));
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(stringify_name.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &stringify_name);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: unconditionally rewrite to Box.method/Arity. The target
|
||||
// may be materialized later during lowering of the box; runtime
|
||||
// resolution by name will succeed once the module is finalized.
|
||||
let fname = fname.clone();
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call cls={} method={} fname={}", cls, method, fname));
|
||||
}
|
||||
// Dev WARN when the function is not yet present (materialize pending)
|
||||
if crate::config::env::cli_verbose() {
|
||||
if let Some(ref module) = self.current_module {
|
||||
if !module.functions.contains_key(&fname) {
|
||||
eprintln!(
|
||||
"[warn] rewrite (materialize pending): {} (class={}, method={}, arity={})",
|
||||
fname, cls, method, arity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate and emit resolve.choose
|
||||
let chosen = format!("{}.{}{}", cls, method, format!("/{}", arity));
|
||||
self.annotate_call_result_from_func_name(dst, &chosen);
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": cls,
|
||||
"method": method,
|
||||
"arity": arity,
|
||||
"chosen": chosen,
|
||||
"reason": "userbox-rewrite",
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"choose",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
return Ok(dst);
|
||||
} else {
|
||||
// Not a user-defined box; fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback (narrowed): when exactly one user-defined method matches by
|
||||
// name/arity across the module, resolve to that even if class inference
|
||||
// failed (defensive for PHI/branch cases). This preserves determinism
|
||||
// because we require uniqueness and a user-defined box prefix.
|
||||
if rewrite_enabled && class_known {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", method, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let fname = cands.remove(0);
|
||||
// sanity: ensure the box prefix looks like a user-defined box
|
||||
if let Some((bx, _)) = fname.split_once('.') {
|
||||
if self.user_defined_boxes.contains(bx) {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
let arity_us = arg_values.len();
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate and emit resolve.choose
|
||||
self.annotate_call_result_from_func_name(dst, &fname);
|
||||
let meta = serde_json::json!({
|
||||
"recv_cls": bx,
|
||||
"method": method,
|
||||
"arity": arity_us,
|
||||
"chosen": fname,
|
||||
"reason": "unique-suffix",
|
||||
});
|
||||
let fn_name = self.current_function.as_ref().map(|f| f.signature.name.as_str());
|
||||
let region = self.debug_current_region_id();
|
||||
crate::debug::hub::emit(
|
||||
"resolve",
|
||||
"choose",
|
||||
fn_name,
|
||||
region.as_deref(),
|
||||
meta,
|
||||
);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else fall back to plugin/boxcall path
|
||||
|
||||
Reference in New Issue
Block a user