shape_vm/executor/objects/mod.rs
1//! Object and array operations for the VM executor.
2//!
3//! Handles: NewArray, NewObject, GetProp, SetProp, Length, ArrayPush, ArrayPop,
4//! MakeClosure, MergeObject, NewTypedObject, TypedMergeObject, CallMethod, MakeRange,
5//! WrapTypeAnnotation, SliceAccess.
6//!
7//! ## Wave 6.5 substep-2 (D-objects-mod) — SURFACE
8//!
9//! This file is the dispatch shell for generic-object opcodes. The substep-1
10//! shim deletion (`push_raw_u64` / `pop_raw_u64` / `push_native_i64` /
11//! `stack_read_owned` / `stack_peek_raw`) bound this territory at 39 mandatory
12//! shim sites. The pre-Wave-6 file body, however, is built on top of types and
13//! helpers that the strict-typing bulldozer **already deleted before
14//! substep-1** — it does not compile against the current `shape-value` crate
15//! and cannot be migrated by mechanical shim rename:
16//!
17//! - `shape_value::ValueWord` / `shape_value::ValueWordExt`
18//! (deleted — see `crates/shape-value/src/lib.rs`'s post-bulldozer header).
19//! - `shape_value::value_word_drop::vw_drop` /
20//! `shape_value::value_word_drop::vw_clone`
21//! (deleted — replaced by `clone_with_kind` / `drop_with_kind` keyed on
22//! `NativeKind`, ADR-006 §2.7.7).
23//! - `ValueWord::from_raw_bits` / `ValueWord::from_*` /
24//! `ValueWord::into_raw_bits` (constructors and accessors all gone with the
25//! type itself).
26//! - `as_heap_ref()` (forbidden — playbook §4 #7; replaced by
27//! `slot.as_heap_value()` on `KindedSlot::slot`).
28//! - `tag_bits::*` / `is_tagged()` / the deleted W-series ValueWord
29//! synthesizer (forbidden — playbook §4 #7).
30//!
31//! On top of those, the `MethodHandler` ABI itself was **kind-less in
32//! both directions** pre-Wave-γ. ADR-006 §2.7.9 / Q11 (Wave-γ
33//! `G-method-fn-v2-abi`) flipped `MethodFnV2` to
34//! `fn(&mut VM, &[KindedSlot], _) -> Result<KindedSlot, VMError>` —
35//! the kinded carrier slice form per §2.7.1 case 4. The dispatch
36//! shell now sources every kind from the §2.7.7 stack parallel-
37//! `Vec<NativeKind>` track via `pop_kinded()` (no fabrication), and
38//! pushes the returned `KindedSlot` via `push_kinded()` (kind from
39//! the handler-returned carrier — no fabrication). The Bool-default
40//! rationalization the W-series formalized is no longer reachable.
41//! With the ABI in place this dispatch shell becomes a mechanical
42//! `pop_kinded` / `push_kinded` / `slot.as_heap_value()` rewrite per
43//! playbook §10 D-objects-mod row — Wave-γ-followup territory.
44//!
45//! Cross-cluster dependencies for the architectural close-out:
46//!
47//! 1. `D-raw-helpers` rewrites/deletes `objects/raw_helpers.rs` (currently
48//! the carrier for `tag_bits::*` and `extract_heap_ref`). Every Cluster D
49//! sibling file (`property_access.rs`, `array_operations.rs`,
50//! `array_joins.rs`, `concurrency_methods.rs`, `channel_methods.rs`,
51//! `number_methods.rs`, etc.) calls `extract_heap_ref(args[0])` for
52//! HeapValue dispatch — same shape needed here for the receiver bits.
53//! 2. Wave-γ-followup body migration: per ADR-006 §2.7.9 / Q11 the
54//! `MethodFnV2` ABI is kinded (`&[KindedSlot]` /
55//! `Result<KindedSlot, VMError>`); ~150 PHF handler bodies stayed
56//! `NotImplemented(SURFACE)` after the ABI flip (Wave-γ
57//! `G-method-fn-v2-abi` close) and are migrated body-by-body in
58//! follow-up sub-clusters per the M-datatable Wave-β `joins.rs`
59//! precedent at close commit `eb78699`.
60//! 3. The remaining `ValueWord::from_*` heap-construction sites
61//! (`ValueWord::from_heap_value(HeapValue::Range { .. })`,
62//! `ValueWord::from_type_annotated_value`, `ValueWord::from_array`, etc.)
63//! rewrite to `Arc::into_raw + push_kinded(_, NativeKind::Ptr(HeapKind::*))`
64//! per playbook §3 per-`HeapKind` push pattern.
65//!
66//! Per playbook §7.4 ("File compiles cleanly OR un-compiling sites have a
67//! documented surface") and §8 surface-and-stop trigger ("Cross-cluster
68//! migration cascade"), this file's bodies are replaced with
69//! `VMError::NotImplemented(SURFACE: ...)` placeholders documenting the
70//! cascade. Function signatures and module declarations are preserved so
71//! external callers (`dispatch.rs`, `additional/mod.rs`, `compiler/*`)
72//! continue to compile.
73//!
74//! ## Migration status snapshot (substep-2 close)
75//!
76//! - Mandatory shim hits: 0 (the 39 `push_raw_u64` / `pop_raw_u64` call sites
77//! are gone — they were inside the bodies that this commit replaces with
78//! surface markers).
79//! - Sibling shim hits: 0 (none in pre-existing file; verified at audit).
80//! - Forbidden-pattern carry-overs: 0 (`ValueWord`, `as_heap_ref`, `vw_drop`,
81//! `value_word_drop`, `as_vw_ref`, `tag_bits`, and the deleted ValueWord
82//! synthesizer are all gone; the `extract_heap_ref` import lived in the
83//! now-deleted bodies and is not reintroduced).
84//! - Surfaces: 6 (`exec_objects` opcode dispatch + 5 method-dispatch entries:
85//! `op_call_method`, `op_make_range`, `op_wrap_type_annotation`,
86//! `dispatch_method_handler`, plus the v2 typed-array PHF fast path baked
87//! into `op_call_method`).
88//!
89//! See `docs/cluster-audits/phase-1b-vm-wave-6-5-playbook.md` §10 row
90//! `D-objects-mod`, §7.4, §8, and ADR-006 §2.7.6 (Q8) / §2.7.7 (Q9).
91
92// PHF method registry
93pub mod method_registry;
94// Raw u64 extraction helpers (v2 — no ValueWord) — D-raw-helpers territory.
95pub mod raw_helpers;
96
97// Property access operations (GetProp, SetProp, Length) — D-prop-access territory.
98pub mod property_access;
99
100// Object creation operations (NewArray, NewObject, NewTypedObject) — D-obj-create territory.
101pub mod object_creation;
102
103// Object merge operations (MergeObject, TypedMergeObject) — D-obj-tail territory.
104pub mod object_operations;
105
106// Array operations (ArrayPush, ArrayPop, SliceAccess) — D-array-ops territory.
107pub mod array_operations;
108
109// Array method modules.
110pub mod array_aggregation;
111pub mod array_basic;
112pub mod array_joins;
113pub mod array_query;
114pub mod array_sets;
115pub mod array_sort;
116pub mod array_transform;
117
118// DataTable method handlers.
119pub mod datatable_methods;
120
121// (W15-column, 2026-05-10) `column_methods` deleted: ADR-006 §2.7.21 / Q22.
122// `Column` is not a surviving `HeapKind` variant — its semantics are
123// absorbed by `HeapKind::TableView` + `TableViewData::ColumnRef` (see
124// `crates/shape-value/src/heap_value.rs`). The previous file held 11
125// surface-only stubs and a stale PHF map; both are removed.
126
127// IndexedTable method handlers.
128pub mod indexed_table_methods;
129
130// HashMap method handlers.
131pub mod hashmap_methods;
132
133// Set method handlers.
134pub mod deque_methods;
135pub mod priority_queue_methods;
136pub mod set_methods;
137
138// Number method handlers.
139pub mod number_methods;
140
141// String method handlers.
142pub mod string_methods;
143
144// Content method handlers.
145pub mod content_methods;
146
147// DateTime method handlers.
148pub mod datetime_methods;
149
150// Instant method handlers.
151pub mod instant_methods;
152
153// Matrix method handlers.
154pub mod matrix_methods;
155
156// Iterator method handlers.
157pub mod iterator_methods;
158
159// Range method handlers (W15-range, ADR-006 §2.7.23 / Q24, 2026-05-10).
160pub mod range_methods;
161
162// Typed array (Vec<int>, Vec<number>, Vec<bool>) method handlers.
163pub mod typed_array_methods;
164
165// W16.2-J.1 (2026-05-22): the V0.c per-kind typed-array handler modules
166// (`typed_int_array_methods` + `typed_number_array_methods`, 667 LoC
167// combined) DELETED. The kind-generic counterparts in
168// `array_aggregation::handle_{sum,avg,min,max,count,reduce}_v2` +
169// `array_basic::handle_{len,is_empty,first,last,push,pop,get,set,clone}_v2`
170// (registered in `ARRAY_METHODS` and gained real bodies via W16.2-J.0,
171// commit `fbe86020`) cover every method the per-kind handlers used to
172// host. See `method_registry.rs` for the deletion banner.
173
174// Concurrency primitive (Mutex<T>, Atomic<T>, Lazy<T>) method handlers.
175pub mod concurrency_methods;
176
177// Channel (MPSC sender/receiver) method handlers.
178pub mod channel_methods;
179
180// Concatenation opcodes (StringConcat, ArrayConcat) — dedicated v2 replacements
181// for the generic Add overload on built-in heap types.
182pub mod concat;
183
184// Typed HashMap and String access opcodes — local-slot based, skip HeapValue dispatch.
185pub mod typed_access;
186
187use crate::{
188 bytecode::{Instruction, OpCode, Operand},
189 executor::VirtualMachine,
190};
191use shape_value::{HeapKind, HeapValue, KindedSlot, NativeKind, TemporalData, ValueSlot, VMError};
192
193/// Select the method-registry PHF lookup for a v2-raw `TypedArray<T>`
194/// receiver, classified by its stamped element-type discriminant.
195///
196/// **W16.2-J.1 (2026-05-22):** the per-kind PHF registries
197/// `TYPED_INT_ARRAY_METHODS` + `TYPED_NUMBER_ARRAY_METHODS` were deleted
198/// alongside their handler module files (`typed_int_array_methods.rs` /
199/// `typed_number_array_methods.rs`, 667 LoC combined). The prereq W16.2-J.0
200/// (commit `fbe86020`) migrated the kind-generic counterparts in
201/// `array_aggregation::handle_{sum,avg,min,max,count,reduce}_v2` +
202/// `array_basic::handle_{len,is_empty,first,last,push,pop,get,set,clone}_v2`
203/// from `ckpt[2-5]_surface` stubs to real bodies delegating to the
204/// `v2_array_detect::{sum,avg,min,max,push,pop,read,write}_element(s)`
205/// primitives — those entries live in `ARRAY_METHODS`.
206///
207/// Result: every numeric `V2ElemType` arm now returns `None`; the caller
208/// falls back to `ARRAY_METHODS` for the kind-generic implementation. The
209/// surviving non-`None` arm is `V2ElemType::Bool` → `BOOL_ARRAY_METHODS`,
210/// which carries closure-callback / aggregation residuals (`count`, `any`,
211/// `all`, `toArray`) tracked by the W17 typed-carrier-monomorphization
212/// workstream and still routes through the bool-specific handler set.
213fn typed_array_method_registry(
214 elem_type: crate::executor::v2_handlers::v2_array_detect::V2ElemType,
215 method_name: &str,
216) -> Option<method_registry::MethodHandler> {
217 use crate::executor::v2_handlers::v2_array_detect::V2ElemType;
218 match elem_type {
219 // Numeric element kinds — fall through to ARRAY_METHODS via the
220 // caller's `.or_else(...)` chain. W16.2-J.1 deleted the per-kind
221 // PHFs that previously lived here; the kind-generic
222 // `array_aggregation::*` / `array_basic::*` handlers in
223 // ARRAY_METHODS now cover len/length/push/pop/first/last/get/set/
224 // sum/avg/mean/min/max/clone uniformly.
225 V2ElemType::I64
226 | V2ElemType::I32
227 | V2ElemType::I8
228 | V2ElemType::U8
229 | V2ElemType::I16
230 | V2ElemType::U16
231 | V2ElemType::U32
232 | V2ElemType::F64
233 | V2ElemType::F32 => None,
234 // Bool carries closure-callback / aggregation residuals
235 // (count / any / all / toArray) — W17 typed-carrier-
236 // monomorphization territory. The kind-generic len/first/last/
237 // isEmpty entries in BOOL_ARRAY_METHODS still alias to
238 // `array_basic::handle_*_v2`, so semantics are uniform with
239 // ARRAY_METHODS for those names.
240 V2ElemType::Bool => method_registry::BOOL_ARRAY_METHODS
241 .get(method_name)
242 .copied(),
243 // Char / String / Decimal / TypedObject have no dedicated typed-
244 // array method registry — fall back to generic ARRAY_METHODS.
245 V2ElemType::Char
246 | V2ElemType::String
247 | V2ElemType::Decimal
248 | V2ElemType::TypedObject => None,
249 }
250}
251
252impl VirtualMachine {
253 /// Dispatch shell for object opcodes.
254 ///
255 /// Each opcode arm currently calls into a sibling Cluster D file
256 /// (`object_creation`, `property_access`, `array_operations`, etc.) whose
257 /// own substep-2 migration is in flight under a peer Wave-α sub-cluster.
258 /// The dispatch shell itself is kind-correct because it forwards to the
259 /// per-opcode handler unchanged. The legacy entries that lived directly
260 /// in `objects/mod.rs` (`op_call_method`, `op_wrap_type_annotation`,
261 /// `op_make_range`) are surfaced below — see each function's doc comment
262 /// for the architectural cascade ruling.
263 #[inline(always)]
264 pub(in crate::executor) fn exec_objects(
265 &mut self,
266 instruction: &Instruction,
267 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
268 ) -> Result<(), VMError> {
269 use OpCode::*;
270 match instruction.opcode {
271 NewArray => self.op_new_array(instruction)?,
272 NewTypedArray => self.op_new_typed_array(instruction)?,
273 NewMatrix => self.op_new_matrix(instruction)?,
274 NewObject => self.op_new_object(instruction)?,
275 GetProp => self.op_get_prop(ctx)?,
276 SetProp => self.op_set_prop()?,
277 SetLocalIndex => self.op_set_local_index(instruction)?,
278 SetModuleBindingIndex => self.op_set_module_binding_index(instruction)?,
279 Length => self.op_length()?,
280 ArrayPush => self.op_array_push()?,
281 ArrayPushLocal => self.op_array_push_local(instruction)?,
282 ArrayPop => self.op_array_pop()?,
283 MakeClosure => self.op_make_closure(instruction)?,
284 MergeObject => self.op_merge_object()?,
285 NewTypedObject => self.op_new_typed_object(instruction)?,
286 TypedMergeObject => self.op_typed_merge_object(instruction)?,
287 WrapTypeAnnotation => self.op_wrap_type_annotation(instruction)?,
288 SliceAccess => self.op_slice_access()?,
289 MakeRange => self.op_make_range()?,
290 _ => unreachable!(
291 "exec_objects called with non-object opcode: {:?}",
292 instruction.opcode
293 ),
294 }
295 Ok(())
296 }
297
298 /// SURFACE: WrapTypeAnnotation cannot be migrated in this cluster.
299 ///
300 /// The pre-Wave-6 body popped a `ValueWord` and constructed a
301 /// `ValueWord::from_type_annotated_value(name, inner)` wrapper. Both the
302 /// `ValueWord` type and the `from_type_annotated_value` constructor were
303 /// deleted by the strict-typing bulldozer before substep-1; there is no
304 /// post-§2.7.7 wrapper shape. The annotation-wrap design itself needs
305 /// re-thinking under ADR-006 (annotations as parallel metadata, not as a
306 /// payload tag), which is outside the D-objects-mod sub-cluster's
307 /// territory.
308 ///
309 /// Cross-cluster cascade: the compiler emitter currently produces
310 /// `WrapTypeAnnotation` opcodes; that emit site is in `compiler/` and
311 /// must coordinate with the kinded annotation-metadata model before this
312 /// handler is rewritten.
313 fn op_wrap_type_annotation(&mut self, _instruction: &Instruction) -> Result<(), VMError> {
314 Err(VMError::NotImplemented(
315 "SURFACE: WrapTypeAnnotation depends on the deleted ValueWord wrapper \
316 type. Annotation wrapping needs a kinded redesign (ADR-006 §2.7.6 \
317 / Q8) — see playbook §8 cross-cluster cascade. D-objects-mod scope \
318 does not include the compiler emit site."
319 .into(),
320 ))
321 }
322
323 /// CallMethod dispatch shell (W16-op-call-method close).
324 ///
325 /// ADR-006 §2.7.10 / Q11 dispatch shell — pops the receiver +
326 /// arg-count call args from the §2.7.7 kinded stack, classifies
327 /// the receiver kind to pick the matching PHF method registry,
328 /// dispatches through `MethodFnV2`, and pushes the kinded result.
329 ///
330 /// Body shape per the W7-op-call-value precedent (close commit
331 /// `27812cf`, `executor/control_flow/mod.rs:dispatch_call_value_immediate`):
332 ///
333 /// 1. Pop `arg_count + 1` slots via `pop_kinded()` (receiver
334 /// included). Each pop transfers one share (heap-bearing kinds)
335 /// into the returned `(bits, kind)` pair (WB2.4 retain-on-read,
336 /// §2.7.7); the `KindedSlot::new` carrier takes ownership of
337 /// that share. Pop order is reverse of push order, so reverse
338 /// the vec back to position-aligned order with `args[0]` =
339 /// receiver.
340 /// 2. Decode `arg_count` + method name from
341 /// `Operand::TypedMethodCall { arg_count, string_id, .. }`
342 /// (`bytecode/opcode_defs.rs:2023`). The method name string is
343 /// indexed via `string_id` into `self.program.strings`.
344 /// 3. Classify `args[0].kind` to pick a PHF registry per the
345 /// §2.7.6 / Q8 heterogeneous-kind body pattern. Numeric / Bool
346 /// / String scalars route to the matching scalar registry;
347 /// `Ptr(HeapKind::*)` heap kinds route to the per-heap-kind
348 /// registry, with `HeapKind::TypedArray` sub-classified on the
349 /// inner `TypedArrayData::{I64, F64, Bool, ...}` variant via
350 /// `slot.as_heap_value()` and `HeapKind::Temporal`
351 /// sub-classified on the inner `TemporalData::{DateTime,
352 /// TimeSpan, ...}` variant. The v2 typed-array fast path
353 /// (`UInt64`-tagged raw `*mut TypedArray<T>` pointer) routes
354 /// through `as_v2_typed_array`; post-W16.2-J.1 every numeric
355 /// element kind falls through to the kind-generic
356 /// `ARRAY_METHODS` PHF (per the `typed_array_method_registry`
357 /// helper, which returns `None` for `V2ElemType::{I*, U*, F*}`).
358 /// `V2ElemType::Bool` continues to route via
359 /// `BOOL_ARRAY_METHODS`.
360 /// 4. PHF lookup keyed on `&str` method name returns the
361 /// `MethodFnV2` handler. A miss surfaces a `RuntimeError`
362 /// citing the receiver kind + method name; user-defined
363 /// methods on `HeapValue::TypedObject` fall through to a UFCS
364 /// function-name lookup (`function_name_index`) before the
365 /// final `Unknown method` error. Closure / Future / Reference
366 /// / SharedCell / FilterExpr receivers reject — they are not
367 /// method-call targets.
368 /// 5. Dispatch: `handler(self, &args, ctx)` returns
369 /// `Result<KindedSlot, VMError>`. The `&[KindedSlot]` borrow
370 /// leaves the shares with the carriers in this stack frame —
371 /// handlers borrow each entry per §2.7.10 / Q11 borrow-only
372 /// ABI.
373 /// 6. Push the result via `push_kinded(result.raw(), result.kind())`
374 /// and `std::mem::forget(result)` so the result share transfers
375 /// cleanly to the stack (no double-drop). The `args` carriers
376 /// drop at end of scope; `KindedSlot::Drop` dispatches on kind
377 /// and releases each share via `drop_with_kind` (no bare
378 /// `vw_drop`, no Bool-default fallback).
379 ///
380 /// Forbidden surfaces (per CLAUDE.md "Renames to refuse on sight"
381 /// + ADR-006 §2.7.10 / Q11): `Vec<KindedSlot>` by-move into a
382 /// dispatch helper; `args: &mut [KindedSlot]`; tag-bits decode on
383 /// receiver bits; `is_heap()` probe on raw bits; Bool-default
384 /// fallback for unknown kind; defection-attractor framing on
385 /// the method-dispatch ABI (`MethodFn` / `MethodFnLegacy` /
386 /// `dispatch_method_handler_raw` / `call_handler_with_u64_slice`).
387 ///
388 /// Surfaces remaining (out of W16 territory):
389 /// - **IC fast-path recording / hit**: `method_ic_check` /
390 /// `method_ic_record` already accept the kinded `MethodFnV2`
391 /// transmute (`ic_fast_paths.rs:42-44`) — wiring the IC
392 /// recording at the dispatch shell is a downstream JIT-IC
393 /// follow-up, not a correctness gate. The dispatch shell stays
394 /// correct without IC; the IC adds speed only.
395 /// - **`HeapKind::Closure` receivers** (e.g. closure-as-trait-
396 /// object dispatch). Trait-object dispatch goes through
397 /// `op_dyn_method_call`, not `op_call_method`; the closure arm
398 /// here rejects with a clear error.
399 pub fn op_call_method(
400 &mut self,
401 instruction: &Instruction,
402 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
403 ) -> Result<(), VMError> {
404 // ADR-006 §2.7.10 / Q11: arg_count + method name from operand
405 // (typed dispatch is the only emit shape per
406 // `compiler/expressions/function_calls.rs:2014` / `binary_ops.rs`
407 // / `unary_ops.rs`). Legacy stack-arg-count dispatch is gone.
408 let (arg_count, string_id, _method_id, _receiver_type_tag) = match instruction.operand {
409 Some(Operand::TypedMethodCall {
410 method_id,
411 arg_count,
412 string_id,
413 receiver_type_tag,
414 }) => (
415 arg_count as usize,
416 string_id as usize,
417 method_id,
418 receiver_type_tag,
419 ),
420 _ => return Err(VMError::InvalidOperand),
421 };
422
423 // ADR-006 §2.7.24 Q25.C: when the receiver is a trait object,
424 // route through the DynMethodCall dispatch shell instead of the
425 // standard CallMethod path. This handles the case where the
426 // compiler couldn't determine at compile-time that the receiver
427 // is a `dyn T` (e.g. `let b = a.clone_me()` where `clone_me`
428 // returns `Self` through a `BoxedReturn` thunk — the result is
429 // a trait object but the compiler emits the standard CallMethod
430 // opcode without a `dyn_locals` entry for `b`). Round-2: this
431 // fallback ensures correctness; a future amendment can teach
432 // type-inference to propagate `dyn T` through method-call
433 // result types and emit `DynMethodCall` at the compile site.
434 if self.sp >= arg_count + 1 {
435 let receiver_idx_check = self.sp - arg_count - 1;
436 let (_, receiver_kind_peek) = self.stack_read_kinded_raw(receiver_idx_check);
437 if receiver_kind_peek
438 == NativeKind::Ptr(shape_value::HeapKind::TraitObject)
439 {
440 // Reconstruct the instruction with `arg_count` /
441 // `string_id` operands and call into the dyn dispatch
442 // path. The TypedMethodCall operand layout matches
443 // exactly what `op_dyn_method_call` expects.
444 return self.exec_trait_object_ops(
445 &Instruction::new(
446 crate::bytecode::OpCode::DynMethodCall,
447 Some(Operand::TypedMethodCall {
448 method_id: _method_id,
449 arg_count: arg_count as u16,
450 string_id: string_id as u16,
451 receiver_type_tag: _receiver_type_tag,
452 }),
453 ),
454 ctx,
455 );
456 }
457 }
458
459 // Pop receiver + arg_count call args. Each pop_kinded transfers
460 // one share into the returned (bits, kind); the KindedSlot
461 // carrier takes ownership and releases via drop_with_kind on
462 // scope exit. ADR-006 §2.7.7 WB2.4 retain-on-read.
463 let total = arg_count + 1;
464 let mut args: Vec<KindedSlot> = Vec::with_capacity(total);
465 for _ in 0..total {
466 let (bits, kind) = self.pop_kinded()?;
467 args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
468 }
469 // Pop is reverse of push order; flip so args[0] is the receiver.
470 args.reverse();
471
472 // Resolve method name. The string pool index was offset-fixed
473 // at link time (`executor/mod.rs:883`), so direct indexing is
474 // always in-range for a well-formed program. We clone into an
475 // owned `String` to release the immutable borrow on
476 // `self.program.strings` before the `dispatch_method_kinded`
477 // call below takes a mutable borrow on `self`.
478 let method_name: String = self
479 .program
480 .strings
481 .get(string_id)
482 .cloned()
483 .ok_or_else(|| {
484 VMError::RuntimeError(format!(
485 "op_call_method: string_id {} out of bounds (pool size {})",
486 string_id,
487 self.program.strings.len()
488 ))
489 })?;
490
491 // Classify the receiver, resolve the handler, and dispatch via
492 // the shared `dispatch_method_kinded` entry — borrow-only ABI per
493 // §2.7.10 / Q11. The handler borrows each KindedSlot; share
494 // ownership stays with the carriers in `args`.
495 let result = self.dispatch_method_kinded(&args, &method_name, ctx)?;
496
497 // Transfer the result share onto the kinded stack. The result
498 // carrier is forgotten so its Drop does not double-release.
499 self.push_kinded(result.raw(), result.kind())?;
500 std::mem::forget(result);
501
502 // `args` carriers drop here. `KindedSlot::Drop` dispatches on
503 // each entry's kind and retires its share via the matching
504 // `Arc::decrement_strong_count::<T>` arm — no bare vw_drop
505 // (forbidden), no Bool-default fallback (forbidden §2.7.7 #9).
506 Ok(())
507 }
508
509 /// Shared method-dispatch entry: resolve the handler via
510 /// [`resolve_method_handler`](Self::resolve_method_handler) and call
511 /// it with the kinded carrier slice.
512 ///
513 /// Two callers consume this entry:
514 ///
515 /// 1. `op_call_method` (above) — VM-side dispatch shell after popping
516 /// the receiver + args from the §2.7.7 stack parallel-kind track.
517 /// 2. `jit_trampoline_call_method` (in
518 /// `crates/shape-vm/src/executor/call_convention.rs`) — the
519 /// §2.7.5 cross-crate stable-FFI consumer that converts the JIT's
520 /// pair-slice form into `&[KindedSlot]` carriers and delegates
521 /// here for the actual dispatch.
522 ///
523 /// `args[0]` is the receiver, `args[1..]` are the call args. Every
524 /// entry's `kind` came from the §2.7.7 parallel-kind track at the
525 /// producing site — no fabrication. The handler borrows each
526 /// `KindedSlot` (§2.7.10 / Q11 borrow-only ABI); share ownership
527 /// stays with the carriers at the caller. The returned `KindedSlot`
528 /// owns its result share — the caller pushes it onto the stack or
529 /// transfers it across the FFI boundary, then `mem::forget`s the
530 /// returned carrier to balance refcounts.
531 pub(crate) fn dispatch_method_kinded(
532 &mut self,
533 args: &[KindedSlot],
534 method_name: &str,
535 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
536 ) -> Result<KindedSlot, VMError> {
537 // Phase 4 (trait Add/AddAssign for user types, 2026-05-16):
538 // Before falling into the PHF-based handler resolution, give
539 // user-defined methods (`impl Trait for X { method m(...) }` and
540 // `impl X { method m(...) }`) a chance to dispatch via UFCS on
541 // the receiver's concrete type name. The compiler registers each
542 // such method under the function name `"{TypeName}::{method}"`
543 // (see `compiler/statements.rs::desugar_impl_method`); we look
544 // that name up in `function_name_index` and, if found, call the
545 // function directly. This makes `a + b` work for `impl Add for
546 // Money` (binary_ops.rs emits `CallMethod("add")` after the
547 // operator-trait check fires), and likewise for any other user-
548 // authored method on a TypedObject.
549 //
550 // The PHF-based fallback below still handles built-in methods on
551 // TypedObject receivers (the `DATATABLE_METHODS` PHF covers the
552 // generic table-shaped methods) — UFCS takes precedence so users
553 // can shadow / extend the built-in surface with their own impls.
554 //
555 // We resolve the candidate function_id WITHOUT consuming `ctx`
556 // first, so we can re-thread `ctx` into the PHF handler when
557 // UFCS declines. The call path takes `ctx` only after the
558 // function_id resolves.
559 if let NativeKind::Ptr(HeapKind::TypedObject) = args[0].kind {
560 if let Some(function_id) = self.resolve_typed_object_ufcs(args, method_name) {
561 return self.invoke_typed_object_ufcs(args, function_id, ctx);
562 }
563 }
564 let handler = self.resolve_method_handler(args, method_name)?;
565 handler(self, args, ctx)
566 }
567
568 /// Resolve a `TypedObject`-receiver method name to a UFCS function id
569 /// (Phase 4 trait Add/AddAssign work, 2026-05-16).
570 ///
571 /// Reads the receiver's `schema_id` (which the v2-raw
572 /// `TypedObjectStorage` exposes at field offset, per
573 /// `heap_value.rs:3497`), looks up the concrete type name in
574 /// `program.type_schema_registry`, and checks
575 /// `function_name_index["{TypeName}::{method}"]`. Returns the
576 /// post-link function id if registered, `None` otherwise.
577 ///
578 /// `compiler/statements.rs::desugar_impl_method` is the producer that
579 /// registers `impl Add for Money { method add(other) ... }` as the
580 /// function `Money::add` in `function_name_index`.
581 ///
582 /// Caller invariant: `args[0].kind == NativeKind::Ptr(HeapKind::TypedObject)`.
583 /// SAFETY: dereferences `args[0].slot.raw()` as `*const TypedObjectStorage`
584 /// per §2.3 typed-Arc invariant + Wave 2 Round 4 D4 ckpt-3 v2-raw
585 /// migration; the borrowed `KindedSlot` in `args[0]` owns one share
586 /// so the pointee stays live for this scope.
587 fn resolve_typed_object_ufcs(
588 &self,
589 args: &[KindedSlot],
590 method_name: &str,
591 ) -> Option<u16> {
592 let receiver_bits = args[0].slot.raw();
593 if receiver_bits == 0 {
594 return None;
595 }
596 // SAFETY: per the caller's invariant the receiver is a
597 // `Ptr(HeapKind::TypedObject)` slot. Slot bits are
598 // `*const TypedObjectStorage` (v2-raw migration per
599 // `heap_value.rs:3497`); the borrowed `KindedSlot` carrier in
600 // `args[0]` owns one share so the pointee stays live for this
601 // scope. Transient borrow — no Arc reconstruction.
602 let schema_id = unsafe {
603 (*(receiver_bits as *const shape_value::TypedObjectStorage)).schema_id
604 };
605 let concrete_type_name = self
606 .program
607 .type_schema_registry
608 .get_by_id(schema_id as u32)
609 .map(|schema| schema.name.clone())?;
610 let function_name = format!("{}::{}", concrete_type_name, method_name);
611 self.function_name_index.get(&function_name).copied()
612 }
613
614 /// Invoke a UFCS-resolved Shape function on a TypedObject receiver +
615 /// args (Phase 4 trait Add/AddAssign work, 2026-05-16).
616 ///
617 /// Pushes receiver + args back onto the kinded stack (cloning shares
618 /// since the borrowed `args` carriers retain ownership of the
619 /// originals — the caller's `KindedSlot::Drop` will release those),
620 /// then sets up a fresh call frame via `call_function_with_nb_args`
621 /// + `execute_until_call_depth`, pops the function's return value
622 /// from the kinded stack, and returns it as a `KindedSlot` whose
623 /// carrier owns the result share.
624 ///
625 /// Mirrors `trait_object_ops.rs::invoke_dyn_unified` for the
626 /// non-Self-arg, non-BoxedReturn case (the typical user-defined
627 /// `impl Add for X { method add(other: X) -> X }` shape).
628 fn invoke_typed_object_ufcs(
629 &mut self,
630 args: &[KindedSlot],
631 function_id: u16,
632 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
633 ) -> Result<KindedSlot, VMError> {
634 // Phase 4 fix (2026-05-16): route through the canonical
635 // `execute_function_by_id` public entry-point — the same pattern
636 // `execute_function_with_named_args` uses for borrowed-args call
637 // sites (`call_convention.rs:211-256`). The earlier hand-rolled
638 // `call_function_with_nb_args + execute_until_call_depth` path
639 // had a non-deterministic double-free that surfaced on `+=`
640 // desugar fixtures (`m = m + Money{...}`); bisect attributed it
641 // to subtle interactions between the manual `self.sp =
642 // base_pointer` adjustment and downstream frame setup. Routing
643 // through the established public entry-point eliminates the
644 // surface — that helper is the §2.7.10/Q11 canonical shape for
645 // "borrowed args, owned-share-per-call invocation".
646 //
647 // Build an owned `Vec<KindedSlot>` for the new frame by bumping
648 // one share per arg via `clone_with_kind` (§2.7.7 WB2.4) — the
649 // borrowed `args` slice's carriers retain ownership of the
650 // originals (op_call_method's `args` carriers drop those at end
651 // of scope), so we mint independent shares for the called
652 // function's locals. `execute_function_by_id` then runs the
653 // standard call protocol: `call_function_with_nb_args` transfers
654 // shares into the new frame, `mem::forget(args)` balances, the
655 // function runs to completion, the return value is popped and
656 // returned as a `KindedSlot` whose carrier owns the result share.
657 let mut call_args: Vec<KindedSlot> = Vec::with_capacity(args.len());
658 for slot in args.iter() {
659 let bits = slot.slot.raw();
660 let kind = slot.kind;
661 crate::executor::vm_impl::stack::clone_with_kind(bits, kind);
662 call_args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
663 }
664 self.execute_function_by_id(function_id, call_args, ctx)
665 }
666
667 /// Resolve a method handler from `(receiver_kind, method_name)`.
668 ///
669 /// Receiver classification per ADR-006 §2.7.6 / Q8 heterogeneous-
670 /// kind body pattern: scalar kinds map directly to scalar PHF
671 /// registries; `Ptr(HeapKind::*)` heap kinds map to the matching
672 /// per-heap-kind registry, with `TypedArray` and `Temporal`
673 /// sub-classified through `slot.as_heap_value()` matching to pick
674 /// the element-typed sub-registry. The `UInt64`-tagged v2 typed-
675 /// array fast path (`*mut TypedArray<T>` pointer with stamped
676 /// element-type byte) routes through `v2_array_detect`.
677 ///
678 /// Returns `Err(RuntimeError)` for unknown method on a known
679 /// receiver kind, or unsupported receiver kind. Falls through to
680 /// `function_name_index` UFCS for `HeapKind::TypedObject`
681 /// receivers when the method is not in `DATATABLE_METHODS` (the
682 /// dispatch table covering generic table-shaped methods is the
683 /// closest fit; user-defined methods land via UFCS).
684 fn resolve_method_handler(
685 &self,
686 args: &[KindedSlot],
687 method_name: &str,
688 ) -> Result<method_registry::MethodHandler, VMError> {
689 use crate::executor::v2_handlers::v2_array_detect::as_v2_typed_array;
690
691 let receiver = &args[0];
692 let kind = receiver.kind;
693
694 // Pure-scalar receivers — kind alone selects the registry.
695 let scalar_handler: Option<method_registry::MethodHandler> = match kind {
696 NativeKind::Float64
697 | NativeKind::NullableFloat64
698 | NativeKind::Int8
699 | NativeKind::NullableInt8
700 | NativeKind::UInt8
701 | NativeKind::NullableUInt8
702 | NativeKind::Int16
703 | NativeKind::NullableInt16
704 | NativeKind::UInt16
705 | NativeKind::NullableUInt16
706 | NativeKind::Int32
707 | NativeKind::NullableInt32
708 | NativeKind::UInt32
709 | NativeKind::NullableUInt32
710 | NativeKind::Int64
711 | NativeKind::NullableInt64
712 | NativeKind::NullableUInt64
713 | NativeKind::IntSize
714 | NativeKind::NullableIntSize
715 | NativeKind::UIntSize
716 | NativeKind::NullableUIntSize => method_registry::NUMBER_METHODS.get(method_name).copied(),
717 NativeKind::Bool => method_registry::BOOL_METHODS.get(method_name).copied(),
718 NativeKind::String => method_registry::STRING_METHODS.get(method_name).copied(),
719 // Round 19 S1.5 W12-nativekind-scalar-additions (2026-05-14):
720 // ADR-006 §2.7.5 amendment adds F32 + Char as scalar variants.
721 // F32 receivers route to NUMBER_METHODS (same numeric method
722 // surface as F64). Char receivers route to CHAR_METHODS — the
723 // existing receiver registry already covers char methods
724 // (`.to_uppercase()`, `.is_alphabetic()`, etc.) and was wired
725 // for the `NativeKind::Ptr(HeapKind::Char)` carrier; the same
726 // method surface applies regardless of which Char carrier
727 // label flows through (both labels store the same codepoint
728 // bits and method bodies read via `as_char` which recognizes
729 // both labels per the §2.7.5 amendment).
730 NativeKind::Float32 => method_registry::NUMBER_METHODS.get(method_name).copied(),
731 NativeKind::Char => method_registry::CHAR_METHODS.get(method_name).copied(),
732 // Wave 2 Agent B W12-StringV2-DecimalV2-NativeKind-additions
733 // (2026-05-14): the v2-raw `*const StringObj` / `*const DecimalObj`
734 // carrier receivers route to the same method registry as their
735 // Arc-wrapped siblings — the method-handler bodies dispatch on
736 // the carrier shape (the slot's kind label drives the per-
737 // carrier read of UTF-8 bytes / Decimal value). Method-handler
738 // body migration for v2-raw reads is the Agent A2 (producer)
739 // / consumer-side cluster-1 hardening territory; this row pins
740 // method-registry selection at the dispatch shell.
741 NativeKind::StringV2 => method_registry::STRING_METHODS.get(method_name).copied(),
742 // DecimalV2 routes to NUMBER_METHODS — same as the Arc-wrapped
743 // `HeapKind::Decimal` sibling per the heap-arm row below.
744 NativeKind::DecimalV2 => method_registry::NUMBER_METHODS.get(method_name).copied(),
745 // r5c-2-β-CKPT-C u64-carrier-disambiguation (2026-05-20):
746 // `Ptr(HeapKind::TypedArray)` is the single canonical carrier
747 // kind for every v2-raw `*mut TypedArray<T>` pointer (direct
748 // `NewTypedArray*` allocation + refcounted struct-field /
749 // closure-capture read). Classify via the stamped element-type
750 // byte → typed-array method registry. A genuine scalar `u64`
751 // (`NativeKind::UInt64`) routes purely to `NUMBER_METHODS` and
752 // is NEVER passed to `as_v2_typed_array` — the pre-fix shared
753 // arm dereferenced an arbitrary scalar `u64` value as a header
754 // pointer → SIGSEGV.
755 NativeKind::Ptr(HeapKind::TypedArray) => {
756 let bits = receiver.slot.raw();
757 if let Some(view) = as_v2_typed_array(bits, kind) {
758 typed_array_method_registry(view.elem_type, method_name)
759 .or_else(|| method_registry::ARRAY_METHODS.get(method_name).copied())
760 } else {
761 // Kind says TypedArray but the bits failed v2 detection —
762 // still an array receiver; fall back to generic methods.
763 method_registry::ARRAY_METHODS.get(method_name).copied()
764 }
765 }
766 NativeKind::UInt64 => {
767 // Genuine scalar `u64` — numeric method surface only.
768 method_registry::NUMBER_METHODS.get(method_name).copied()
769 }
770 NativeKind::Ptr(_) => None,
771 // R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 +
772 // §2.7.7/Q9, 2026-05-19): `NativeKind::Null` receivers have
773 // no method dispatch surface — null has no methods.
774 NativeKind::Null => None,
775 };
776 if let Some(h) = scalar_handler {
777 return Ok(h);
778 }
779
780 // Heap receivers — dispatch on HeapKind, then sub-classify
781 // TypedArray / Temporal via `slot.as_heap_value()`.
782 if let NativeKind::Ptr(hk) = kind {
783 let heap_handler: Option<method_registry::MethodHandler> = match hk {
784 HeapKind::String => method_registry::STRING_METHODS.get(method_name).copied(),
785 HeapKind::Char => method_registry::CHAR_METHODS.get(method_name).copied(),
786 HeapKind::HashMap => method_registry::HASHMAP_METHODS.get(method_name).copied(),
787 HeapKind::HashSet => method_registry::SET_METHODS.get(method_name).copied(),
788 HeapKind::DataTable => method_registry::DATATABLE_METHODS
789 .get(method_name)
790 .copied(),
791 HeapKind::Iterator => method_registry::ITERATOR_METHODS.get(method_name).copied(),
792 HeapKind::Instant => method_registry::INSTANT_METHODS.get(method_name).copied(),
793 HeapKind::Content => method_registry::CONTENT_METHODS.get(method_name).copied(),
794 HeapKind::Decimal => method_registry::NUMBER_METHODS.get(method_name).copied(),
795 HeapKind::BigInt => method_registry::NUMBER_METHODS.get(method_name).copied(),
796 HeapKind::TypedArray => {
797 // V3-S5 ckpt-5: TypedArrayData enum + outer
798 // HeapValue::TypedArray arm DELETED at ckpt-1..ckpt-4.
799 // Sub-classification by inner variant is gone; fall
800 // through to the generic ARRAY_METHODS PHF. Per-element-
801 // kind dispatch lands at ckpt-6 STRICT close via the
802 // v2-raw `TypedArray<T>` direct-access target (caller
803 // classifies element type from the v2 header's
804 // element-type byte instead of the deleted variant).
805 method_registry::ARRAY_METHODS.get(method_name).copied()
806 }
807 // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13):
808 // Matrix is a first-class HeapKind — receivers route
809 // directly to `MATRIX_METHODS` (no inner-TypedArrayData
810 // sub-classification two-step). MatrixSlice receivers
811 // route to `FLOAT_ARRAY_METHODS` (their methods are
812 // numeric-aggregations over a flat f64 region; the same
813 // PHF that handles `F64`-typed arrays applies).
814 HeapKind::Matrix => method_registry::MATRIX_METHODS.get(method_name).copied(),
815 HeapKind::MatrixSlice => method_registry::FLOAT_ARRAY_METHODS
816 .get(method_name)
817 .copied(),
818 HeapKind::Temporal => {
819 // C1-temporal-lowering (Phase 2d Wave 2): Temporal
820 // slots are `Arc::into_raw::<TemporalData>` — NOT a
821 // `Box<HeapValue>` allocation. `as_heap_value()` would
822 // be wrong-type recovery (5-arm receiver-recovery
823 // soundness rule, CLAUDE.md / handover §0). Sub-
824 // classify by directly borrowing `&TemporalData` from
825 // the slot's Arc-raw pointer, mirroring
826 // `objects/datetime_methods.rs::recv_temporal`.
827 //
828 // SAFETY: when receiver.kind == Ptr(HeapKind::Temporal),
829 // receiver.slot.raw() is `Arc::into_raw::<TemporalData>`
830 // (set by `op_push_const::Constant::Duration` /
831 // `Constant::DateTimeExpr` arms, by
832 // `temporal_result()` in datetime_methods.rs, and by
833 // the §2.7.7 stack parallel-kind track). The carrier
834 // owns one strong-count share for the dispatch
835 // duration; the &TemporalData borrow's lifetime is
836 // bounded by `args[0]`'s share ownership.
837 let bits = receiver.slot.raw();
838 if bits == 0 {
839 None
840 } else {
841 let td: &TemporalData =
842 unsafe { &*(bits as *const TemporalData) };
843 match td {
844 TemporalData::DateTime(_) => {
845 method_registry::DATETIME_METHODS
846 .get(method_name)
847 .copied()
848 }
849 TemporalData::TimeSpan(_) | TemporalData::Duration(_) => {
850 method_registry::TIMESPAN_METHODS
851 .get(method_name)
852 .copied()
853 }
854 // Timeframe / TimeReference / DateTimeExpr /
855 // DataDateTimeRef have no method PHF — they
856 // are language-level metadata, not method-
857 // call targets. Fall through to
858 // UnknownMethod.
859 _ => None,
860 }
861 }
862 }
863 HeapKind::TypedObject => {
864 // User-defined object methods land here. The
865 // built-in DataTable PHF covers shared table-shape
866 // methods; UFCS resolution below catches user-
867 // defined `fn TypeName.method(self, ...)` shapes.
868 method_registry::DATATABLE_METHODS
869 .get(method_name)
870 .copied()
871 }
872 HeapKind::TableView => method_registry::DATATABLE_METHODS
873 .get(method_name)
874 .copied(),
875 // Wave 15 W15-deque / W15-channel / W15-priority-queue
876 // closes (ADR-006 §2.7.19/Q20, §2.7.20/Q21, §2.7.18/Q19)
877 // — the new HeapKind ordinals 23/24/25 with their
878 // `*_METHODS` registries.
879 HeapKind::Deque => method_registry::DEQUE_METHODS.get(method_name).copied(),
880 HeapKind::Channel => method_registry::CHANNEL_METHODS.get(method_name).copied(),
881 HeapKind::PriorityQueue => method_registry::PRIORITY_QUEUE_METHODS
882 .get(method_name)
883 .copied(),
884 // W17-concurrency (ADR-006 §2.7.25, 2026-05-11): the
885 // new HeapKind ordinals 30/31/32 with their
886 // MUTEX_METHODS / ATOMIC_METHODS / LAZY_METHODS
887 // registries. Method-receiver classification routes
888 // `m.lock()` / `a.fetch_add(...)` / `l.get()` here.
889 HeapKind::Mutex => method_registry::MUTEX_METHODS.get(method_name).copied(),
890 HeapKind::Atomic => method_registry::ATOMIC_METHODS.get(method_name).copied(),
891 HeapKind::Lazy => method_registry::LAZY_METHODS.get(method_name).copied(),
892 // W15-range close (ADR-006 §2.7.23/Q24): Range receivers
893 // route to the RANGE_METHODS PHF.
894 HeapKind::Range => method_registry::RANGE_METHODS.get(method_name).copied(),
895 // W14-variant-codegen close (ADR-006 §2.7.17/Q18):
896 // Result/Option are typed-Arc carriers; method-call
897 // dispatch goes through op_is_ok / op_unwrap_ok / etc.
898 // typed opcodes, not through the generic method PHF.
899 // No method-PHF arm; falls through to UFCS / unknown.
900 HeapKind::Result | HeapKind::Option => None,
901 // ADR-006 §2.7.10 explicitly excludes the closure /
902 // future / reference / shared-cell / filter-expr
903 // discriminators from method-call dispatch — these are
904 // not user-callable receivers. Trait-object method
905 // calls go through `op_dyn_method_call`, not here —
906 // the compiler-emission tier (W17-trait-object-emission)
907 // emits `DynMethodCall` opcodes that walk the receiver's
908 // `Arc<TraitObjectStorage>::vtable` directly per
909 // ADR-006 §2.7.24 / Q25.C.5 `VTableEntry` shape, NOT
910 // through this generic method PHF.
911 HeapKind::Closure
912 | HeapKind::Future
913 | HeapKind::Reference
914 | HeapKind::SharedCell
915 | HeapKind::FilterExpr
916 | HeapKind::TraitObject
917 | HeapKind::IoHandle
918 | HeapKind::TaskGroup
919 | HeapKind::NativeView
920 | HeapKind::NativeScalar
921 // W17-comptime-vm-dispatch (ADR-006 §2.7.26, 2026-05-12):
922 // ModuleFn references are not user-callable receivers
923 // via method-call dispatch — they route through
924 // op_call_value's `Ptr(HeapKind::ModuleFn)` arm directly
925 // (`invoke_module_fn_id_stub`), not through this generic
926 // PHF lookup.
927 | HeapKind::ModuleFn => None,
928 };
929 if let Some(h) = heap_handler {
930 return Ok(h);
931 }
932 }
933
934 // UFCS / unknown — surface the receiver kind in the error so
935 // call sites can diagnose. Per playbook §3 "surface-and-stop
936 // if PHF lookup API doesn't quite match", an unknown method
937 // is *not* a SURFACE — it's a real runtime error the program
938 // can hit, so we return `RuntimeError`, not `NotImplemented`.
939 Err(VMError::RuntimeError(format!(
940 "no method '{}' on receiver kind {:?}",
941 method_name, kind
942 )))
943 }
944
945 /// `MakeRange` opcode body — pop (start, end, inclusive) from the §2.7.7
946 /// kinded stack and push a fresh `Arc<RangeData>` slot with kind
947 /// `NativeKind::Ptr(HeapKind::Range)` (W15-range, ADR-006 §2.7.23 / Q24).
948 ///
949 /// Stack layout at entry (from `compiler/expressions/misc.rs:369`):
950 ///
951 /// ```ignore
952 /// [.., start_value, end_value, PushConst<Bool>(inclusive), MakeRange]
953 /// ```
954 ///
955 /// Popping order is reverse-push: `inclusive` first, then `end`, then
956 /// `start`. Per the surface syntax, `start_value` and `end_value` are
957 /// `int`-typed expressions (`0..10`); the `PushNull` placeholder for
958 /// open ranges (`..n` / `n..`) reaches this handler with kind
959 /// `NativeKind::Bool` and bits zero (the `PushNull` shape) — open
960 /// ranges are surfaced as a SURFACE error pending the iterator-tier
961 /// semantic (`for i in 0..` infinite loops are their own ADR
962 /// follow-up; matches the pre-strict-typing surface).
963 ///
964 /// Other-kind bounds (Decimal, BigInt, NativeScalar) similarly
965 /// surface — the post-strict-typing `RangeData { start: i64, end: i64,
966 /// .. }` shape only models i64 ranges at landing. Cross-kind range
967 /// bounds are tracked as a follow-up §2.7.23 amendment (mirror of the
968 /// W14 Result/Option payload-cardinality discussion).
969 pub(in crate::executor) fn op_make_range(&mut self) -> Result<(), VMError> {
970 use shape_value::{KindedSlot, NativeKind, ValueSlot, heap_value::RangeData};
971
972 // Pop in reverse-push order: inclusive flag first, then end, then start.
973 // We immediately wrap each pop result in a `KindedSlot` carrier so its
974 // `Drop` impl handles refcount release on every error path automatically
975 // — no manual `drop_with_kind` bookkeeping needed.
976 let incl_kinded = {
977 let (bits, kind) = self.pop_kinded()?;
978 KindedSlot::new(ValueSlot::from_raw(bits), kind)
979 };
980 let end_kinded = {
981 let (bits, kind) = self.pop_kinded()?;
982 KindedSlot::new(ValueSlot::from_raw(bits), kind)
983 };
984 let start_kinded = {
985 let (bits, kind) = self.pop_kinded()?;
986 KindedSlot::new(ValueSlot::from_raw(bits), kind)
987 };
988
989 // The `inclusive` operand is a `PushConst<Bool>` per
990 // `compiler/expressions/misc.rs:362-368`. Kind must be Bool —
991 // any other kind is a kind-source bug at the emit site.
992 let inclusive = match incl_kinded.kind() {
993 NativeKind::Bool => incl_kinded.slot().as_bool(),
994 _ => {
995 return Err(VMError::RuntimeError(
996 "MakeRange: inclusive flag operand must be Bool (kind-source bug \
997 at compile site — `compiler/expressions/misc.rs` emits a \
998 `PushConst<Bool>` for the inclusive flag)".into(),
999 ));
1000 }
1001 };
1002
1003 // Bounds: only i64 supported at landing (ADR-006 §2.7.23). Other
1004 // kinds — Float64 (`0.0..1.0` would-be syntax), Decimal, BigInt,
1005 // NativeScalar — surface for the cross-kind Range payload
1006 // follow-up. Bool with zero bits IS the `PushNull` open-range
1007 // placeholder (`..n` / `n..` / `..`) emitted by the compiler;
1008 // surface that distinctly so the diagnostic is precise.
1009 let to_i64 = |k: &KindedSlot, side: &str| -> Result<i64, VMError> {
1010 match k.kind() {
1011 NativeKind::Int64 => Ok(k.slot().as_i64()),
1012 NativeKind::Bool if k.slot().raw() == 0 => Err(VMError::NotImplemented(format!(
1013 "MakeRange: open-range bound on {side} side (PushNull placeholder) — \
1014 SURFACE: open ranges (`..n` / `n..` / `..`) need the iterator-tier \
1015 infinite-iter semantic per ADR-006 §2.7.23 follow-up. Closed ranges \
1016 (`start..end` / `start..=end`) work today.",
1017 ))),
1018 other => Err(VMError::NotImplemented(format!(
1019 "MakeRange: cross-kind bound on {side} side (got {other:?}) — \
1020 SURFACE: post-strict-typing RangeData only models i64 ranges at \
1021 landing. Cross-kind bounds (Decimal, BigInt, Float64, NativeScalar) \
1022 tracked as ADR-006 §2.7.23 follow-up.",
1023 ))),
1024 }
1025 };
1026
1027 let start = to_i64(&start_kinded, "start")?;
1028 let end = to_i64(&end_kinded, "end")?;
1029
1030 let range = std::sync::Arc::new(RangeData::new(start, end, 1, inclusive));
1031 self.push_kinded_slot(KindedSlot::from_range(range))?;
1032 Ok(())
1033 }
1034}
1035
1036// ═════════════════════════════════════════════════════════════════════════════
1037// Tests removed during D-objects-mod surface.
1038// ═════════════════════════════════════════════════════════════════════════════
1039//
1040// The pre-Wave-6 `v2a_dispatch_tests` module exercised the v2 typed-array PHF
1041// dispatch through `op_call_method` and used `ValueWord::from_native_ptr` /
1042// `ValueWord::from_array` / `ValueWord::from_i64` for receiver construction.
1043// All four constructors are deleted with the type. The tests' canonical
1044// shape (PHF resolution + handler invocation) is independent of the
1045// dispatch shell and fits naturally in `method_registry.rs`'s own test
1046// module once the handler ABI migrates; they are not required to live here.
1047// Re-instated in the post-cascade rewrite under cluster
1048// `E-builtins-backlog` / `D-v2-array-detect`.