Skip to main content

runmat_runtime/
lib.rs

1#![allow(
2    clippy::await_holding_lock,
3    clippy::enum_variant_names,
4    clippy::get_first,
5    clippy::io_other_error,
6    clippy::needless_range_loop,
7    clippy::redundant_closure,
8    clippy::result_large_err,
9    clippy::too_many_arguments,
10    clippy::useless_conversion
11)]
12#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
13
14use runmat_builtins::{BuiltinErrorDescriptor, Value};
15#[cfg(not(target_arch = "wasm32"))]
16use runmat_gc_api::GcPtr;
17
18pub mod dispatcher;
19
20pub mod callsite;
21pub mod console;
22pub mod data;
23pub mod interaction;
24pub mod interrupt;
25pub mod output_context;
26pub mod output_count;
27pub mod source_context;
28
29pub mod builtins;
30pub mod comparison;
31pub mod plotting_hooks;
32pub mod replay;
33pub mod runtime_error;
34pub mod user_functions;
35pub mod warning_store;
36pub mod workspace;
37
38/// Standard result type for runtime builtins.
39pub type BuiltinResult<T> = Result<T, RuntimeError>;
40
41pub const OBJECT_INDEX_PAREN: &str = "()";
42pub const OBJECT_INDEX_BRACE: &str = "{}";
43pub const OBJECT_INDEX_MEMBER: &str = ".";
44pub const CALL_METHOD_BUILTIN_NAME: &str = "call_method";
45pub const CALL_BOUND_METHOD_BUILTIN_NAME: &str = "__runmat_call_bound_method__";
46pub const OBJECT_SUBSREF_METHOD: &str = "subsref";
47pub const OBJECT_SUBSASGN_METHOD: &str = "subsasgn";
48pub(crate) const IDENT_UNDEFINED_FUNCTION: &str = "RunMat:UndefinedFunction";
49
50pub fn object_property_getter_name(field: &str) -> String {
51    format!("get.{field}")
52}
53
54pub fn object_property_setter_name(field: &str) -> String {
55    format!("set.{field}")
56}
57
58pub(crate) fn current_requested_outputs() -> usize {
59    crate::output_count::current_output_count().unwrap_or(1)
60}
61
62fn undefined_callable_error(identity: &runmat_hir::CallableIdentity) -> RuntimeError {
63    let detail = format!("Undefined function for callable identity {identity:?}");
64    build_runtime_error(detail)
65        .with_identifier(IDENT_UNDEFINED_FUNCTION)
66        .build()
67}
68
69pub(crate) fn is_undefined_function_error(err: &RuntimeError) -> bool {
70    err.identifier() == Some(IDENT_UNDEFINED_FUNCTION)
71}
72
73fn build_shape_checked_cell(
74    values: Vec<Value>,
75    rows: usize,
76    cols: usize,
77    context: &str,
78) -> Result<runmat_builtins::CellArray, RuntimeError> {
79    runmat_builtins::CellArray::new(values, rows, cols).map_err(|err| {
80        build_runtime_error(format!("{context}: {err}"))
81            .with_identifier("RunMat:ShapeMismatch")
82            .build()
83    })
84}
85
86pub(crate) fn runtime_descriptor_error(
87    builtin: &'static str,
88    error: &'static BuiltinErrorDescriptor,
89) -> RuntimeError {
90    runtime_descriptor_error_with_message(builtin, error.message, error)
91}
92
93pub(crate) fn runtime_descriptor_error_with_detail(
94    builtin: &'static str,
95    error: &'static BuiltinErrorDescriptor,
96    detail: impl AsRef<str>,
97) -> RuntimeError {
98    runtime_descriptor_error_with_message(
99        builtin,
100        format!("{}: {}", error.message, detail.as_ref()),
101        error,
102    )
103}
104
105fn runtime_descriptor_error_with_message(
106    builtin: &'static str,
107    message: impl Into<String>,
108    error: &'static BuiltinErrorDescriptor,
109) -> RuntimeError {
110    let mut builder = build_runtime_error(message).with_builtin(builtin);
111    if let Some(identifier) = error.identifier {
112        builder = builder.with_identifier(identifier);
113    }
114    builder.build()
115}
116
117pub(crate) fn object_receiver_class_name(receiver: &Value) -> Option<String> {
118    match receiver {
119        Value::Object(obj) => Some(obj.class_name.clone()),
120        Value::HandleObject(handle) => {
121            let target = unsafe { &*handle.target.as_raw() };
122            Some(match target {
123                Value::Object(obj) => obj.class_name.clone(),
124                _ => handle.class_name.clone(),
125            })
126        }
127        _ => None,
128    }
129}
130
131fn class_member_identity(class_name: &str, member: &str) -> runmat_hir::CallableIdentity {
132    runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
133        runmat_hir::SymbolName(class_name.to_string()),
134        runmat_hir::SymbolName(member.to_string()),
135    ]))
136}
137
138pub(crate) fn qualified_name_segments(name: &str) -> Vec<runmat_hir::SymbolName> {
139    name.split('.')
140        .map(|segment| runmat_hir::SymbolName(segment.to_string()))
141        .collect()
142}
143
144pub(crate) fn is_well_formed_qualified_name(name: &str) -> bool {
145    let segments = qualified_name_segments(name);
146    segments.len() > 1 && segments.iter().all(|segment| !segment.0.is_empty())
147}
148
149pub(crate) fn callable_identity_for_handle_name(
150    name: &str,
151) -> (
152    runmat_hir::CallableIdentity,
153    runmat_hir::CallableFallbackPolicy,
154) {
155    if is_well_formed_qualified_name(name) {
156        let segments = qualified_name_segments(name);
157        (
158            runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(segments)),
159            runmat_hir::CallableFallbackPolicy::ExternalBoundary,
160        )
161    } else {
162        (
163            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(name.to_string())),
164            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
165        )
166    }
167}
168
169pub(crate) fn external_callable_identity_for_name(name: &str) -> runmat_hir::CallableIdentity {
170    if !is_well_formed_qualified_name(name) {
171        runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
172            runmat_hir::SymbolName(name.to_string()),
173        ]))
174    } else {
175        let segments = qualified_name_segments(name);
176        runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(segments))
177    }
178}
179
180pub(crate) async fn dispatch_object_external_member(
181    class_name: String,
182    member: &str,
183    args: Vec<Value>,
184    requested_outputs: usize,
185) -> BuiltinResult<Value> {
186    dispatch_callable_with_policy(
187        class_member_identity(&class_name, member),
188        runmat_hir::CallableFallbackPolicy::ExternalBoundary,
189        args,
190        requested_outputs,
191    )
192    .await
193}
194
195async fn dispatch_named_with_requested_outputs(
196    name: &str,
197    args: &[Value],
198    requested_outputs: usize,
199) -> BuiltinResult<Value> {
200    call_builtin_async_with_outputs(name, args, requested_outputs).await
201}
202
203pub(crate) async fn dispatch_callable_with_policy(
204    identity: runmat_hir::CallableIdentity,
205    fallback_policy: runmat_hir::CallableFallbackPolicy,
206    args: Vec<Value>,
207    requested_outputs: usize,
208) -> BuiltinResult<Value> {
209    let request = crate::user_functions::CallableRequest::resolved(
210        identity.clone(),
211        fallback_policy,
212        args.clone(),
213        requested_outputs,
214    );
215    if let Some(result) = crate::user_functions::try_call_semantic_descriptor(request).await {
216        return result;
217    }
218
219    if let Some(name) = fallback_policy.vm_fallback_name_for(&identity) {
220        return dispatch_named_with_requested_outputs(&name, &args, requested_outputs).await;
221    }
222
223    Err(undefined_callable_error(&identity))
224}
225
226pub async fn call_feval_async_with_outputs(
227    func_value: Value,
228    args: &[Value],
229    requested_outputs: usize,
230) -> Result<Value, RuntimeError> {
231    let _guard = crate::output_count::push_output_count(Some(requested_outputs));
232    feval_builtin(func_value, args.to_vec()).await
233}
234
235pub use runtime_error::{
236    build_runtime_error, replay_error, replay_error_with_source, CallFrame, ErrorContext,
237    ReplayErrorKind, RuntimeError, RuntimeErrorBuilder,
238};
239
240#[cfg(feature = "blas-lapack")]
241pub mod blas;
242#[cfg(feature = "blas-lapack")]
243pub mod lapack;
244
245// Link to Apple's Accelerate framework on macOS
246#[cfg(all(feature = "blas-lapack", target_os = "macos"))]
247#[link(name = "Accelerate", kind = "framework")]
248extern "C" {}
249
250// Ensure OpenBLAS is linked on non-macOS platforms when BLAS/LAPACK is enabled
251#[cfg(all(feature = "blas-lapack", not(target_os = "macos")))]
252extern crate openblas_src;
253
254pub use dispatcher::{
255    call_builtin, call_builtin_async, call_builtin_async_with_outputs, class_access_context,
256    gather_if_needed, gather_if_needed_async, is_gpu_value, push_class_access_context,
257    value_contains_gpu,
258};
259
260#[cfg(feature = "plot-core")]
261pub use builtins::plotting::{
262    export_figure_scene as runtime_plot_export_figure_scene,
263    import_figure_scene_async as runtime_plot_import_figure_scene_async,
264    import_figure_scene_from_path_async as runtime_plot_import_figure_scene_from_path_async,
265};
266pub use replay::{
267    runtime_export_workspace_state, runtime_import_workspace_state, WorkspaceReplayMode,
268};
269
270pub use runmat_macros::{register_fusion_spec, register_gpu_spec};
271
272// Pruned legacy re-exports; prefer builtins::* and explicit shims only
273// Transitional root-level shims for widely used helpers
274pub use builtins::common::concatenation::create_matrix_from_values;
275pub use builtins::common::elementwise::{
276    elementwise_div, elementwise_mul, elementwise_neg, elementwise_pow, power,
277};
278pub use builtins::common::indexing::perform_indexing;
279pub use builtins::common::matrix::value_matmul;
280// Explicitly re-export for external users of the VM that build matrices from values
281// (kept above)
282// Note: constants and mathematics modules only contain #[runtime_builtin] functions
283// and don't export public items, so they don't need to be re-exported
284
285#[cfg(feature = "blas-lapack")]
286pub use blas::*;
287#[cfg(feature = "blas-lapack")]
288pub use lapack::*;
289
290pub fn make_cell_with_shape(values: Vec<Value>, shape: Vec<usize>) -> Result<Value, String> {
291    #[cfg(target_arch = "wasm32")]
292    {
293        let ca = runmat_builtins::CellArray::new_with_shape(values, shape)
294            .map_err(|e| format!("Cell creation error: {e}"))?;
295        Ok(Value::Cell(ca))
296    }
297
298    #[cfg(not(target_arch = "wasm32"))]
299    {
300        let handles: Vec<GcPtr<Value>> = values
301            .into_iter()
302            .map(|v| runmat_gc::gc_allocate(v).map_err(|e| format!("Cell creation error: {e}")))
303            .collect::<Result<_, _>>()?;
304        let ca = runmat_builtins::CellArray::new_handles_with_shape(handles, shape)
305            .map_err(|e| format!("Cell creation error: {e}"))?;
306        Ok(Value::Cell(ca))
307    }
308}
309
310pub(crate) fn make_cell(values: Vec<Value>, rows: usize, cols: usize) -> Result<Value, String> {
311    make_cell_with_shape(values, vec![rows, cols])
312}
313
314fn to_string_scalar(v: &Value) -> Result<String, String> {
315    let s: String = v.try_into()?;
316    Ok(s)
317}
318
319fn to_string_array(v: &Value) -> Result<runmat_builtins::StringArray, String> {
320    match v {
321        Value::String(s) => runmat_builtins::StringArray::new(vec![s.clone()], vec![1, 1])
322            .map_err(|e| e.to_string()),
323        Value::StringArray(sa) => Ok(sa.clone()),
324        Value::CharArray(ca) => {
325            // Convert each row to a string; treat as column vector
326            let mut out: Vec<String> = Vec::with_capacity(ca.rows);
327            for r in 0..ca.rows {
328                let mut s = String::with_capacity(ca.cols);
329                for c in 0..ca.cols {
330                    s.push(ca.data[r * ca.cols + c]);
331                }
332                out.push(s);
333            }
334            runmat_builtins::StringArray::new(out, vec![ca.rows, 1]).map_err(|e| e.to_string())
335        }
336        other => Err(format!("cannot convert to string array: {other:?}")),
337    }
338}
339
340pub(crate) async fn strjoin_rowwise(a: Value, delim: Value) -> crate::BuiltinResult<Value> {
341    let d = to_string_scalar(&delim)?;
342    let sa = to_string_array(&a)?;
343    let rows = *sa.shape.first().unwrap_or(&sa.data.len());
344    let cols = *sa.shape.get(1).unwrap_or(&1);
345    if rows == 0 || cols == 0 {
346        return Ok(Value::StringArray(
347            runmat_builtins::StringArray::new(Vec::new(), vec![0, 0]).unwrap(),
348        ));
349    }
350    let mut out: Vec<String> = Vec::with_capacity(rows);
351    for r in 0..rows {
352        let mut s = String::new();
353        for c in 0..cols {
354            if c > 0 {
355                s.push_str(&d);
356            }
357            s.push_str(&sa.data[r + c * rows]);
358        }
359        out.push(s);
360    }
361    Ok(Value::StringArray(
362        runmat_builtins::StringArray::new(out, vec![rows, 1])
363            .map_err(|e| format!("strjoin: {e}"))?,
364    ))
365}
366
367pub(crate) async fn deal_builtin(rest: Vec<Value>) -> crate::BuiltinResult<Value> {
368    if let Some(out_count) = crate::output_count::current_output_count() {
369        if out_count == 0 {
370            return Ok(Value::OutputList(Vec::new()));
371        }
372        if out_count > 1 {
373            return Ok(crate::output_count::output_list_with_padding(
374                out_count, rest,
375            ));
376        }
377    }
378    // Return cell row vector of inputs for expansion
379    let cols = rest.len();
380    make_cell(rest, 1, cols).map_err(Into::into)
381}
382
383// Object/handle utilities used by interpreter lowering for OOP/func handles
384
385pub(crate) async fn rethrow_builtin(e: Value) -> crate::BuiltinResult<Value> {
386    match e {
387        Value::MException(me) => Err(build_runtime_error(me.message)
388            .with_identifier(me.identifier)
389            .build()),
390        Value::String(s) => Err(build_runtime_error(s).build()),
391        other => Err(build_runtime_error(format!("RunMat:error: {other:?}")).build()),
392    }
393}
394
395// -------- Handle classes & events --------
396
397pub(crate) async fn new_handle_object_builtin(class_name: String) -> crate::BuiltinResult<Value> {
398    // Create an underlying object instance and wrap it in a handle
399    let obj = create_class_object(class_name.clone()).await?;
400    let gc = runmat_gc::gc_allocate(obj).map_err(|e| format!("gc: {e}"))?;
401    Ok(Value::HandleObject(runmat_builtins::HandleRef {
402        class_name,
403        target: gc,
404        valid: true,
405    }))
406}
407
408pub(crate) async fn isvalid_builtin(v: Value) -> crate::BuiltinResult<Value> {
409    match v {
410        Value::HandleObject(h) => Ok(Value::Bool(runmat_builtins::is_handle_valid(&h))),
411        Value::Listener(l) => Ok(Value::Bool(l.valid && l.enabled)),
412        _ => Ok(Value::Bool(false)),
413    }
414}
415
416use std::sync::{Mutex, OnceLock};
417
418#[derive(Default)]
419struct EventRegistry {
420    next_id: u64,
421    listeners: std::collections::HashMap<(usize, String), Vec<runmat_builtins::Listener>>,
422}
423
424static EVENT_REGISTRY: OnceLock<Mutex<EventRegistry>> = OnceLock::new();
425
426fn events() -> &'static Mutex<EventRegistry> {
427    EVENT_REGISTRY.get_or_init(|| Mutex::new(EventRegistry::default()))
428}
429
430pub(crate) fn invalidate_listener_registration(listener_id: u64) {
431    let mut reg = events().lock().unwrap();
432    for listeners in reg.listeners.values_mut() {
433        for listener in listeners.iter_mut() {
434            if listener.id == listener_id {
435                listener.valid = false;
436                listener.enabled = false;
437            }
438        }
439    }
440}
441
442pub(crate) fn canonicalize_callback_handle_for_semantic_resolution(callback: Value) -> Value {
443    fn normalize_handle_name(text: &str) -> Option<String> {
444        let trimmed = text.trim();
445        let name = trimmed.strip_prefix('@').unwrap_or(trimmed).trim();
446        (!name.is_empty()).then(|| name.to_string())
447    }
448
449    fn resolve_text_handle(text: &str) -> Option<Value> {
450        let name = normalize_handle_name(text)?;
451        let function = crate::user_functions::resolve_semantic_function_by_name(&name)?;
452        Some(Value::BoundFunctionHandle { name, function })
453    }
454
455    match callback {
456        Value::String(text) => resolve_text_handle(&text).unwrap_or_else(|| {
457            crate::builtins::introspection::function_handle_text::dispatch_str2func(Value::String(
458                text.clone(),
459            ))
460            .unwrap_or(Value::String(text))
461        }),
462        Value::StringArray(array) if array.data.len() == 1 => {
463            let text = &array.data[0];
464            resolve_text_handle(text).unwrap_or_else(|| {
465                crate::builtins::introspection::function_handle_text::dispatch_str2func(
466                    Value::StringArray(array.clone()),
467                )
468                .unwrap_or(Value::StringArray(array))
469            })
470        }
471        Value::CharArray(chars) if chars.rows == 1 => {
472            let text: String = chars.data.iter().collect();
473            resolve_text_handle(&text).unwrap_or_else(|| {
474                crate::builtins::introspection::function_handle_text::dispatch_str2func(
475                    Value::CharArray(chars.clone()),
476                )
477                .unwrap_or(Value::CharArray(chars))
478            })
479        }
480        Value::FunctionHandle(name) => {
481            if let Some(function) = crate::user_functions::resolve_semantic_function_by_name(&name)
482            {
483                Value::BoundFunctionHandle { name, function }
484            } else {
485                Value::FunctionHandle(name)
486            }
487        }
488        Value::ExternalFunctionHandle(name) => {
489            if is_well_formed_qualified_name(&name) {
490                if let Some(function) =
491                    crate::user_functions::resolve_semantic_function_by_name(&name)
492                {
493                    return Value::BoundFunctionHandle { name, function };
494                }
495            }
496            Value::ExternalFunctionHandle(name)
497        }
498        Value::MethodFunctionHandle(name) => {
499            if let Some(function) = crate::user_functions::resolve_semantic_function_by_name(&name)
500            {
501                Value::BoundFunctionHandle { name, function }
502            } else {
503                Value::MethodFunctionHandle(name)
504            }
505        }
506        Value::Closure(mut closure) => {
507            if closure.bound_function.is_none() {
508                if let Some(function) =
509                    crate::user_functions::resolve_semantic_function_by_name(&closure.function_name)
510                {
511                    closure.bound_function = Some(function);
512                }
513            }
514            Value::Closure(closure)
515        }
516        other => other,
517    }
518}
519
520fn canonicalize_listener_callback(callback: Value) -> Value {
521    canonicalize_callback_handle_for_semantic_resolution(callback)
522}
523
524pub(crate) async fn addlistener_builtin(
525    target: Value,
526    event_name: String,
527    callback: Value,
528) -> crate::BuiltinResult<Value> {
529    let key_ptr: usize = match &target {
530        Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
531        Value::Object(o) => o as *const _ as usize,
532        _ => {
533            return Err(
534                build_runtime_error("addlistener: target must be handle or object")
535                    .with_builtin("addlistener")
536                    .with_identifier("RunMat:AddListenerTargetInvalid")
537                    .build(),
538            )
539        }
540    };
541    let mut reg = events().lock().unwrap();
542    let id = {
543        reg.next_id += 1;
544        reg.next_id
545    };
546    let tgt_gc = match target {
547        Value::HandleObject(h) => h.target,
548        Value::Object(o) => {
549            runmat_gc::gc_allocate(Value::Object(o)).map_err(|e| format!("gc: {e}"))?
550        }
551        _ => unreachable!(),
552    };
553    let callback = canonicalize_listener_callback(callback);
554    let cb_gc = runmat_gc::gc_allocate(callback).map_err(|e| format!("gc: {e}"))?;
555    let listener = runmat_builtins::Listener {
556        id,
557        target: tgt_gc,
558        event_name: event_name.clone(),
559        callback: cb_gc,
560        enabled: true,
561        valid: true,
562    };
563    reg.listeners
564        .entry((key_ptr, event_name))
565        .or_default()
566        .push(listener.clone());
567    Ok(Value::Listener(listener))
568}
569
570pub(crate) async fn notify_builtin(
571    target: Value,
572    event_name: String,
573    rest: Vec<Value>,
574) -> crate::BuiltinResult<Value> {
575    let key_ptr: usize = match &target {
576        Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
577        Value::Object(o) => o as *const _ as usize,
578        _ => {
579            return Err(
580                build_runtime_error("notify: target must be handle or object")
581                    .with_builtin("notify")
582                    .with_identifier("RunMat:NotifyTargetInvalid")
583                    .build(),
584            )
585        }
586    };
587    let mut to_call: Vec<runmat_builtins::Listener> = Vec::new();
588    {
589        let reg = events().lock().unwrap();
590        if let Some(list) = reg.listeners.get(&(key_ptr, event_name.clone())) {
591            for l in list {
592                if l.valid && l.enabled {
593                    to_call.push(l.clone());
594                }
595            }
596        }
597    }
598    for l in to_call {
599        // Call callback via feval-like protocol.
600        let mut args = Vec::new();
601        args.push(target.clone());
602        args.extend(rest.iter().cloned());
603        let cbv: Value = (*l.callback).clone();
604        let should_dispatch = match &cbv {
605            Value::String(s) => !s.trim().is_empty(),
606            Value::StringArray(sa) => sa.data.len() == 1 && !sa.data[0].trim().is_empty(),
607            Value::CharArray(ca) if ca.rows == 1 => {
608                let text: String = ca.data.iter().collect();
609                !text.trim().is_empty()
610            }
611            Value::FunctionHandle(_)
612            | Value::ExternalFunctionHandle(_)
613            | Value::MethodFunctionHandle(_)
614            | Value::BoundFunctionHandle { .. }
615            | Value::Closure(_) => true,
616            _ => false,
617        };
618        if should_dispatch {
619            let _ = call_feval_async_with_outputs(cbv.clone(), &args, 0).await?;
620        }
621    }
622    Ok(Value::Num(0.0))
623}
624
625// Test-oriented dependent property handlers (global). If a class defines a Dependent
626// property named 'p', the VM will try to call get.p / set.p. We provide generic
627// implementations that read/write a conventional backing field 'p_backing'.
628pub(crate) async fn get_p_builtin(obj: Value) -> crate::BuiltinResult<Value> {
629    match obj {
630        Value::Object(o) => {
631            if let Some(v) = o.properties.get("p_backing") {
632                Ok(v.clone())
633            } else {
634                Ok(Value::Num(0.0))
635            }
636        }
637        other => Err(build_runtime_error(format!(
638            "get.p: requires object receiver (got {other:?})"
639        ))
640        .with_builtin("get.p")
641        .with_identifier("RunMat:GetPReceiverInvalid")
642        .build()),
643    }
644}
645
646pub(crate) async fn set_p_builtin(obj: Value, val: Value) -> crate::BuiltinResult<Value> {
647    match obj {
648        Value::Object(mut o) => {
649            o.properties.insert("p_backing".to_string(), val);
650            Ok(Value::Object(o))
651        }
652        other => Err(build_runtime_error(format!(
653            "set.p: requires object receiver (got {other:?})"
654        ))
655        .with_builtin("set.p")
656        .with_identifier("RunMat:SetPReceiverInvalid")
657        .build()),
658    }
659}
660
661pub(crate) async fn make_anon_builtin(params: String, body: String) -> crate::BuiltinResult<Value> {
662    Ok(Value::String(format!("@anon({params}) {body}")))
663}
664
665pub async fn create_class_object(class_name: String) -> crate::BuiltinResult<Value> {
666    if runmat_builtins::is_class_abstract(&class_name) {
667        return Err(build_runtime_error(format!(
668            "Cannot instantiate abstract class '{}'.",
669            class_name
670        ))
671        .with_identifier("RunMat:AbstractMethodMissing")
672        .build());
673    }
674    if let Some(def) = runmat_builtins::get_class(&class_name) {
675        // Collect class hierarchy from root to leaf for default initialization
676        let mut chain: Vec<runmat_builtins::ClassDef> = Vec::new();
677        let mut is_handle_class = false;
678        let mut visited = std::collections::HashSet::new();
679        // Walk up to root
680        let mut cursor: Option<String> = Some(def.name.clone());
681        while let Some(name) = cursor {
682            if name.eq_ignore_ascii_case("handle") {
683                is_handle_class = true;
684                break;
685            }
686            if !visited.insert(name.clone()) {
687                break;
688            }
689            if let Some(cd) = runmat_builtins::get_class(&name) {
690                if cd
691                    .parent
692                    .as_ref()
693                    .is_some_and(|parent| parent.eq_ignore_ascii_case("handle"))
694                {
695                    is_handle_class = true;
696                }
697                chain.push(cd.clone());
698                cursor = cd.parent.clone();
699            } else {
700                break;
701            }
702        }
703        // Reverse to root-first
704        chain.reverse();
705        let mut obj = runmat_builtins::ObjectInstance::new(def.name.clone());
706        // Apply defaults from root to leaf (leaf overrides effectively by later assignment)
707        let empty_default = || {
708            Value::Tensor(runmat_builtins::Tensor::new(vec![], vec![0, 0]).expect("empty tensor"))
709        };
710        for cd in chain {
711            for (k, p) in cd.properties.iter() {
712                if !p.is_static {
713                    obj.properties.insert(
714                        k.clone(),
715                        p.default_value.clone().unwrap_or_else(empty_default),
716                    );
717                }
718            }
719        }
720        if is_handle_class {
721            let gc = runmat_gc::gc_allocate(Value::Object(obj)).map_err(|e| format!("gc: {e}"))?;
722            Ok(Value::HandleObject(runmat_builtins::HandleRef {
723                class_name: def.name.clone(),
724                target: gc,
725                valid: true,
726            }))
727        } else {
728            Ok(Value::Object(obj))
729        }
730    } else {
731        Ok(Value::Object(runmat_builtins::ObjectInstance::new(
732            class_name,
733        )))
734    }
735}
736
737pub async fn call_super_constructor(
738    class_name: String,
739    super_class_name: String,
740    args: Vec<Value>,
741) -> crate::BuiltinResult<Value> {
742    let receiver = create_class_object(class_name).await?;
743    let ctor_name = super_class_name
744        .rsplit('.')
745        .next()
746        .filter(|name| !name.trim().is_empty())
747        .unwrap_or(super_class_name.as_str());
748    let ctor_lookup = runmat_builtins::lookup_method(&super_class_name, ctor_name)
749        .or_else(|| runmat_builtins::lookup_method(&super_class_name, &super_class_name));
750    let Some((ctor, _owner)) = ctor_lookup else {
751        return Ok(receiver);
752    };
753    let Some(result) =
754        crate::user_functions::try_call_semantic_function_by_name(&ctor.function_name, &args, 1)
755            .await
756    else {
757        return Ok(receiver);
758    };
759    let ctor_result = result?;
760    fn merge_parent_props_into_object(
761        receiver_obj: &mut runmat_builtins::ObjectInstance,
762        ctor_result: Value,
763    ) {
764        match ctor_result {
765            Value::Object(parent_obj) => {
766                for (name, value) in parent_obj.properties {
767                    receiver_obj.properties.insert(name, value);
768                }
769            }
770            Value::HandleObject(parent_handle) => {
771                let raw_parent = unsafe { parent_handle.target.as_raw() };
772                if !raw_parent.is_null() {
773                    if let Value::Object(parent_obj) = unsafe { (&*raw_parent).clone() } {
774                        for (name, value) in parent_obj.properties {
775                            receiver_obj.properties.insert(name, value);
776                        }
777                    }
778                }
779            }
780            Value::Struct(parent_fields) => {
781                for (name, value) in parent_fields.fields {
782                    receiver_obj.properties.insert(name, value);
783                }
784            }
785            _ => {}
786        }
787    }
788    match receiver {
789        Value::Object(mut receiver_obj) => {
790            merge_parent_props_into_object(&mut receiver_obj, ctor_result);
791            Ok(Value::Object(receiver_obj))
792        }
793        Value::HandleObject(handle) => {
794            let raw = unsafe { handle.target.as_raw_mut() };
795            if !raw.is_null() {
796                if let Value::Object(mut receiver_obj) = unsafe { (&*raw).clone() } {
797                    merge_parent_props_into_object(&mut receiver_obj, ctor_result);
798                    unsafe {
799                        *raw = Value::Object(receiver_obj);
800                    }
801                }
802            }
803            Ok(Value::HandleObject(handle))
804        }
805        _ => Ok(receiver),
806    }
807}
808
809pub async fn call_super_method(
810    class_name: String,
811    super_class_name: String,
812    method_name: String,
813    args: Vec<Value>,
814) -> crate::BuiltinResult<Value> {
815    let Some((method, owner)) = runmat_builtins::lookup_method(&super_class_name, &method_name)
816    else {
817        return Err(build_runtime_error(format!(
818            "Undefined superclass method '{}@{}'",
819            method_name, super_class_name
820        ))
821        .with_identifier("RunMat:UndefinedFunction")
822        .build());
823    };
824    if method.is_static {
825        return Err(build_runtime_error(format!(
826            "Superclass method '{}@{}' is static and cannot be called with super method syntax.",
827            method_name, super_class_name
828        ))
829        .with_identifier("RunMat:MethodStaticAccess")
830        .build());
831    }
832    let access_allowed = match method.access {
833        runmat_builtins::Access::Public => true,
834        runmat_builtins::Access::Protected => {
835            runmat_builtins::is_class_or_subclass(&class_name, &owner)
836        }
837        runmat_builtins::Access::Private => class_name == owner,
838    };
839    if !access_allowed {
840        return Err(build_runtime_error(format!(
841            "Method '{}@{}' is not accessible from class '{}'.",
842            method_name, super_class_name, class_name
843        ))
844        .with_identifier("RunMat:MethodPrivate")
845        .build());
846    }
847    let Some(result) =
848        crate::user_functions::try_call_semantic_function_by_name(&method.function_name, &args, 1)
849            .await
850    else {
851        return Err(
852            build_runtime_error(format!("Undefined function: {}", method.function_name))
853                .with_identifier("RunMat:UndefinedFunction")
854                .build(),
855        );
856    };
857    result
858}
859
860// handle-object builtins removed for now
861
862pub(crate) async fn classref_builtin(class_name: String) -> crate::BuiltinResult<Value> {
863    Ok(Value::ClassRef(class_name))
864}
865
866pub(crate) async fn register_test_classes_builtin() -> crate::BuiltinResult<Value> {
867    use runmat_builtins::*;
868    let mut props = std::collections::HashMap::new();
869    props.insert(
870        "x".to_string(),
871        PropertyDef {
872            name: "x".to_string(),
873            is_static: false,
874            is_constant: false,
875            is_dependent: false,
876            get_access: Access::Public,
877            set_access: Access::Public,
878            default_value: Some(Value::Num(0.0)),
879        },
880    );
881    props.insert(
882        "y".to_string(),
883        PropertyDef {
884            name: "y".to_string(),
885            is_static: false,
886            is_constant: false,
887            is_dependent: false,
888            get_access: Access::Public,
889            set_access: Access::Public,
890            default_value: Some(Value::Num(0.0)),
891        },
892    );
893    props.insert(
894        "staticValue".to_string(),
895        PropertyDef {
896            name: "staticValue".to_string(),
897            is_static: true,
898            is_constant: false,
899            is_dependent: false,
900            get_access: Access::Public,
901            set_access: Access::Public,
902            default_value: Some(Value::Num(42.0)),
903        },
904    );
905    props.insert(
906        "secret".to_string(),
907        PropertyDef {
908            name: "secret".to_string(),
909            is_static: false,
910            is_constant: false,
911            is_dependent: false,
912            get_access: Access::Private,
913            set_access: Access::Private,
914            default_value: Some(Value::Num(99.0)),
915        },
916    );
917    let mut methods = std::collections::HashMap::new();
918    methods.insert(
919        "move".to_string(),
920        MethodDef {
921            name: "move".to_string(),
922            is_static: false,
923            is_abstract: false,
924            is_sealed: false,
925            access: Access::Public,
926            function_name: "Point.move".to_string(),
927            implicit_class_argument: None,
928        },
929    );
930    methods.insert(
931        "origin".to_string(),
932        MethodDef {
933            name: "origin".to_string(),
934            is_static: true,
935            is_abstract: false,
936            is_sealed: false,
937            access: Access::Public,
938            function_name: "Point.origin".to_string(),
939            implicit_class_argument: None,
940        },
941    );
942    runmat_builtins::register_class(ClassDef {
943        name: "Point".to_string(),
944        parent: None,
945        properties: props,
946        methods,
947    });
948
949    // Namespaced class example: pkg.PointNS with same shape as Point
950    let mut ns_props = std::collections::HashMap::new();
951    ns_props.insert(
952        "x".to_string(),
953        PropertyDef {
954            name: "x".to_string(),
955            is_static: false,
956            is_constant: false,
957            is_dependent: false,
958            get_access: Access::Public,
959            set_access: Access::Public,
960            default_value: Some(Value::Num(1.0)),
961        },
962    );
963    ns_props.insert(
964        "y".to_string(),
965        PropertyDef {
966            name: "y".to_string(),
967            is_static: false,
968            is_constant: false,
969            is_dependent: false,
970            get_access: Access::Public,
971            set_access: Access::Public,
972            default_value: Some(Value::Num(2.0)),
973        },
974    );
975    let ns_methods = std::collections::HashMap::new();
976    runmat_builtins::register_class(ClassDef {
977        name: "pkg.PointNS".to_string(),
978        parent: None,
979        properties: ns_props,
980        methods: ns_methods,
981    });
982
983    // Inheritance: Shape (base) and Circle (derived)
984    let shape_props = std::collections::HashMap::new();
985    let mut shape_methods = std::collections::HashMap::new();
986    shape_methods.insert(
987        "area".to_string(),
988        MethodDef {
989            name: "area".to_string(),
990            is_static: false,
991            is_abstract: false,
992            is_sealed: false,
993            access: Access::Public,
994            function_name: "Shape.area".to_string(),
995            implicit_class_argument: None,
996        },
997    );
998    runmat_builtins::register_class(ClassDef {
999        name: "Shape".to_string(),
1000        parent: None,
1001        properties: shape_props,
1002        methods: shape_methods,
1003    });
1004
1005    let mut circle_props = std::collections::HashMap::new();
1006    circle_props.insert(
1007        "r".to_string(),
1008        PropertyDef {
1009            name: "r".to_string(),
1010            is_static: false,
1011            is_constant: false,
1012            is_dependent: false,
1013            get_access: Access::Public,
1014            set_access: Access::Public,
1015            default_value: Some(Value::Num(0.0)),
1016        },
1017    );
1018    let mut circle_methods = std::collections::HashMap::new();
1019    circle_methods.insert(
1020        "area".to_string(),
1021        MethodDef {
1022            name: "area".to_string(),
1023            is_static: false,
1024            is_abstract: false,
1025            is_sealed: false,
1026            access: Access::Public,
1027            function_name: "Circle.area".to_string(),
1028            implicit_class_argument: None,
1029        },
1030    );
1031    runmat_builtins::register_class(ClassDef {
1032        name: "Circle".to_string(),
1033        parent: Some("Shape".to_string()),
1034        properties: circle_props,
1035        methods: circle_methods,
1036    });
1037
1038    // Constructor demo class: Ctor with static constructor method Ctor
1039    let ctor_props = std::collections::HashMap::new();
1040    let mut ctor_methods = std::collections::HashMap::new();
1041    ctor_methods.insert(
1042        "Ctor".to_string(),
1043        MethodDef {
1044            name: "Ctor".to_string(),
1045            is_static: true,
1046            is_abstract: false,
1047            is_sealed: false,
1048            access: Access::Public,
1049            function_name: "Ctor.Ctor".to_string(),
1050            implicit_class_argument: None,
1051        },
1052    );
1053    runmat_builtins::register_class(ClassDef {
1054        name: "Ctor".to_string(),
1055        parent: None,
1056        properties: ctor_props,
1057        methods: ctor_methods,
1058    });
1059
1060    // Overloaded indexing demo class: OverIdx with subsref/subsasgn
1061    let overidx_props = std::collections::HashMap::new();
1062    let mut overidx_methods = std::collections::HashMap::new();
1063    overidx_methods.insert(
1064        OBJECT_SUBSREF_METHOD.to_string(),
1065        MethodDef {
1066            name: OBJECT_SUBSREF_METHOD.to_string(),
1067            is_static: false,
1068            is_abstract: false,
1069            is_sealed: false,
1070            access: Access::Public,
1071            function_name: format!("OverIdx.{OBJECT_SUBSREF_METHOD}"),
1072            implicit_class_argument: None,
1073        },
1074    );
1075    overidx_methods.insert(
1076        OBJECT_SUBSASGN_METHOD.to_string(),
1077        MethodDef {
1078            name: OBJECT_SUBSASGN_METHOD.to_string(),
1079            is_static: false,
1080            is_abstract: false,
1081            is_sealed: false,
1082            access: Access::Public,
1083            function_name: format!("OverIdx.{OBJECT_SUBSASGN_METHOD}"),
1084            implicit_class_argument: None,
1085        },
1086    );
1087    runmat_builtins::register_class(ClassDef {
1088        name: "OverIdx".to_string(),
1089        parent: None,
1090        properties: overidx_props,
1091        methods: overidx_methods,
1092    });
1093
1094    // Class without indexing protocol methods, used by negative subsref/subsasgn contracts.
1095    runmat_builtins::register_class(ClassDef {
1096        name: "NoIdx".to_string(),
1097        parent: None,
1098        properties: std::collections::HashMap::new(),
1099        methods: std::collections::HashMap::new(),
1100    });
1101    Ok(Value::Num(1.0))
1102}
1103
1104#[cfg(feature = "test-classes")]
1105pub async fn test_register_classes() {
1106    let _ = register_test_classes_builtin().await;
1107}
1108
1109// Example method implementation: Point.move(obj, dx, dy) -> updated obj
1110const FEVAL_ERROR_HANDLE_NAME_INVALID: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
1111    code: "RM.FEVAL.HANDLE_NAME_INVALID",
1112    identifier: Some("RunMat:FevalHandleNameInvalid"),
1113    when: "A function or method handle name is empty.",
1114    message: "feval: function handle name must not be empty",
1115};
1116
1117const FEVAL_ERROR_HANDLE_STRING_INVALID: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
1118    code: "RM.FEVAL.HANDLE_STRING_INVALID",
1119    identifier: Some("RunMat:FevalHandleStringInvalid"),
1120    when: "Text handle input does not start with '@'.",
1121    message: "feval: expected function handle string starting with '@'",
1122};
1123
1124const FEVAL_ERROR_HANDLE_SHAPE_INVALID: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
1125    code: "RM.FEVAL.HANDLE_SHAPE_INVALID",
1126    identifier: Some("RunMat:FevalHandleShapeInvalid"),
1127    when: "Text handle input has invalid char/string array shape.",
1128    message: "feval: function handle text input must be scalar row text",
1129};
1130
1131const FEVAL_ERROR_SEMANTIC_UNAVAILABLE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
1132    code: "RM.FEVAL.SEMANTIC_UNAVAILABLE",
1133    identifier: Some("RunMat:SemanticFunctionUnavailable"),
1134    when: "Semantic function identity cannot be invoked in current runtime state.",
1135    message: "feval: semantic function handle is unavailable",
1136};
1137
1138const FEVAL_ERROR_FUNCTION_VALUE_UNSUPPORTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
1139    code: "RM.FEVAL.FUNCTION_VALUE_UNSUPPORTED",
1140    identifier: Some("RunMat:FevalFunctionValueUnsupported"),
1141    when: "The first argument is not a supported callable value.",
1142    message: "feval: unsupported function value",
1143};
1144
1145pub(crate) const FEVAL_ERRORS: [BuiltinErrorDescriptor; 5] = [
1146    FEVAL_ERROR_HANDLE_NAME_INVALID,
1147    FEVAL_ERROR_HANDLE_STRING_INVALID,
1148    FEVAL_ERROR_HANDLE_SHAPE_INVALID,
1149    FEVAL_ERROR_SEMANTIC_UNAVAILABLE,
1150    FEVAL_ERROR_FUNCTION_VALUE_UNSUPPORTED,
1151];
1152
1153pub(crate) async fn feval_builtin(f: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1154    fn normalize_feval_handle_name(name: &str) -> Option<String> {
1155        let trimmed = name.trim();
1156        (!trimmed.is_empty()).then(|| trimmed.to_string())
1157    }
1158
1159    async fn call_by_identity(
1160        identity: runmat_hir::CallableIdentity,
1161        fallback_policy: runmat_hir::CallableFallbackPolicy,
1162        args: &[Value],
1163        requested_outputs: usize,
1164    ) -> crate::BuiltinResult<Value> {
1165        dispatch_callable_with_policy(identity, fallback_policy, args.to_vec(), requested_outputs)
1166            .await
1167    }
1168
1169    async fn call_by_name(
1170        name: &str,
1171        args: &[Value],
1172        requested_outputs: usize,
1173    ) -> crate::BuiltinResult<Value> {
1174        let normalized = normalize_feval_handle_name(name)
1175            .ok_or_else(|| runtime_descriptor_error("feval", &FEVAL_ERROR_HANDLE_NAME_INVALID))?;
1176        let (identity, fallback_policy) = callable_identity_for_handle_name(&normalized);
1177        call_by_identity(identity, fallback_policy, args, requested_outputs).await
1178    }
1179
1180    let requested_outputs = crate::output_count::current_output_count().unwrap_or(1);
1181
1182    match f {
1183        // Function handle strings like "@sin"
1184        Value::String(s) => {
1185            if let Some(name) = s.strip_prefix('@') {
1186                call_by_name(name, &rest, requested_outputs).await
1187            } else {
1188                Err(runtime_descriptor_error_with_detail(
1189                    "feval",
1190                    &FEVAL_ERROR_HANDLE_STRING_INVALID,
1191                    format!("got {s}"),
1192                ))
1193            }
1194        }
1195        // Also accept character row vector handles like '@max'
1196        Value::CharArray(ca) => {
1197            if ca.rows == 1 {
1198                let s: String = ca.data.iter().collect();
1199                if let Some(name) = s.strip_prefix('@') {
1200                    call_by_name(name, &rest, requested_outputs).await
1201                } else {
1202                    Err(runtime_descriptor_error_with_detail(
1203                        "feval",
1204                        &FEVAL_ERROR_HANDLE_STRING_INVALID,
1205                        format!("got {s}"),
1206                    ))
1207                }
1208            } else {
1209                Err(runtime_descriptor_error_with_detail(
1210                    "feval",
1211                    &FEVAL_ERROR_HANDLE_SHAPE_INVALID,
1212                    "char array must be a row vector",
1213                ))
1214            }
1215        }
1216        Value::StringArray(sa) => {
1217            if sa.data.len() == 1 {
1218                let s = &sa.data[0];
1219                if let Some(name) = s.strip_prefix('@') {
1220                    call_by_name(name, &rest, requested_outputs).await
1221                } else {
1222                    Err(runtime_descriptor_error_with_detail(
1223                        "feval",
1224                        &FEVAL_ERROR_HANDLE_STRING_INVALID,
1225                        format!("got {s}"),
1226                    ))
1227                }
1228            } else {
1229                Err(runtime_descriptor_error_with_detail(
1230                    "feval",
1231                    &FEVAL_ERROR_HANDLE_SHAPE_INVALID,
1232                    "string array must be scalar",
1233                ))
1234            }
1235        }
1236        Value::FunctionHandle(name) => call_by_name(&name, &rest, requested_outputs).await,
1237        Value::ExternalFunctionHandle(name) => call_by_name(&name, &rest, requested_outputs).await,
1238        Value::MethodFunctionHandle(name) => {
1239            let method_name = name.trim().to_string();
1240            if method_name.is_empty() {
1241                return Err(runtime_descriptor_error(
1242                    "feval",
1243                    &FEVAL_ERROR_HANDLE_NAME_INVALID,
1244                ));
1245            }
1246            dispatch_callable_with_policy(
1247                runmat_hir::CallableIdentity::Method(runmat_hir::MethodId(method_name)),
1248                runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
1249                rest,
1250                requested_outputs,
1251            )
1252            .await
1253        }
1254        Value::BoundFunctionHandle { name, function } => {
1255            let request = crate::user_functions::CallableRequest::semantic(
1256                function,
1257                rest.clone(),
1258                requested_outputs,
1259            );
1260            if let Some(result) = crate::user_functions::try_call_semantic_descriptor(request).await
1261            {
1262                return result;
1263            }
1264            Err(runtime_descriptor_error_with_detail(
1265                "feval",
1266                &FEVAL_ERROR_SEMANTIC_UNAVAILABLE,
1267                format!("semantic function handle '{name}' ({function}) is unavailable"),
1268            ))
1269        }
1270        Value::Closure(c) => {
1271            if let Some(function) = c.bound_function {
1272                let mut args = c.captures.clone();
1273                args.extend(rest);
1274                let request = crate::user_functions::CallableRequest::semantic(
1275                    function,
1276                    args.clone(),
1277                    requested_outputs,
1278                );
1279                if let Some(result) =
1280                    crate::user_functions::try_call_semantic_descriptor(request).await
1281                {
1282                    return result;
1283                }
1284                return Err(runtime_descriptor_error_with_detail(
1285                    "feval",
1286                    &FEVAL_ERROR_SEMANTIC_UNAVAILABLE,
1287                    format!(
1288                        "semantic closure '{}' ({function}) is unavailable",
1289                        c.function_name
1290                    ),
1291                ));
1292            }
1293
1294            if c.function_name == CALL_METHOD_BUILTIN_NAME && c.captures.len() >= 2 {
1295                let base = c.captures[0].clone();
1296                let method = match &c.captures[1] {
1297                    Value::String(name) => name.clone(),
1298                    Value::CharArray(chars) if chars.rows == 1 => chars.data.iter().collect(),
1299                    _ => {
1300                        return Err(build_runtime_error(
1301                            "call_method: closure captures must include method name text",
1302                        )
1303                        .with_builtin("call_method")
1304                        .with_identifier("RunMat:CallMethodNameInvalid")
1305                        .build())
1306                    }
1307                };
1308                let mut method_args = c.captures.iter().skip(2).cloned().collect::<Vec<_>>();
1309                method_args.extend(rest);
1310                return crate::builtins::introspection::call_method::dispatch_call_method(
1311                    base,
1312                    method,
1313                    method_args,
1314                )
1315                .await;
1316            }
1317
1318            let mut args = c.captures.clone();
1319            args.extend(rest);
1320            if let Some(function) =
1321                crate::user_functions::resolve_semantic_function_by_name(&c.function_name)
1322            {
1323                let request = crate::user_functions::CallableRequest::semantic(
1324                    function,
1325                    args.clone(),
1326                    requested_outputs,
1327                );
1328                if let Some(result) =
1329                    crate::user_functions::try_call_semantic_descriptor(request).await
1330                {
1331                    return result;
1332                }
1333            }
1334            call_by_name(&c.function_name, &args, requested_outputs).await
1335        }
1336        receiver @ Value::Object(_) | receiver @ Value::HandleObject(_) => {
1337            let payload = Value::Cell(build_shape_checked_cell(
1338                rest.clone(),
1339                1,
1340                rest.len(),
1341                "feval object index payload",
1342            )?);
1343            crate::builtins::introspection::object_indexing::dispatch_subsref(
1344                receiver,
1345                OBJECT_INDEX_PAREN.to_string(),
1346                payload,
1347            )
1348            .await
1349        }
1350        other => Err(runtime_descriptor_error_with_detail(
1351            "feval",
1352            &FEVAL_ERROR_FUNCTION_VALUE_UNSUPPORTED,
1353            format!("{other:?}"),
1354        )),
1355    }
1356}
1357
1358#[cfg(test)]
1359mod tests {
1360    use super::*;
1361    use crate::builtins::introspection::test_methods::*;
1362    use futures::executor::block_on;
1363    use runmat_builtins::{register_class, Access, ClassDef, PropertyDef};
1364    use std::collections::HashMap;
1365    use std::sync::{
1366        atomic::{AtomicU64, AtomicUsize, Ordering},
1367        Arc,
1368    };
1369
1370    static TEST_CLASS_COUNTER: AtomicU64 = AtomicU64::new(0);
1371
1372    fn unique_class_name(prefix: &str) -> String {
1373        let id = TEST_CLASS_COUNTER.fetch_add(1, Ordering::Relaxed);
1374        format!("{}_{}", prefix, id)
1375    }
1376
1377    #[test]
1378    fn descriptor_migration_covers_lib_runtime_builtins() {
1379        let cases = [
1380            ("deal", "[varargout] = deal(varargin)"),
1381            ("rethrow", "rethrow(err)"),
1382            ("call_method", "[out] = call_method(base, method, varargin)"),
1383            (
1384                "new_handle_object",
1385                "handle = new_handle_object(class_name)",
1386            ),
1387            (
1388                "addlistener",
1389                "listener = addlistener(target, event_name, callback)",
1390            ),
1391            ("notify", "status = notify(target, event_name, varargin)"),
1392            ("get.p", "value = get.p(obj)"),
1393            ("set.p", "obj = set.p(obj, value)"),
1394            ("make_anon", "handle_text = make_anon(params, body)"),
1395            ("classref", "ref = classref(class_name)"),
1396            (
1397                "__register_test_classes",
1398                "status = __register_test_classes()",
1399            ),
1400            ("Point.move", "obj = Point.move(obj, dx, dy)"),
1401            ("Circle.area", "area = Circle.area(obj)"),
1402            ("Ctor.Ctor", "obj = Ctor.Ctor(x)"),
1403            ("PkgF.foo", "value = PkgF.foo()"),
1404            ("OverIdx.plus", "out = OverIdx.plus(obj, rhs)"),
1405            (
1406                "OverIdx.subsref",
1407                "out = OverIdx.subsref(obj, kind, payload)",
1408            ),
1409            ("feval", "[varargout] = feval(f, varargin)"),
1410            ("str2func", "fh = str2func(name)"),
1411            ("func2str", "name = func2str(fh)"),
1412            ("functions", "info = functions(fh)"),
1413            ("inputname", "name = inputname(argNumber)"),
1414            ("localfunctions", "handles = localfunctions()"),
1415            ("narginchk", "narginchk(minArgs, maxArgs)"),
1416            ("nargoutchk", "nargoutchk(minArgs, maxArgs)"),
1417            ("mfilename", "name = mfilename()"),
1418            ("getmethod", "fh = getmethod(obj_or_class, name)"),
1419        ];
1420
1421        for (name, label) in cases {
1422            let builtin = runmat_builtins::builtin_function_by_name(name)
1423                .unwrap_or_else(|| panic!("builtin {name} not registered"));
1424            let descriptor = builtin
1425                .descriptor
1426                .unwrap_or_else(|| panic!("descriptor missing for {name}"));
1427            assert!(
1428                descriptor.signatures.iter().any(|sig| sig.label == label),
1429                "missing signature {label} for {name}"
1430            );
1431        }
1432    }
1433
1434    #[test]
1435    fn feval_closure_uses_semantic_function_identity() {
1436        let _guard = crate::user_functions::install_semantic_function_invoker(Some(Arc::new(
1437            |function, args, requested_outputs| {
1438                assert_eq!(function, 42);
1439                assert_eq!(requested_outputs, 1);
1440                assert_eq!(args, &[Value::Num(2.0)]);
1441                Box::pin(async { Ok(Value::Num(7.0)) })
1442            },
1443        )));
1444        let closure = Value::Closure(runmat_builtins::Closure {
1445            function_name: "function_target".to_string(),
1446            bound_function: Some(42),
1447            captures: Vec::new(),
1448        });
1449
1450        let result = block_on(feval_builtin(closure, vec![Value::Num(2.0)]))
1451            .expect("semantic closure feval succeeds");
1452        assert_eq!(result, Value::Num(7.0));
1453    }
1454
1455    #[test]
1456    fn feval_semantic_function_handle_uses_semantic_identity() {
1457        let _guard = crate::user_functions::install_semantic_function_invoker(Some(Arc::new(
1458            |function, args, requested_outputs| {
1459                assert_eq!(function, 43);
1460                assert_eq!(requested_outputs, 1);
1461                assert_eq!(args, &[Value::Num(3.0)]);
1462                Box::pin(async { Ok(Value::Num(9.0)) })
1463            },
1464        )));
1465        let handle = Value::BoundFunctionHandle {
1466            name: "function_target".to_string(),
1467            function: 43,
1468        };
1469
1470        let result = block_on(feval_builtin(handle, vec![Value::Num(3.0)]))
1471            .expect("semantic function handle feval succeeds");
1472        assert_eq!(result, Value::Num(9.0));
1473    }
1474
1475    #[test]
1476    fn feval_semantic_function_handle_errors_when_semantic_invoker_unavailable() {
1477        let _guard = crate::user_functions::install_semantic_function_invoker(None);
1478        let handle = Value::BoundFunctionHandle {
1479            name: "function_target".to_string(),
1480            function: 9043,
1481        };
1482
1483        let err = block_on(feval_builtin(handle, vec![Value::Num(3.0)])).expect_err(
1484            "semantic function handle should not fall back to name-based dispatch when unavailable",
1485        );
1486        assert_eq!(err.identifier(), Some("RunMat:SemanticFunctionUnavailable"));
1487        assert!(
1488            err.message()
1489                .contains("semantic function handle 'function_target' (9043) is unavailable"),
1490            "unexpected error: {err:?}"
1491        );
1492    }
1493
1494    #[test]
1495    fn feval_name_only_handle_uses_semantic_resolver() {
1496        let _resolver_guard =
1497            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1498                (name == "resolved_target").then_some(45)
1499            })));
1500        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
1501            Arc::new(|function, args, requested_outputs| {
1502                assert_eq!(function, 45);
1503                assert_eq!(requested_outputs, 1);
1504                assert_eq!(args, &[Value::Num(4.0)]);
1505                Box::pin(async { Ok(Value::Num(11.0)) })
1506            }),
1507        ));
1508
1509        let result = block_on(feval_builtin(
1510            Value::FunctionHandle("resolved_target".to_string()),
1511            vec![Value::Num(4.0)],
1512        ))
1513        .expect("resolved name-only handle feval succeeds");
1514        assert_eq!(result, Value::Num(11.0));
1515    }
1516
1517    #[test]
1518    fn feval_method_function_handle_uses_semantic_resolver() {
1519        let _resolver_guard =
1520            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1521                (name == "resolved_method").then_some(5045)
1522            })));
1523        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
1524            Arc::new(|function, args, requested_outputs| {
1525                assert_eq!(function, 5045);
1526                assert_eq!(requested_outputs, 1);
1527                assert_eq!(args, &[Value::Num(4.0)]);
1528                Box::pin(async { Ok(Value::Num(15.0)) })
1529            }),
1530        ));
1531
1532        let result = block_on(feval_builtin(
1533            Value::MethodFunctionHandle("resolved_method".to_string()),
1534            vec![Value::Num(4.0)],
1535        ))
1536        .expect("resolved method handle feval succeeds");
1537        assert_eq!(result, Value::Num(15.0));
1538    }
1539
1540    #[test]
1541    fn feval_method_function_handle_does_not_fallback_to_builtin_name() {
1542        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1543        let err = block_on(feval_builtin(
1544            Value::MethodFunctionHandle("sqrt".to_string()),
1545            vec![Value::Num(9.0)],
1546        ))
1547        .expect_err("method function handle should not fallback to builtin name dispatch");
1548        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
1549    }
1550
1551    #[test]
1552    fn feval_name_only_closure_uses_semantic_resolver() {
1553        let _resolver_guard =
1554            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1555                (name == "resolved_target").then_some(145)
1556            })));
1557        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
1558            Arc::new(|function, args, requested_outputs| {
1559                assert_eq!(function, 145);
1560                assert_eq!(requested_outputs, 1);
1561                assert_eq!(args, &[Value::Num(9.0), Value::Num(4.0)]);
1562                Box::pin(async { Ok(Value::Num(13.0)) })
1563            }),
1564        ));
1565
1566        let closure = Value::Closure(runmat_builtins::Closure {
1567            function_name: "resolved_target".to_string(),
1568            bound_function: None,
1569            captures: vec![Value::Num(9.0)],
1570        });
1571
1572        let result = block_on(feval_builtin(closure, vec![Value::Num(4.0)]))
1573            .expect("resolved name-only closure feval succeeds");
1574        assert_eq!(result, Value::Num(13.0));
1575    }
1576
1577    #[test]
1578    fn feval_name_only_closure_falls_back_when_semantic_invoker_unavailable() {
1579        let _resolver_guard =
1580            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1581                (name == "sin").then_some(245)
1582            })));
1583        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(None);
1584
1585        let closure = Value::Closure(runmat_builtins::Closure {
1586            function_name: "sin".to_string(),
1587            bound_function: None,
1588            captures: Vec::new(),
1589        });
1590
1591        let result =
1592            block_on(feval_builtin(closure, vec![Value::Num(0.0)])).expect("sin fallback works");
1593        assert_eq!(result, Value::Num(0.0));
1594    }
1595
1596    #[test]
1597    fn feval_external_function_handle_errors_when_unresolved() {
1598        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1599        let err = block_on(feval_builtin(
1600            Value::ExternalFunctionHandle("missing.external".to_string()),
1601            vec![Value::Num(1.0)],
1602        ))
1603        .expect_err("external function handle should error when unresolved");
1604        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
1605        assert!(
1606            err.message().contains("missing.external"),
1607            "unexpected error: {err:?}"
1608        );
1609    }
1610
1611    #[test]
1612    fn feval_single_segment_external_function_handle_uses_runtime_name_resolution() {
1613        let _resolver_guard =
1614            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1615                (name == "resolved_target").then_some(4501)
1616            })));
1617        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
1618            Arc::new(|function, args, requested_outputs| {
1619                assert_eq!(function, 4501);
1620                assert_eq!(requested_outputs, 1);
1621                assert_eq!(args, &[Value::Num(4.0)]);
1622                Box::pin(async { Ok(Value::Num(12.0)) })
1623            }),
1624        ));
1625
1626        let result = block_on(feval_builtin(
1627            Value::ExternalFunctionHandle("resolved_target".to_string()),
1628            vec![Value::Num(4.0)],
1629        ))
1630        .expect("single-segment external function handle should use runtime-name resolution");
1631        assert_eq!(result, Value::Num(12.0));
1632    }
1633
1634    #[test]
1635    fn feval_rejects_string_without_at_with_identifier() {
1636        let err = block_on(feval_builtin(
1637            Value::String("sin".to_string()),
1638            vec![Value::Num(0.0)],
1639        ))
1640        .expect_err("feval string handle without @ should fail");
1641        assert_eq!(err.identifier(), Some("RunMat:FevalHandleStringInvalid"));
1642    }
1643
1644    #[test]
1645    fn feval_rejects_char_handle_without_at_with_identifier() {
1646        let err = block_on(feval_builtin(
1647            Value::CharArray(runmat_builtins::CharArray::new_row("sin")),
1648            vec![Value::Num(0.0)],
1649        ))
1650        .expect_err("feval char handle without @ should fail");
1651        assert_eq!(err.identifier(), Some("RunMat:FevalHandleStringInvalid"));
1652    }
1653
1654    #[test]
1655    fn feval_rejects_non_row_char_handle_with_identifier() {
1656        let chars = runmat_builtins::CharArray::new(vec!['@', 's'], 2, 1)
1657            .expect("char array construction should succeed");
1658        let err = block_on(feval_builtin(
1659            Value::CharArray(chars),
1660            vec![Value::Num(0.0)],
1661        ))
1662        .expect_err("feval non-row char handle should fail");
1663        assert_eq!(err.identifier(), Some("RunMat:FevalHandleShapeInvalid"));
1664    }
1665
1666    #[test]
1667    fn feval_rejects_empty_at_string_handle_with_identifier() {
1668        let err = block_on(feval_builtin(
1669            Value::String("@".to_string()),
1670            vec![Value::Num(0.0)],
1671        ))
1672        .expect_err("feval empty @string handle should fail");
1673        assert_eq!(err.identifier(), Some("RunMat:FevalHandleNameInvalid"));
1674    }
1675
1676    #[test]
1677    fn feval_rejects_empty_function_handle_value_with_identifier() {
1678        let err = block_on(feval_builtin(
1679            Value::FunctionHandle(String::new()),
1680            vec![Value::Num(0.0)],
1681        ))
1682        .expect_err("feval empty function-handle value should fail");
1683        assert_eq!(err.identifier(), Some("RunMat:FevalHandleNameInvalid"));
1684    }
1685
1686    #[test]
1687    fn feval_trims_text_handle_name_for_resolution() {
1688        let _resolver_guard =
1689            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1690                (name == "resolved_target").then_some(9876)
1691            })));
1692        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
1693            Arc::new(|function, args, requested_outputs| {
1694                assert_eq!(function, 9876);
1695                assert_eq!(requested_outputs, 1);
1696                assert_eq!(args, &[Value::Num(4.0)]);
1697                Box::pin(async { Ok(Value::Num(12.0)) })
1698            }),
1699        ));
1700
1701        let value = block_on(feval_builtin(
1702            Value::String("@ resolved_target ".to_string()),
1703            vec![Value::Num(4.0)],
1704        ))
1705        .expect("trimmed text handle should resolve");
1706        assert_eq!(value, Value::Num(12.0));
1707    }
1708
1709    #[test]
1710    fn str2func_returns_semantic_handle_when_resolver_can_resolve() {
1711        let _resolver_guard =
1712            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1713                (name == "resolved_target").then_some(145)
1714            })));
1715        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1716            Value::String("resolved_target".to_string()),
1717        )
1718        .expect("str2func should succeed");
1719        assert_eq!(
1720            value,
1721            Value::BoundFunctionHandle {
1722                name: "resolved_target".to_string(),
1723                function: 145,
1724            }
1725        );
1726    }
1727
1728    #[test]
1729    fn str2func_returns_dynamic_handle_when_resolver_cannot_resolve() {
1730        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1731        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1732            Value::String("@missing_target".to_string()),
1733        )
1734        .expect("str2func should succeed");
1735        assert_eq!(value, Value::FunctionHandle("missing_target".to_string()));
1736    }
1737
1738    #[test]
1739    fn str2func_returns_external_handle_for_qualified_name() {
1740        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1741        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1742            Value::String("Point.origin".to_string()),
1743        )
1744        .expect("str2func should succeed");
1745        assert_eq!(
1746            value,
1747            Value::ExternalFunctionHandle("Point.origin".to_string())
1748        );
1749    }
1750
1751    #[test]
1752    fn str2func_malformed_qualified_name_returns_dynamic_handle() {
1753        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1754        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1755            Value::String("Point..origin".to_string()),
1756        )
1757        .expect("str2func should succeed");
1758        assert_eq!(value, Value::FunctionHandle("Point..origin".to_string()));
1759    }
1760
1761    #[test]
1762    fn func2str_rejects_non_handle_with_identifier() {
1763        let err = crate::builtins::introspection::function_handle_text::dispatch_func2str(
1764            Value::Num(1.0),
1765        )
1766        .expect_err("func2str non-handle input should fail");
1767        assert_eq!(err.identifier(), Some("RunMat:Func2StrHandleTypeInvalid"));
1768    }
1769
1770    #[test]
1771    fn str2func_rejects_empty_name_with_identifier() {
1772        let err = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1773            Value::String("   ".to_string()),
1774        )
1775        .expect_err("empty function name should fail");
1776        assert_eq!(err.identifier(), Some("RunMat:Str2FuncNameInvalid"));
1777    }
1778
1779    #[test]
1780    fn str2func_rejects_non_row_char_name_with_identifier() {
1781        let chars = runmat_builtins::CharArray::new(vec!['a', 'b'], 2, 1)
1782            .expect("char array construction should succeed");
1783        let err = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1784            Value::CharArray(chars),
1785        )
1786        .expect_err("non-row char-array function name should fail");
1787        assert_eq!(err.identifier(), Some("RunMat:Str2FuncNameShapeInvalid"));
1788    }
1789
1790    #[test]
1791    fn str2func_rejects_non_text_name_with_identifier() {
1792        let err = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1793            Value::Num(1.0),
1794        )
1795        .expect_err("non-text function name should fail");
1796        assert_eq!(err.identifier(), Some("RunMat:Str2FuncNameTypeInvalid"));
1797    }
1798
1799    #[test]
1800    fn str2func_accepts_scalar_string_array_name() {
1801        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1802        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1803            Value::StringArray(
1804                runmat_builtins::StringArray::new(vec!["@missing_target".to_string()], vec![1, 1])
1805                    .expect("string array construction should succeed"),
1806            ),
1807        )
1808        .expect("scalar string-array function name should succeed");
1809        assert_eq!(value, Value::FunctionHandle("missing_target".to_string()));
1810    }
1811
1812    #[test]
1813    fn str2func_rejects_nonscalar_string_array_name_with_identifier() {
1814        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1815        let value = Value::StringArray(
1816            runmat_builtins::StringArray::new(vec!["@a".to_string(), "@b".to_string()], vec![1, 2])
1817                .expect("string array construction should succeed"),
1818        );
1819        let err = crate::builtins::introspection::function_handle_text::dispatch_str2func(value)
1820            .expect_err("nonscalar string-array function name must fail");
1821        assert_eq!(err.identifier(), Some("RunMat:Str2FuncNameShapeInvalid"));
1822    }
1823
1824    #[test]
1825    fn str2func_scalar_string_array_prefers_semantic_handle_when_resolved() {
1826        let _resolver_guard =
1827            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1828                (name == "resolved_target").then_some(445)
1829            })));
1830        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1831            Value::StringArray(
1832                runmat_builtins::StringArray::new(vec!["@resolved_target".to_string()], vec![1, 1])
1833                    .expect("string array construction should succeed"),
1834            ),
1835        )
1836        .expect("scalar string-array function name should resolve semantically");
1837        assert_eq!(
1838            value,
1839            Value::BoundFunctionHandle {
1840                name: "resolved_target".to_string(),
1841                function: 445,
1842            }
1843        );
1844    }
1845
1846    #[test]
1847    fn str2func_scalar_string_array_returns_external_handle_for_qualified_name() {
1848        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1849        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1850            Value::StringArray(
1851                runmat_builtins::StringArray::new(vec!["Point.origin".to_string()], vec![1, 1])
1852                    .expect("string array construction should succeed"),
1853            ),
1854        )
1855        .expect("scalar string-array qualified name should succeed");
1856        assert_eq!(
1857            value,
1858            Value::ExternalFunctionHandle("Point.origin".to_string())
1859        );
1860    }
1861
1862    #[test]
1863    fn str2func_scalar_string_array_malformed_qualified_name_returns_dynamic_handle() {
1864        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1865        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1866            Value::StringArray(
1867                runmat_builtins::StringArray::new(vec!["Point..origin".to_string()], vec![1, 1])
1868                    .expect("string array construction should succeed"),
1869            ),
1870        )
1871        .expect("scalar string-array malformed qualified name should succeed");
1872        assert_eq!(value, Value::FunctionHandle("Point..origin".to_string()));
1873    }
1874
1875    #[test]
1876    fn str2func_scalar_string_array_rejects_empty_name_with_identifier() {
1877        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1878        let err = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1879            Value::StringArray(
1880                runmat_builtins::StringArray::new(vec!["   ".to_string()], vec![1, 1])
1881                    .expect("string array construction should succeed"),
1882            ),
1883        )
1884        .expect_err("scalar string-array empty function name should fail");
1885        assert_eq!(err.identifier(), Some("RunMat:Str2FuncNameInvalid"));
1886    }
1887
1888    #[test]
1889    fn str2func_scalar_string_array_qualified_name_prefers_semantic_handle_when_resolved() {
1890        let _resolver_guard =
1891            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
1892                (name == "pkg.resolved_target").then_some(446)
1893            })));
1894        let value = crate::builtins::introspection::function_handle_text::dispatch_str2func(
1895            Value::StringArray(
1896                runmat_builtins::StringArray::new(
1897                    vec!["@pkg.resolved_target".to_string()],
1898                    vec![1, 1],
1899                )
1900                .expect("string array construction should succeed"),
1901            ),
1902        )
1903        .expect("scalar string-array qualified function name should resolve semantically");
1904        assert_eq!(
1905            value,
1906            Value::BoundFunctionHandle {
1907                name: "pkg.resolved_target".to_string(),
1908                function: 446,
1909            }
1910        );
1911    }
1912
1913    #[test]
1914    fn getmethod_classref_returns_typed_external_function_handle() {
1915        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
1916        let value = crate::builtins::introspection::getmethod::dispatch_getmethod(
1917            Value::ClassRef("Point".to_string()),
1918            "origin".to_string(),
1919        )
1920        .expect("getmethod should resolve classref method handle");
1921        assert_eq!(
1922            value,
1923            Value::ExternalFunctionHandle("Point.origin".to_string())
1924        );
1925    }
1926
1927    #[test]
1928    fn getmethod_rejects_empty_method_name() {
1929        let err = crate::builtins::introspection::getmethod::dispatch_getmethod(
1930            Value::ClassRef("Point".to_string()),
1931            "   ".to_string(),
1932        )
1933        .expect_err("empty method name should be rejected");
1934        assert_eq!(err.identifier(), Some("RunMat:GetMethodNameInvalid"));
1935    }
1936
1937    #[test]
1938    fn getmethod_rejects_unsupported_receiver_with_identifier() {
1939        let err = crate::builtins::introspection::getmethod::dispatch_getmethod(
1940            Value::Num(1.0),
1941            "origin".to_string(),
1942        )
1943        .expect_err("unsupported receiver should be rejected");
1944        assert_eq!(
1945            err.identifier(),
1946            Some("RunMat:GetMethodReceiverUnsupported")
1947        );
1948    }
1949
1950    #[test]
1951    fn create_class_object_handles_class_parent_cycles() {
1952        let class_a = unique_class_name("runtime_ctor_cycle_a");
1953        let class_b = unique_class_name("runtime_ctor_cycle_b");
1954
1955        let mut props_a = HashMap::new();
1956        props_a.insert(
1957            "fromA".to_string(),
1958            PropertyDef {
1959                name: "fromA".to_string(),
1960                is_static: false,
1961                is_constant: false,
1962                is_dependent: false,
1963                get_access: Access::Public,
1964                set_access: Access::Public,
1965                default_value: Some(Value::Num(1.0)),
1966            },
1967        );
1968        let mut props_b = HashMap::new();
1969        props_b.insert(
1970            "fromB".to_string(),
1971            PropertyDef {
1972                name: "fromB".to_string(),
1973                is_static: false,
1974                is_constant: false,
1975                is_dependent: false,
1976                get_access: Access::Public,
1977                set_access: Access::Public,
1978                default_value: Some(Value::Num(2.0)),
1979            },
1980        );
1981
1982        register_class(ClassDef {
1983            name: class_a.clone(),
1984            parent: Some(class_b.clone()),
1985            properties: props_a,
1986            methods: HashMap::new(),
1987        });
1988        register_class(ClassDef {
1989            name: class_b,
1990            parent: Some(class_a.clone()),
1991            properties: props_b,
1992            methods: HashMap::new(),
1993        });
1994
1995        let value = block_on(create_class_object(class_a.clone()))
1996            .expect("constructor should terminate under parent-cycle metadata");
1997        let Value::Object(obj) = value else {
1998            panic!("expected object result");
1999        };
2000        assert_eq!(obj.class_name, class_a);
2001        assert_eq!(obj.properties.get("fromA"), Some(&Value::Num(1.0)));
2002        assert_eq!(obj.properties.get("fromB"), Some(&Value::Num(2.0)));
2003    }
2004
2005    #[test]
2006    fn create_class_object_abstract_class_reports_stable_identifier() {
2007        let class_name = unique_class_name("runtime_ctor_abstract");
2008        runmat_builtins::register_class_with_modifiers(
2009            ClassDef {
2010                name: class_name.clone(),
2011                parent: None,
2012                properties: HashMap::new(),
2013                methods: HashMap::new(),
2014            },
2015            false,
2016            true,
2017        );
2018
2019        let err = block_on(create_class_object(class_name))
2020            .expect_err("abstract class instantiation should fail");
2021        assert_eq!(err.identifier(), Some("RunMat:AbstractMethodMissing"));
2022        assert!(err.message().contains("Cannot instantiate abstract class"));
2023    }
2024
2025    #[test]
2026    fn callable_identity_for_malformed_handle_name_stays_dynamic() {
2027        let (identity, fallback_policy) = callable_identity_for_handle_name("pkg..remote_inc");
2028        assert!(matches!(
2029            identity,
2030            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(name))
2031                if name == "pkg..remote_inc"
2032        ));
2033        assert_eq!(
2034            fallback_policy,
2035            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution
2036        );
2037    }
2038
2039    #[test]
2040    fn unresolved_callable_without_display_name_reports_typed_identity() {
2041        let err = block_on(dispatch_callable_with_policy(
2042            runmat_hir::CallableIdentity::AnonymousFunction(runmat_hir::FunctionId(77)),
2043            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2044            vec![],
2045            1,
2046        ))
2047        .expect_err("anonymous callable identity should fail unresolved");
2048        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
2049        assert!(
2050            err.message().contains("AnonymousFunction(FunctionId(77))"),
2051            "unexpected error: {err:?}"
2052        );
2053    }
2054
2055    #[test]
2056    fn unresolved_malformed_external_callable_reports_typed_identity() {
2057        let err = block_on(dispatch_callable_with_policy(
2058            runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
2059                runmat_hir::SymbolName("pkg".to_string()),
2060                runmat_hir::SymbolName("".to_string()),
2061                runmat_hir::SymbolName("remote".to_string()),
2062            ])),
2063            runmat_hir::CallableFallbackPolicy::ExternalBoundary,
2064            vec![],
2065            1,
2066        ))
2067        .expect_err("malformed external callable identity should fail unresolved");
2068        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
2069        assert!(
2070            err.message()
2071                .contains("ExternalName(QualifiedName([SymbolName(\"pkg\"), SymbolName(\"\"), SymbolName(\"remote\")]))"),
2072            "unexpected error: {err:?}"
2073        );
2074    }
2075
2076    #[test]
2077    fn unresolved_method_callable_reports_typed_identity() {
2078        let err = block_on(dispatch_callable_with_policy(
2079            runmat_hir::CallableIdentity::Method(runmat_hir::MethodId(
2080                "missing_method".to_string(),
2081            )),
2082            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2083            vec![],
2084            1,
2085        ))
2086        .expect_err("method callable identity should fail unresolved");
2087        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
2088        assert!(
2089            err.message()
2090                .contains("Method(MethodId(\"missing_method\"))"),
2091            "unexpected error: {err:?}"
2092        );
2093        assert!(
2094            !err.message()
2095                .contains("Undefined function 'missing_method'"),
2096            "method identity should not use fallback display-name text: {err:?}"
2097        );
2098    }
2099
2100    #[test]
2101    fn feval_qualified_at_handle_errors_as_unresolved_external() {
2102        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
2103        let err = block_on(feval_builtin(
2104            Value::String("@missing.external".to_string()),
2105            vec![Value::Num(1.0)],
2106        ))
2107        .expect_err("qualified @handle should error when unresolved");
2108        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
2109        assert!(
2110            err.message().contains("missing.external"),
2111            "unexpected error: {err:?}"
2112        );
2113    }
2114
2115    #[test]
2116    fn func2str_extracts_name_from_function_handles() {
2117        assert_eq!(
2118            crate::builtins::introspection::function_handle_text::dispatch_func2str(
2119                Value::FunctionHandle("sin".to_string())
2120            )
2121            .expect("func2str"),
2122            Value::String("sin".to_string())
2123        );
2124        assert_eq!(
2125            crate::builtins::introspection::function_handle_text::dispatch_func2str(
2126                Value::ExternalFunctionHandle("Point.origin".to_string())
2127            )
2128            .expect("func2str"),
2129            Value::String("Point.origin".to_string())
2130        );
2131        assert_eq!(
2132            crate::builtins::introspection::function_handle_text::dispatch_func2str(
2133                Value::BoundFunctionHandle {
2134                    name: "local_fn".to_string(),
2135                    function: 44,
2136                }
2137            )
2138            .expect("func2str"),
2139            Value::String("local_fn".to_string())
2140        );
2141        assert_eq!(
2142            crate::builtins::introspection::function_handle_text::dispatch_func2str(
2143                Value::Closure(runmat_builtins::Closure {
2144                    function_name: "captured_fn".to_string(),
2145                    bound_function: None,
2146                    captures: Vec::new(),
2147                })
2148            )
2149            .expect("func2str"),
2150            Value::String("captured_fn".to_string())
2151        );
2152    }
2153
2154    #[test]
2155    fn none_policy_does_not_use_semantic_resolver() {
2156        let _resolver_guard =
2157            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2158                (name == "resolved_target").then_some(45)
2159            })));
2160        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2161            Arc::new(|function, args, requested_outputs| {
2162                assert_eq!(function, 45);
2163                assert_eq!(requested_outputs, 1);
2164                assert_eq!(args, &[Value::Num(4.0)]);
2165                Box::pin(async { Ok(Value::Num(11.0)) })
2166            }),
2167        ));
2168
2169        let request = crate::user_functions::CallableRequest::resolved(
2170            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(
2171                "resolved_target".to_string(),
2172            )),
2173            runmat_hir::CallableFallbackPolicy::None,
2174            vec![Value::Num(4.0)],
2175            1,
2176        );
2177
2178        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request));
2179        assert!(result.is_none());
2180    }
2181
2182    #[test]
2183    fn runtime_name_resolution_policy_uses_semantic_resolver() {
2184        let _resolver_guard =
2185            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2186                (name == "resolved_target").then_some(45)
2187            })));
2188        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2189            Arc::new(|function, args, requested_outputs| {
2190                assert_eq!(function, 45);
2191                assert_eq!(requested_outputs, 1);
2192                assert_eq!(args, &[Value::Num(4.0)]);
2193                Box::pin(async { Ok(Value::Num(11.0)) })
2194            }),
2195        ));
2196
2197        let request = crate::user_functions::CallableRequest::resolved(
2198            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(
2199                "resolved_target".to_string(),
2200            )),
2201            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2202            vec![Value::Num(4.0)],
2203            1,
2204        );
2205
2206        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request))
2207            .expect("runtime resolution should attempt semantic resolver")
2208            .expect("semantic invoker should succeed");
2209        assert_eq!(result, Value::Num(11.0));
2210    }
2211
2212    #[test]
2213    fn object_dispatch_policy_does_not_use_semantic_resolver() {
2214        let _resolver_guard =
2215            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2216                (name == "resolved_target").then_some(45)
2217            })));
2218        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2219            Arc::new(|function, args, requested_outputs| {
2220                assert_eq!(function, 45);
2221                assert_eq!(requested_outputs, 1);
2222                assert_eq!(args, &[Value::Num(4.0)]);
2223                Box::pin(async { Ok(Value::Num(11.0)) })
2224            }),
2225        ));
2226
2227        let request = crate::user_functions::CallableRequest::resolved(
2228            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(
2229                "resolved_target".to_string(),
2230            )),
2231            runmat_hir::CallableFallbackPolicy::ObjectDispatch,
2232            vec![Value::Num(4.0)],
2233            1,
2234        );
2235
2236        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request));
2237        assert!(result.is_none());
2238    }
2239
2240    #[test]
2241    fn external_name_runtime_name_resolution_policy_does_not_use_semantic_resolver() {
2242        let _resolver_guard =
2243            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2244                (name == "resolved_target").then_some(45)
2245            })));
2246        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2247            Arc::new(|function, args, requested_outputs| {
2248                assert_eq!(function, 45);
2249                assert_eq!(requested_outputs, 1);
2250                assert_eq!(args, &[Value::Num(4.0)]);
2251                Box::pin(async { Ok(Value::Num(11.0)) })
2252            }),
2253        ));
2254
2255        let request = crate::user_functions::CallableRequest::resolved(
2256            runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
2257                runmat_hir::SymbolName("resolved_target".to_string()),
2258            ])),
2259            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2260            vec![Value::Num(4.0)],
2261            1,
2262        );
2263
2264        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request));
2265        assert!(result.is_none());
2266    }
2267
2268    #[test]
2269    fn external_boundary_policy_uses_semantic_resolver() {
2270        let _resolver_guard =
2271            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2272                (name == "pkg.resolved_target").then_some(45)
2273            })));
2274        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2275            Arc::new(|function, args, requested_outputs| {
2276                assert_eq!(function, 45);
2277                assert_eq!(requested_outputs, 1);
2278                assert_eq!(args, &[Value::Num(4.0)]);
2279                Box::pin(async { Ok(Value::Num(11.0)) })
2280            }),
2281        ));
2282
2283        let request = crate::user_functions::CallableRequest::resolved(
2284            runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
2285                runmat_hir::SymbolName("pkg".to_string()),
2286                runmat_hir::SymbolName("resolved_target".to_string()),
2287            ])),
2288            runmat_hir::CallableFallbackPolicy::ExternalBoundary,
2289            vec![Value::Num(4.0)],
2290            1,
2291        );
2292
2293        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request))
2294            .expect("external boundary policy should attempt semantic resolver")
2295            .expect("semantic invoker should succeed");
2296        assert_eq!(result, Value::Num(11.0));
2297    }
2298
2299    #[test]
2300    fn external_boundary_policy_malformed_external_identity_does_not_use_semantic_resolver() {
2301        let _resolver_guard =
2302            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2303                (name == "pkg..resolved_target").then_some(45)
2304            })));
2305        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2306            Arc::new(|function, args, requested_outputs| {
2307                assert_eq!(function, 45);
2308                assert_eq!(requested_outputs, 1);
2309                assert_eq!(args, &[Value::Num(4.0)]);
2310                Box::pin(async { Ok(Value::Num(11.0)) })
2311            }),
2312        ));
2313
2314        let request = crate::user_functions::CallableRequest::resolved(
2315            runmat_hir::CallableIdentity::ExternalName(runmat_hir::QualifiedName(vec![
2316                runmat_hir::SymbolName("pkg..resolved_target".to_string()),
2317            ])),
2318            runmat_hir::CallableFallbackPolicy::ExternalBoundary,
2319            vec![Value::Num(4.0)],
2320            1,
2321        );
2322
2323        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request));
2324        assert!(result.is_none());
2325    }
2326
2327    #[test]
2328    fn runtime_name_resolution_policy_uses_semantic_resolver_after_object_probe() {
2329        let _resolver_guard =
2330            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2331                (name == "resolved_target").then_some(45)
2332            })));
2333        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2334            Arc::new(|function, args, requested_outputs| {
2335                assert_eq!(function, 45);
2336                assert_eq!(requested_outputs, 1);
2337                assert_eq!(args, &[Value::Num(4.0)]);
2338                Box::pin(async { Ok(Value::Num(11.0)) })
2339            }),
2340        ));
2341
2342        let request = crate::user_functions::CallableRequest::resolved(
2343            runmat_hir::CallableIdentity::DynamicName(runmat_hir::SymbolName(
2344                "resolved_target".to_string(),
2345            )),
2346            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2347            vec![Value::Num(4.0)],
2348            1,
2349        );
2350
2351        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request))
2352            .expect("post-object-probe runtime-name policy should attempt semantic resolver")
2353            .expect("semantic invoker should succeed");
2354        assert_eq!(result, Value::Num(11.0));
2355    }
2356
2357    #[test]
2358    fn method_identity_runtime_name_resolution_policy_uses_semantic_resolver() {
2359        let _resolver_guard =
2360            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2361                (name == "resolved_target").then_some(45)
2362            })));
2363        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2364            Arc::new(|function, args, requested_outputs| {
2365                assert_eq!(function, 45);
2366                assert_eq!(requested_outputs, 1);
2367                assert_eq!(args, &[Value::Num(4.0)]);
2368                Box::pin(async { Ok(Value::Num(11.0)) })
2369            }),
2370        ));
2371
2372        let request = crate::user_functions::CallableRequest::resolved(
2373            runmat_hir::CallableIdentity::Method(runmat_hir::MethodId(
2374                "resolved_target".to_string(),
2375            )),
2376            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2377            vec![Value::Num(4.0)],
2378            1,
2379        );
2380
2381        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request))
2382            .expect("method runtime-name policy should attempt semantic resolver")
2383            .expect("semantic invoker should succeed");
2384        assert_eq!(result, Value::Num(11.0));
2385    }
2386
2387    #[test]
2388    fn imported_identity_runtime_name_resolution_policy_uses_semantic_resolver() {
2389        let _resolver_guard =
2390            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2391                (name == "Point.origin").then_some(45)
2392            })));
2393        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2394            Arc::new(|function, args, requested_outputs| {
2395                assert_eq!(function, 45);
2396                assert_eq!(requested_outputs, 1);
2397                assert_eq!(args, &[Value::Num(4.0)]);
2398                Box::pin(async { Ok(Value::Num(11.0)) })
2399            }),
2400        ));
2401
2402        let request = crate::user_functions::CallableRequest::resolved(
2403            runmat_hir::CallableIdentity::Imported(runmat_hir::DefPath {
2404                package: runmat_hir::PackageName("Point".to_string()),
2405                module: runmat_hir::QualifiedName(vec![
2406                    runmat_hir::SymbolName("Point".to_string()),
2407                    runmat_hir::SymbolName("origin".to_string()),
2408                ]),
2409                item: vec![runmat_hir::DefPathSegment::Function(
2410                    runmat_hir::SymbolName("origin".to_string()),
2411                )],
2412            }),
2413            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2414            vec![Value::Num(4.0)],
2415            1,
2416        );
2417
2418        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request))
2419            .expect("imported runtime-name policy should attempt semantic resolver")
2420            .expect("semantic invoker should succeed");
2421        assert_eq!(result, Value::Num(11.0));
2422    }
2423
2424    #[test]
2425    fn imported_identity_runtime_name_resolution_policy_rejects_malformed_path_without_semantic_probe(
2426    ) {
2427        let resolver_calls = Arc::new(AtomicUsize::new(0));
2428        let resolver_calls_for_closure = Arc::clone(&resolver_calls);
2429        let _resolver_guard =
2430            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(move |_| {
2431                resolver_calls_for_closure.fetch_add(1, Ordering::Relaxed);
2432                Some(45)
2433            })));
2434        let _invoker_guard = crate::user_functions::install_semantic_function_invoker(Some(
2435            Arc::new(|function, args, requested_outputs| {
2436                assert_eq!(function, 45);
2437                assert_eq!(requested_outputs, 1);
2438                assert_eq!(args, &[Value::Num(4.0)]);
2439                Box::pin(async { Ok(Value::Num(11.0)) })
2440            }),
2441        ));
2442
2443        let request = crate::user_functions::CallableRequest::resolved(
2444            runmat_hir::CallableIdentity::Imported(runmat_hir::DefPath {
2445                package: runmat_hir::PackageName("Point".to_string()),
2446                module: runmat_hir::QualifiedName(vec![
2447                    runmat_hir::SymbolName("Point".to_string()),
2448                    runmat_hir::SymbolName("origin".to_string()),
2449                ]),
2450                item: vec![runmat_hir::DefPathSegment::Function(
2451                    runmat_hir::SymbolName("other".to_string()),
2452                )],
2453            }),
2454            runmat_hir::CallableFallbackPolicy::RuntimeNameResolution,
2455            vec![Value::Num(4.0)],
2456            1,
2457        );
2458
2459        let result = block_on(crate::user_functions::try_call_semantic_descriptor(request));
2460        assert!(
2461            result.is_none(),
2462            "mismatched imported identity should not attempt semantic resolver"
2463        );
2464        assert_eq!(
2465            resolver_calls.load(Ordering::Relaxed),
2466            0,
2467            "malformed imported identity should be rejected before resolver probe"
2468        );
2469    }
2470
2471    #[test]
2472    fn call_method_fallback_preserves_requested_outputs() {
2473        let _output_guard = crate::output_count::push_output_count(Some(2));
2474        let base = Value::Object(runmat_builtins::ObjectInstance::new(
2475            "NoSuchMethodClass".to_string(),
2476        ));
2477        let result = block_on(
2478            crate::builtins::introspection::call_method::dispatch_call_method(
2479                base.clone(),
2480                "deal".to_string(),
2481                vec![Value::Num(9.0), Value::Num(10.0)],
2482            ),
2483        )
2484        .expect("call_method fallback should succeed");
2485        match result {
2486            Value::OutputList(values) => {
2487                assert!(values.len() >= 2);
2488                assert_eq!(values[0], base);
2489                assert_eq!(values[1], Value::Num(9.0));
2490            }
2491            other => {
2492                panic!("expected output list from multi-output call_method fallback, got {other:?}")
2493            }
2494        }
2495    }
2496
2497    #[test]
2498    fn call_method_trims_method_name_for_resolution() {
2499        let _output_guard = crate::output_count::push_output_count(Some(2));
2500        let base = Value::Object(runmat_builtins::ObjectInstance::new(
2501            "NoSuchMethodClass".to_string(),
2502        ));
2503        let result = block_on(
2504            crate::builtins::introspection::call_method::dispatch_call_method(
2505                base.clone(),
2506                "  deal  ".to_string(),
2507                vec![Value::Num(9.0), Value::Num(10.0)],
2508            ),
2509        )
2510        .expect("call_method fallback should succeed after method-name trimming");
2511        match result {
2512            Value::OutputList(values) => {
2513                assert!(values.len() >= 2);
2514                assert_eq!(values[0], base);
2515                assert_eq!(values[1], Value::Num(9.0));
2516            }
2517            other => {
2518                panic!("expected output list from trimmed-name call_method fallback, got {other:?}")
2519            }
2520        }
2521    }
2522
2523    #[test]
2524    fn feval_call_method_closure_fast_path_preserves_requested_outputs() {
2525        let _output_guard = crate::output_count::push_output_count(Some(2));
2526        let base = Value::Object(runmat_builtins::ObjectInstance::new(
2527            "NoSuchMethodClass".to_string(),
2528        ));
2529        let closure = Value::Closure(runmat_builtins::Closure {
2530            function_name: CALL_METHOD_BUILTIN_NAME.to_string(),
2531            bound_function: None,
2532            captures: vec![
2533                base.clone(),
2534                Value::String("deal".to_string()),
2535                Value::Num(9.0),
2536            ],
2537        });
2538        let result = block_on(feval_builtin(closure, vec![Value::Num(10.0)]))
2539            .expect("feval call_method closure should succeed");
2540        match result {
2541            Value::OutputList(values) => {
2542                assert!(values.len() >= 2);
2543                assert_eq!(values[0], base);
2544                assert_eq!(values[1], Value::Num(9.0));
2545            }
2546            other => {
2547                panic!(
2548                    "expected output list from feval call_method closure fast path, got {other:?}"
2549                )
2550            }
2551        }
2552    }
2553
2554    #[test]
2555    fn feval_call_method_closure_fast_path_trims_method_name_for_resolution() {
2556        let _output_guard = crate::output_count::push_output_count(Some(2));
2557        let base = Value::Object(runmat_builtins::ObjectInstance::new(
2558            "NoSuchMethodClass".to_string(),
2559        ));
2560        let closure = Value::Closure(runmat_builtins::Closure {
2561            function_name: CALL_METHOD_BUILTIN_NAME.to_string(),
2562            bound_function: None,
2563            captures: vec![
2564                base.clone(),
2565                Value::String("  deal  ".to_string()),
2566                Value::Num(9.0),
2567            ],
2568        });
2569        let result = block_on(feval_builtin(closure, vec![Value::Num(10.0)]))
2570            .expect("feval call_method closure should succeed after method-name trimming");
2571        match result {
2572            Value::OutputList(values) => {
2573                assert!(values.len() >= 2);
2574                assert_eq!(values[0], base);
2575                assert_eq!(values[1], Value::Num(9.0));
2576            }
2577            other => {
2578                panic!(
2579                    "expected output list from trimmed call_method closure fast path, got {other:?}"
2580                )
2581            }
2582        }
2583    }
2584
2585    #[test]
2586    fn feval_call_method_closure_rejects_nontext_method_capture_with_identifier() {
2587        let closure = Value::Closure(runmat_builtins::Closure {
2588            function_name: CALL_METHOD_BUILTIN_NAME.to_string(),
2589            bound_function: None,
2590            captures: vec![
2591                Value::Object(runmat_builtins::ObjectInstance::new("Point".to_string())),
2592                Value::Num(1.0),
2593            ],
2594        });
2595        let err = block_on(feval_builtin(closure, Vec::new()))
2596            .expect_err("feval call_method closure should reject nontext method capture");
2597        assert_eq!(err.identifier(), Some("RunMat:CallMethodNameInvalid"));
2598    }
2599
2600    #[test]
2601    fn call_method_rejects_non_object_receiver_with_identifier() {
2602        let err = block_on(
2603            crate::builtins::introspection::call_method::dispatch_call_method(
2604                Value::Num(1.0),
2605                "origin".to_string(),
2606                Vec::new(),
2607            ),
2608        )
2609        .expect_err("non-object receiver should fail");
2610        assert_eq!(err.identifier(), Some("RunMat:InvalidObjectDispatch"));
2611    }
2612
2613    #[test]
2614    fn call_method_rejects_empty_method_name_with_identifier() {
2615        let err = block_on(
2616            crate::builtins::introspection::call_method::dispatch_call_method(
2617                Value::Object(runmat_builtins::ObjectInstance::new("Point".to_string())),
2618                "  ".to_string(),
2619                Vec::new(),
2620            ),
2621        )
2622        .expect_err("empty method name should fail");
2623        assert_eq!(err.identifier(), Some("RunMat:CallMethodNameInvalid"));
2624    }
2625
2626    #[test]
2627    fn subsref_rejects_non_object_receiver_with_identifier() {
2628        let err = block_on(
2629            crate::builtins::introspection::object_indexing::dispatch_subsref(
2630                Value::Num(1.0),
2631                OBJECT_INDEX_PAREN.to_string(),
2632                Value::Num(2.0),
2633            ),
2634        )
2635        .expect_err("non-object subsref receiver should fail");
2636        assert_eq!(err.identifier(), Some("RunMat:InvalidObjectDispatch"));
2637    }
2638
2639    #[test]
2640    fn subsasgn_rejects_non_object_receiver_with_identifier() {
2641        let err = block_on(
2642            crate::builtins::introspection::object_indexing::dispatch_subsasgn(
2643                Value::Num(1.0),
2644                OBJECT_INDEX_PAREN.to_string(),
2645                Value::Num(2.0),
2646                Value::Num(3.0),
2647            ),
2648        )
2649        .expect_err("non-object subsasgn receiver should fail");
2650        assert_eq!(err.identifier(), Some("RunMat:InvalidObjectDispatch"));
2651    }
2652
2653    #[test]
2654    fn subsref_missing_protocol_errors_with_identifier() {
2655        let err = block_on(
2656            crate::builtins::introspection::object_indexing::dispatch_subsref(
2657                Value::Object(runmat_builtins::ObjectInstance::new(
2658                    "NoSubsrefProtocolClass".to_string(),
2659                )),
2660                OBJECT_INDEX_PAREN.to_string(),
2661                Value::Cell(runmat_builtins::CellArray::new(vec![Value::Num(1.0)], 1, 1).unwrap()),
2662            ),
2663        )
2664        .expect_err("missing subsref protocol should fail");
2665        assert_eq!(err.identifier(), Some("RunMat:MissingSubsref"));
2666    }
2667
2668    #[test]
2669    fn subsasgn_missing_protocol_errors_with_identifier() {
2670        let err = block_on(
2671            crate::builtins::introspection::object_indexing::dispatch_subsasgn(
2672                Value::Object(runmat_builtins::ObjectInstance::new(
2673                    "NoSubsasgnProtocolClass".to_string(),
2674                )),
2675                OBJECT_INDEX_PAREN.to_string(),
2676                Value::Cell(runmat_builtins::CellArray::new(vec![Value::Num(1.0)], 1, 1).unwrap()),
2677                Value::Num(3.0),
2678            ),
2679        )
2680        .expect_err("missing subsasgn protocol should fail");
2681        assert_eq!(err.identifier(), Some("RunMat:MissingSubsasgn"));
2682    }
2683
2684    #[test]
2685    fn get_p_rejects_non_object_receiver_with_identifier() {
2686        let err = block_on(get_p_builtin(Value::Num(1.0)))
2687            .expect_err("get.p should reject non-object receiver");
2688        assert_eq!(err.identifier(), Some("RunMat:GetPReceiverInvalid"));
2689    }
2690
2691    #[test]
2692    fn set_p_rejects_non_object_receiver_with_identifier() {
2693        let err = block_on(set_p_builtin(Value::Num(1.0), Value::Num(2.0)))
2694            .expect_err("set.p should reject non-object receiver");
2695        assert_eq!(err.identifier(), Some("RunMat:SetPReceiverInvalid"));
2696    }
2697
2698    #[test]
2699    fn point_move_rejects_non_object_receiver_with_identifier() {
2700        let err = block_on(point_move_method(Value::Num(1.0), 2.0, 3.0))
2701            .expect_err("Point.move should reject non-object receiver");
2702        assert_eq!(err.identifier(), Some("RunMat:PointMoveReceiverInvalid"));
2703    }
2704
2705    #[test]
2706    fn circle_area_rejects_non_object_receiver_with_identifier() {
2707        let err = block_on(circle_area_method(Value::Num(1.0)))
2708            .expect_err("Circle.area should reject non-object receiver");
2709        assert_eq!(err.identifier(), Some("RunMat:CircleAreaReceiverInvalid"));
2710    }
2711
2712    #[test]
2713    fn overidx_plus_rejects_non_object_receiver_with_identifier() {
2714        let err = block_on(overidx_plus(Value::Num(1.0), Value::Num(2.0)))
2715            .expect_err("OverIdx.plus should reject non-object receiver");
2716        assert_eq!(err.identifier(), Some("RunMat:OverIdxReceiverInvalid"));
2717    }
2718
2719    #[test]
2720    fn overidx_subsref_unsupported_payload_errors_with_identifier() {
2721        let err = block_on(overidx_subsref(
2722            Value::Object(runmat_builtins::ObjectInstance::new("OverIdx".to_string())),
2723            OBJECT_INDEX_PAREN.to_string(),
2724            Value::Num(1.0),
2725        ))
2726        .expect_err("OverIdx.subsref unsupported payload should fail");
2727        assert_eq!(
2728            err.identifier(),
2729            Some("RunMat:OverIdxSubsrefPayloadUnsupported")
2730        );
2731    }
2732
2733    #[test]
2734    fn overidx_subsasgn_unsupported_payload_errors_with_identifier() {
2735        let err = block_on(overidx_subsasgn(
2736            Value::Object(runmat_builtins::ObjectInstance::new("OverIdx".to_string())),
2737            OBJECT_INDEX_PAREN.to_string(),
2738            Value::Num(1.0),
2739            Value::Num(2.0),
2740        ))
2741        .expect_err("OverIdx.subsasgn unsupported payload should fail");
2742        assert_eq!(
2743            err.identifier(),
2744            Some("RunMat:OverIdxSubsasgnPayloadUnsupported")
2745        );
2746    }
2747
2748    #[test]
2749    fn feval_object_receiver_routes_to_subsref_identifier() {
2750        let err = block_on(feval_builtin(
2751            Value::Object(runmat_builtins::ObjectInstance::new(
2752                "NoSubsrefProtocolClass".to_string(),
2753            )),
2754            vec![Value::Num(1.0)],
2755        ))
2756        .expect_err("feval(object, ...) should route through subsref dispatch");
2757        assert_eq!(err.identifier(), Some("RunMat:MissingSubsref"));
2758    }
2759
2760    #[test]
2761    fn feval_unsupported_callable_value_errors_with_identifier() {
2762        let err = block_on(feval_builtin(Value::Num(1.0), vec![Value::Num(2.0)]))
2763            .expect_err("numeric callable value should fail");
2764        assert_eq!(
2765            err.identifier(),
2766            Some("RunMat:FevalFunctionValueUnsupported")
2767        );
2768    }
2769
2770    #[test]
2771    fn shape_checked_cell_builder_maps_shape_identifier() {
2772        let err = super::build_shape_checked_cell(vec![Value::Num(1.0)], 2, 2, "test")
2773            .expect_err("expected shape mismatch");
2774        assert_eq!(err.identifier(), Some("RunMat:ShapeMismatch"));
2775    }
2776
2777    #[test]
2778    fn feval_accepts_scalar_string_array_handle() {
2779        let handle =
2780            runmat_builtins::StringArray::new(vec!["@sin".to_string()], vec![1, 1]).expect("sa");
2781        let result = block_on(feval_builtin(
2782            Value::StringArray(handle),
2783            vec![Value::Num(0.0)],
2784        ))
2785        .expect("string-array handle feval should succeed");
2786        assert_eq!(result, Value::Num(0.0));
2787    }
2788
2789    #[test]
2790    fn feval_rejects_nonscalar_string_array_handle_with_identifier() {
2791        let handle = runmat_builtins::StringArray::new(
2792            vec!["@sin".to_string(), "@cos".to_string()],
2793            vec![1, 2],
2794        )
2795        .expect("sa");
2796        let err = block_on(feval_builtin(
2797            Value::StringArray(handle),
2798            vec![Value::Num(0.0)],
2799        ))
2800        .expect_err("nonscalar string-array handle should fail");
2801        assert_eq!(err.identifier(), Some("RunMat:FevalHandleShapeInvalid"));
2802    }
2803
2804    #[test]
2805    fn call_feval_async_with_outputs_preserves_unresolved_identifier() {
2806        let err = block_on(super::call_feval_async_with_outputs(
2807            Value::ExternalFunctionHandle("missing.external".to_string()),
2808            &[Value::Num(3.0)],
2809            1,
2810        ))
2811        .expect_err("unresolved external handle should fail");
2812        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
2813    }
2814
2815    #[test]
2816    fn addlistener_rejects_non_object_target_with_identifier() {
2817        let err = block_on(addlistener_builtin(
2818            Value::Num(1.0),
2819            "Changed".to_string(),
2820            Value::FunctionHandle("sin".to_string()),
2821        ))
2822        .expect_err("addlistener should reject non-object target");
2823        assert_eq!(err.identifier(), Some("RunMat:AddListenerTargetInvalid"));
2824    }
2825
2826    #[test]
2827    fn notify_rejects_non_object_target_with_identifier() {
2828        let err = block_on(notify_builtin(
2829            Value::Num(1.0),
2830            "Changed".to_string(),
2831            Vec::new(),
2832        ))
2833        .expect_err("notify should reject non-object target");
2834        assert_eq!(err.identifier(), Some("RunMat:NotifyTargetInvalid"));
2835    }
2836
2837    #[test]
2838    fn addlistener_function_handle_prefers_semantic_identity_when_resolved() {
2839        let _resolver_guard =
2840            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2841                (name == "event_callback").then_some(61)
2842            })));
2843        let target =
2844            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2845        let listener = block_on(addlistener_builtin(
2846            target,
2847            "Changed".to_string(),
2848            Value::FunctionHandle("event_callback".to_string()),
2849        ))
2850        .expect("listener registered");
2851        let Value::Listener(listener) = listener else {
2852            panic!("expected listener value");
2853        };
2854        assert!(matches!(
2855            &*listener.callback,
2856            Value::BoundFunctionHandle { name, function }
2857                if name == "event_callback" && *function == 61
2858        ));
2859    }
2860
2861    #[test]
2862    fn addlistener_external_function_handle_prefers_semantic_identity_when_resolved() {
2863        let _resolver_guard =
2864            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2865                (name == "pkg.event_callback").then_some(62)
2866            })));
2867        let target =
2868            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2869        let listener = block_on(addlistener_builtin(
2870            target,
2871            "Changed".to_string(),
2872            Value::ExternalFunctionHandle("pkg.event_callback".to_string()),
2873        ))
2874        .expect("listener registered");
2875        let Value::Listener(listener) = listener else {
2876            panic!("expected listener value");
2877        };
2878        assert!(matches!(
2879            &*listener.callback,
2880            Value::BoundFunctionHandle { name, function }
2881                if name == "pkg.event_callback" && *function == 62
2882        ));
2883    }
2884
2885    #[test]
2886    fn addlistener_string_handle_prefers_semantic_identity_when_resolved() {
2887        let _resolver_guard =
2888            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2889                (name == "event_callback").then_some(63)
2890            })));
2891        let target =
2892            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2893        let listener = block_on(addlistener_builtin(
2894            target,
2895            "Changed".to_string(),
2896            Value::String("@event_callback".to_string()),
2897        ))
2898        .expect("listener registered");
2899        let Value::Listener(listener) = listener else {
2900            panic!("expected listener value");
2901        };
2902        assert!(matches!(
2903            &*listener.callback,
2904            Value::BoundFunctionHandle { name, function }
2905                if name == "event_callback" && *function == 63
2906        ));
2907    }
2908
2909    #[test]
2910    fn addlistener_char_handle_prefers_semantic_identity_when_resolved() {
2911        let _resolver_guard =
2912            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2913                (name == "event_callback").then_some(64)
2914            })));
2915        let target =
2916            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2917        let listener = block_on(addlistener_builtin(
2918            target,
2919            "Changed".to_string(),
2920            Value::CharArray(runmat_builtins::CharArray::new_row("@event_callback")),
2921        ))
2922        .expect("listener registered");
2923        let Value::Listener(listener) = listener else {
2924            panic!("expected listener value");
2925        };
2926        assert!(matches!(
2927            &*listener.callback,
2928            Value::BoundFunctionHandle { name, function }
2929                if name == "event_callback" && *function == 64
2930        ));
2931    }
2932
2933    #[test]
2934    fn addlistener_string_array_handle_prefers_semantic_identity_when_resolved() {
2935        let _resolver_guard =
2936            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2937                (name == "event_callback").then_some(66)
2938            })));
2939        let target =
2940            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2941        let callback =
2942            runmat_builtins::StringArray::new(vec!["@event_callback".to_string()], vec![1, 1])
2943                .expect("string array");
2944        let listener = block_on(addlistener_builtin(
2945            target,
2946            "Changed".to_string(),
2947            Value::StringArray(callback),
2948        ))
2949        .expect("listener registered");
2950        let Value::Listener(listener) = listener else {
2951            panic!("expected listener value");
2952        };
2953        assert!(matches!(
2954            &*listener.callback,
2955            Value::BoundFunctionHandle { name, function }
2956                if name == "event_callback" && *function == 66
2957        ));
2958    }
2959
2960    #[test]
2961    fn addlistener_closure_prefers_embedded_semantic_identity_when_resolved() {
2962        let _resolver_guard =
2963            crate::user_functions::install_semantic_function_resolver(Some(Arc::new(|name| {
2964                (name == "event_callback").then_some(65)
2965            })));
2966        let target =
2967            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
2968        let callback = Value::Closure(runmat_builtins::Closure {
2969            function_name: "event_callback".to_string(),
2970            bound_function: None,
2971            captures: vec![Value::Num(9.0)],
2972        });
2973        let listener = block_on(addlistener_builtin(target, "Changed".to_string(), callback))
2974            .expect("listener registered");
2975        let Value::Listener(listener) = listener else {
2976            panic!("expected listener value");
2977        };
2978        assert!(matches!(
2979            &*listener.callback,
2980            Value::Closure(runmat_builtins::Closure {
2981                function_name,
2982                bound_function: Some(65),
2983                captures,
2984            }) if function_name == "event_callback" && captures == &vec![Value::Num(9.0)]
2985        ));
2986    }
2987
2988    #[test]
2989    fn notify_semantic_function_handle_uses_semantic_identity() {
2990        let calls = Arc::new(AtomicUsize::new(0));
2991        let seen_calls = Arc::clone(&calls);
2992        let _guard = crate::user_functions::install_semantic_function_invoker(Some(Arc::new(
2993            move |function, args, requested_outputs| {
2994                assert_eq!(function, 44);
2995                assert_eq!(requested_outputs, 0);
2996                assert_eq!(args.len(), 1);
2997                assert!(matches!(args[0], Value::HandleObject(_)));
2998                seen_calls.fetch_add(1, Ordering::SeqCst);
2999                Box::pin(async { Ok(Value::Num(0.0)) })
3000            },
3001        )));
3002        let target =
3003            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
3004        let callback = Value::BoundFunctionHandle {
3005            name: "event_callback".to_string(),
3006            function: 44,
3007        };
3008
3009        block_on(addlistener_builtin(
3010            target.clone(),
3011            "Changed".to_string(),
3012            callback,
3013        ))
3014        .expect("listener registered");
3015        block_on(notify_builtin(target, "Changed".to_string(), Vec::new()))
3016            .expect("notify succeeds");
3017        assert_eq!(calls.load(Ordering::SeqCst), 1);
3018    }
3019
3020    #[test]
3021    fn notify_char_handle_callback_surfaces_unresolved_identifier() {
3022        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
3023        let target =
3024            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
3025        block_on(addlistener_builtin(
3026            target.clone(),
3027            "Changed".to_string(),
3028            Value::CharArray(runmat_builtins::CharArray::new_row(
3029                "@definitely_missing_callback",
3030            )),
3031        ))
3032        .expect("listener registered");
3033        let err = block_on(notify_builtin(target, "Changed".to_string(), Vec::new()))
3034            .expect_err("unresolved char callback should fail");
3035        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
3036    }
3037
3038    #[test]
3039    fn notify_string_array_handle_callback_surfaces_unresolved_identifier() {
3040        let _resolver_guard = crate::user_functions::install_semantic_function_resolver(None);
3041        let target =
3042            block_on(new_handle_object_builtin("EventTarget".to_string())).expect("handle target");
3043        let callback = runmat_builtins::StringArray::new(
3044            vec!["@definitely_missing_callback".to_string()],
3045            vec![1, 1],
3046        )
3047        .expect("string array");
3048        block_on(addlistener_builtin(
3049            target.clone(),
3050            "Changed".to_string(),
3051            Value::StringArray(callback),
3052        ))
3053        .expect("listener registered");
3054        let err = block_on(notify_builtin(target, "Changed".to_string(), Vec::new()))
3055            .expect_err("unresolved string-array callback should fail");
3056        assert_eq!(err.identifier(), Some("RunMat:UndefinedFunction"));
3057    }
3058
3059    #[test]
3060    fn feval_semantic_handle_honors_zero_requested_outputs() {
3061        let _guard = crate::user_functions::install_semantic_function_invoker(Some(Arc::new(
3062            |function, args, requested_outputs| {
3063                assert_eq!(function, 46);
3064                assert_eq!(requested_outputs, 0);
3065                assert_eq!(args, &[Value::Num(5.0)]);
3066                Box::pin(async { Ok(Value::OutputList(Vec::new())) })
3067            },
3068        )));
3069        let _output_guard = crate::output_count::push_output_count(Some(0));
3070        let handle = Value::BoundFunctionHandle {
3071            name: "function_target".to_string(),
3072            function: 46,
3073        };
3074
3075        let result = block_on(feval_builtin(handle, vec![Value::Num(5.0)]))
3076            .expect("semantic function handle feval succeeds");
3077        assert_eq!(result, Value::OutputList(Vec::new()));
3078    }
3079
3080    #[test]
3081    fn feval_semantic_handle_honors_multi_requested_outputs() {
3082        let _guard = crate::user_functions::install_semantic_function_invoker(Some(Arc::new(
3083            |function, args, requested_outputs| {
3084                assert_eq!(function, 47);
3085                assert_eq!(requested_outputs, 2);
3086                assert_eq!(args, &[Value::Num(6.0)]);
3087                Box::pin(async { Ok(Value::OutputList(vec![Value::Num(1.0), Value::Num(2.0)])) })
3088            },
3089        )));
3090        let _output_guard = crate::output_count::push_output_count(Some(2));
3091        let handle = Value::BoundFunctionHandle {
3092            name: "function_target".to_string(),
3093            function: 47,
3094        };
3095
3096        let result = block_on(feval_builtin(handle, vec![Value::Num(6.0)]))
3097            .expect("semantic function handle feval succeeds");
3098        assert_eq!(
3099            result,
3100            Value::OutputList(vec![Value::Num(1.0), Value::Num(2.0)])
3101        );
3102    }
3103
3104    #[test]
3105    fn feval_semantic_closure_errors_when_semantic_invoker_unavailable() {
3106        let _guard = crate::user_functions::install_semantic_function_invoker(None);
3107        let closure = Value::Closure(runmat_builtins::Closure {
3108            function_name: "function_target".to_string(),
3109            bound_function: Some(9044),
3110            captures: vec![Value::Num(1.0)],
3111        });
3112
3113        let err = block_on(feval_builtin(closure, vec![Value::Num(2.0)]))
3114            .expect_err("semantic closure should not fall back to name-based dispatch");
3115        assert_eq!(err.identifier(), Some("RunMat:SemanticFunctionUnavailable"));
3116        assert!(
3117            err.message()
3118                .contains("semantic closure 'function_target' (9044) is unavailable"),
3119            "unexpected error: {err:?}"
3120        );
3121    }
3122}