(recv_h: u32, name: &str, args: &[Val])
| 40 | } |
| 41 | |
| 42 | fn dispatch_call(recv_h: u32, name: &str, args: &[Val]) -> Result<Val, VmErr> { |
| 43 | with_recv("edge_op call: invalid receiver handle", recv_h, |vm, recv| { |
| 44 | // `__call__` means "invoke `recv` as a callable", letting plugins forward arbitrary Python hooks (lambdas, builtins, classes) through `Handle::call("__call__", args)`. Pushes args + callee then drives `exec_call` so every callable kind (`Extern`, `NativeFn`, `Func`, `BoundMethod`, `Class`, ...) routes through the same dispatch path the VM uses normally. Empty caller-slots are fine because lambdas/hooks that escape a plugin call cannot reference caller-frame locals, they can still capture their own defining scope through the regular Func captures vector. |
| 45 | if name == "__call__" { |
| 46 | // Stack layout for `Call`: callee at the bottom, then positional args (top is the rightmost). `parse_call_args` pops args first, then `exec_call` pops the callee. |
| 47 | let stack_before = vm.stack.len(); |
| 48 | vm.stack.push(recv); |
| 49 | for a in args { vm.stack.push(*a); } |
| 50 | let operand = args.len() as u16; // (num_kw<<8)|num_pos; no kwargs from FFI hooks. |
| 51 | let chunk: &crate::modules::parser::SSAChunk = unsafe { &*(vm.chunk as *const _) }; |
| 52 | let mut empty_slots: [Val; 0] = []; |
| 53 | vm.exec_call(operand, chunk, &mut empty_slots)?; |
| 54 | if vm.stack.len() != stack_before + 1 { |
| 55 | return Err(VmErr::Runtime("edge_op call(__call__): callable left no result")); |
| 56 | } |
| 57 | return vm.stack.pop().ok_or(VmErr::Runtime("edge_op call(__call__): stack drained")); |
| 58 | } |
| 59 | let ty = vm.type_name(recv); |
| 60 | let mid = lookup_method(ty, name).ok_or_else(|| VmErr::Attribute(s!("'", str ty, "' object has no method '", str name, "'")))?; |
| 61 | let stack_before = vm.stack.len(); |
| 62 | dispatch_method(vm, mid, recv, args, &[])?; |
| 63 | if vm.stack.len() != stack_before + 1 { |
| 64 | return Err(VmErr::Runtime("edge_op call: method left no result")); |
| 65 | } |
| 66 | // The length check above guarantees a value is present; `ok_or` keeps the FFI boundary panic-free if a future change drops the invariant. |
| 67 | vm.stack.pop().ok_or(VmErr::Runtime("edge_op call: stack drained mid-dispatch")) |
| 68 | }) |
| 69 | } |
| 70 | |
| 71 | /* GetAttr: module/instance attr, or bind builtin method as BoundMethod. */ |
| 72 | fn dispatch_get_attr(recv_h: u32, name: &str) -> Result<Val, VmErr> { |
no test coverage detected