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