1use std::cell::{Cell, RefCell};
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{
7 atomic::{AtomicBool, Ordering as AtomicOrdering},
8 Arc,
9};
10
11use runmat_builtins::{
12 Access, BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
13 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
14 CharArray, ClassDef, HandleRef, IntValue, LogicalArray, MethodDef, ObjectInstance, PropertyDef,
15 StructValue, Tensor, Value,
16};
17use runmat_gc::{GcHandle, GcRoot, RootId, Trace, Tracer};
18use runmat_macros::runtime_builtin;
19
20use crate::builtins::common::random_args::keyword_of;
21use crate::builtins::common::spec::{
22 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
23 ReductionNaN, ResidencyPolicy, ShapeRequirements,
24};
25use crate::builtins::containers::type_resolvers::{
26 map_cell_type, map_handle_type, map_is_key_type, map_unknown_type,
27};
28use crate::{
29 build_runtime_error, gather_if_needed_async, BuiltinResult, RuntimeError, OBJECT_INDEX_BRACE,
30 OBJECT_INDEX_MEMBER, OBJECT_INDEX_PAREN, OBJECT_SUBSASGN_METHOD, OBJECT_SUBSREF_METHOD,
31};
32
33const CLASS_NAME: &str = "containers.Map";
34const BUILTIN_CONSTRUCTOR: &str = "containers.Map";
35const BUILTIN_KEYS: &str = "containers.Map.keys";
36const BUILTIN_VALUES: &str = "containers.Map.values";
37const BUILTIN_IS_KEY: &str = "containers.Map.isKey";
38const BUILTIN_REMOVE: &str = "containers.Map.remove";
39const BUILTIN_SUBSREF: &str = "containers.Map.subsref";
40const BUILTIN_SUBSASGN: &str = "containers.Map.subsasgn";
41
42const CONTAINERS_MAP_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
43 name: "M",
44 ty: BuiltinParamType::Any,
45 arity: BuiltinParamArity::Required,
46 default: None,
47 description: "containers.Map handle object.",
48}];
49
50const CONTAINERS_MAP_INPUTS_KEYS_VALUES: [BuiltinParamDescriptor; 2] = [
51 BuiltinParamDescriptor {
52 name: "keys",
53 ty: BuiltinParamType::Any,
54 arity: BuiltinParamArity::Required,
55 default: None,
56 description: "Key container (cell, string/char, or numeric vector).",
57 },
58 BuiltinParamDescriptor {
59 name: "values",
60 ty: BuiltinParamType::Any,
61 arity: BuiltinParamArity::Required,
62 default: None,
63 description: "Value container aligned with keys.",
64 },
65];
66
67const CONTAINERS_MAP_INPUTS_KEYS_VALUES_OPTS: [BuiltinParamDescriptor; 3] = [
68 BuiltinParamDescriptor {
69 name: "keys",
70 ty: BuiltinParamType::Any,
71 arity: BuiltinParamArity::Required,
72 default: None,
73 description: "Key container (cell, string/char, or numeric vector).",
74 },
75 BuiltinParamDescriptor {
76 name: "values",
77 ty: BuiltinParamType::Any,
78 arity: BuiltinParamArity::Required,
79 default: None,
80 description: "Value container aligned with keys.",
81 },
82 BuiltinParamDescriptor {
83 name: "options",
84 ty: BuiltinParamType::Any,
85 arity: BuiltinParamArity::Variadic,
86 default: None,
87 description: "Name/value options (KeyType, ValueType, UniformValues, ComparisonMethod).",
88 },
89];
90
91const CONTAINERS_MAP_INPUTS_OPTIONS_ONLY: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
92 name: "options",
93 ty: BuiltinParamType::Any,
94 arity: BuiltinParamArity::Variadic,
95 default: None,
96 description: "Name/value options (KeyType, ValueType, UniformValues, ComparisonMethod).",
97}];
98
99const CONTAINERS_MAP_SIGNATURES: [BuiltinSignatureDescriptor; 4] = [
100 BuiltinSignatureDescriptor {
101 label: "M = containers.Map()",
102 inputs: &[],
103 outputs: &CONTAINERS_MAP_OUTPUT,
104 },
105 BuiltinSignatureDescriptor {
106 label: "M = containers.Map(keys, values)",
107 inputs: &CONTAINERS_MAP_INPUTS_KEYS_VALUES,
108 outputs: &CONTAINERS_MAP_OUTPUT,
109 },
110 BuiltinSignatureDescriptor {
111 label: "M = containers.Map(keys, values, Name, Value, ...)",
112 inputs: &CONTAINERS_MAP_INPUTS_KEYS_VALUES_OPTS,
113 outputs: &CONTAINERS_MAP_OUTPUT,
114 },
115 BuiltinSignatureDescriptor {
116 label: "M = containers.Map(Name, Value, ...)",
117 inputs: &CONTAINERS_MAP_INPUTS_OPTIONS_ONLY,
118 outputs: &CONTAINERS_MAP_OUTPUT,
119 },
120];
121
122const CONTAINERS_MAP_METHOD_INPUT_MAP: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
123 name: "M",
124 ty: BuiltinParamType::Any,
125 arity: BuiltinParamArity::Required,
126 default: None,
127 description: "containers.Map handle object.",
128}];
129
130const CONTAINERS_MAP_KEYS_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
131 name: "K",
132 ty: BuiltinParamType::Any,
133 arity: BuiltinParamArity::Required,
134 default: None,
135 description: "Row cell array containing map keys.",
136}];
137
138const CONTAINERS_MAP_VALUES_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
139 name: "V",
140 ty: BuiltinParamType::Any,
141 arity: BuiltinParamArity::Required,
142 default: None,
143 description: "Row cell array containing map values.",
144}];
145
146const CONTAINERS_MAP_INPUTS_KEY_SPEC: [BuiltinParamDescriptor; 2] = [
147 BuiltinParamDescriptor {
148 name: "M",
149 ty: BuiltinParamType::Any,
150 arity: BuiltinParamArity::Required,
151 default: None,
152 description: "containers.Map handle object.",
153 },
154 BuiltinParamDescriptor {
155 name: "keySet",
156 ty: BuiltinParamType::Any,
157 arity: BuiltinParamArity::Required,
158 default: None,
159 description: "Key scalar or key collection to query/mutate.",
160 },
161];
162
163const CONTAINERS_MAP_ISKEY_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
164 name: "tf",
165 ty: BuiltinParamType::LogicalArray,
166 arity: BuiltinParamArity::Required,
167 default: None,
168 description: "Logical membership result for each key.",
169}];
170
171const CONTAINERS_MAP_INPUTS_SUBSREF: [BuiltinParamDescriptor; 3] = [
172 BuiltinParamDescriptor {
173 name: "M",
174 ty: BuiltinParamType::Any,
175 arity: BuiltinParamArity::Required,
176 default: None,
177 description: "containers.Map handle object.",
178 },
179 BuiltinParamDescriptor {
180 name: "kind",
181 ty: BuiltinParamType::StringScalar,
182 arity: BuiltinParamArity::Required,
183 default: None,
184 description: "Indexing kind: (), ., or {}.",
185 },
186 BuiltinParamDescriptor {
187 name: "payload",
188 ty: BuiltinParamType::Any,
189 arity: BuiltinParamArity::Required,
190 default: None,
191 description: "Indexing payload cell/property argument.",
192 },
193];
194
195const CONTAINERS_MAP_INPUTS_SUBSASGN: [BuiltinParamDescriptor; 4] = [
196 BuiltinParamDescriptor {
197 name: "M",
198 ty: BuiltinParamType::Any,
199 arity: BuiltinParamArity::Required,
200 default: None,
201 description: "containers.Map handle object.",
202 },
203 BuiltinParamDescriptor {
204 name: "kind",
205 ty: BuiltinParamType::StringScalar,
206 arity: BuiltinParamArity::Required,
207 default: None,
208 description: "Assignment kind: (), ., or {}.",
209 },
210 BuiltinParamDescriptor {
211 name: "payload",
212 ty: BuiltinParamType::Any,
213 arity: BuiltinParamArity::Required,
214 default: None,
215 description: "Assignment payload cell/property argument.",
216 },
217 BuiltinParamDescriptor {
218 name: "rhs",
219 ty: BuiltinParamType::Any,
220 arity: BuiltinParamArity::Required,
221 default: None,
222 description: "Assigned value (scalar or collection).",
223 },
224];
225
226const CONTAINERS_MAP_SUBSREF_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
227 name: "value",
228 ty: BuiltinParamType::Any,
229 arity: BuiltinParamArity::Required,
230 default: None,
231 description: "Lookup/property value result.",
232}];
233
234const CONTAINERS_MAP_KEYS_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
235 [BuiltinSignatureDescriptor {
236 label: "K = containers.Map.keys(M)",
237 inputs: &CONTAINERS_MAP_METHOD_INPUT_MAP,
238 outputs: &CONTAINERS_MAP_KEYS_OUTPUT,
239 }];
240
241const CONTAINERS_MAP_VALUES_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
242 [BuiltinSignatureDescriptor {
243 label: "V = containers.Map.values(M)",
244 inputs: &CONTAINERS_MAP_METHOD_INPUT_MAP,
245 outputs: &CONTAINERS_MAP_VALUES_OUTPUT,
246 }];
247
248const CONTAINERS_MAP_ISKEY_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
249 [BuiltinSignatureDescriptor {
250 label: "tf = containers.Map.isKey(M, keySet)",
251 inputs: &CONTAINERS_MAP_INPUTS_KEY_SPEC,
252 outputs: &CONTAINERS_MAP_ISKEY_OUTPUT,
253 }];
254
255const CONTAINERS_MAP_REMOVE_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
256 [BuiltinSignatureDescriptor {
257 label: "M = containers.Map.remove(M, keySet)",
258 inputs: &CONTAINERS_MAP_INPUTS_KEY_SPEC,
259 outputs: &CONTAINERS_MAP_OUTPUT,
260 }];
261
262const CONTAINERS_MAP_SUBSREF_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
263 [BuiltinSignatureDescriptor {
264 label: "value = containers.Map.subsref(M, kind, payload)",
265 inputs: &CONTAINERS_MAP_INPUTS_SUBSREF,
266 outputs: &CONTAINERS_MAP_SUBSREF_OUTPUT,
267 }];
268
269const CONTAINERS_MAP_SUBSASGN_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
270 [BuiltinSignatureDescriptor {
271 label: "M = containers.Map.subsasgn(M, kind, payload, rhs)",
272 inputs: &CONTAINERS_MAP_INPUTS_SUBSASGN,
273 outputs: &CONTAINERS_MAP_OUTPUT,
274 }];
275
276const CONTAINERS_MAP_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
277 code: "RM.CONTAINERS_MAP.INVALID_ARGUMENT",
278 identifier: Some("RunMat:containers.Map:InvalidArgument"),
279 when: "Map constructor/method inputs, option grammar, or key/value payloads are invalid.",
280 message: "containers.Map: invalid argument",
281};
282
283const CONTAINERS_MAP_ERROR_MISSING_KEY: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
284 code: "RM.CONTAINERS_MAP.MISSING_KEY",
285 identifier: Some("RunMat:containers.Map:MissingKey"),
286 when: "Lookup/removal targets a key that is not present in the map.",
287 message: "containers.Map: The specified key is not present in this container.",
288};
289
290const CONTAINERS_MAP_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
291 code: "RM.CONTAINERS_MAP.INTERNAL",
292 identifier: Some("RunMat:containers.Map:Internal"),
293 when: "Map registry/storage operations fail unexpectedly.",
294 message: "containers.Map: internal operation failed",
295};
296
297const CONTAINERS_MAP_ERRORS: [BuiltinErrorDescriptor; 3] = [
298 CONTAINERS_MAP_ERROR_INVALID_ARGUMENT,
299 CONTAINERS_MAP_ERROR_MISSING_KEY,
300 CONTAINERS_MAP_ERROR_INTERNAL,
301];
302
303pub const CONTAINERS_MAP_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
304 signatures: &CONTAINERS_MAP_SIGNATURES,
305 output_mode: BuiltinOutputMode::Fixed,
306 completion_policy: BuiltinCompletionPolicy::Public,
307 errors: &CONTAINERS_MAP_ERRORS,
308};
309
310pub const CONTAINERS_MAP_KEYS_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
311 signatures: &CONTAINERS_MAP_KEYS_SIGNATURES,
312 output_mode: BuiltinOutputMode::Fixed,
313 completion_policy: BuiltinCompletionPolicy::Public,
314 errors: &CONTAINERS_MAP_ERRORS,
315};
316
317pub const CONTAINERS_MAP_VALUES_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
318 signatures: &CONTAINERS_MAP_VALUES_SIGNATURES,
319 output_mode: BuiltinOutputMode::Fixed,
320 completion_policy: BuiltinCompletionPolicy::Public,
321 errors: &CONTAINERS_MAP_ERRORS,
322};
323
324pub const CONTAINERS_MAP_ISKEY_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
325 signatures: &CONTAINERS_MAP_ISKEY_SIGNATURES,
326 output_mode: BuiltinOutputMode::Fixed,
327 completion_policy: BuiltinCompletionPolicy::Public,
328 errors: &CONTAINERS_MAP_ERRORS,
329};
330
331pub const CONTAINERS_MAP_REMOVE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
332 signatures: &CONTAINERS_MAP_REMOVE_SIGNATURES,
333 output_mode: BuiltinOutputMode::Fixed,
334 completion_policy: BuiltinCompletionPolicy::Public,
335 errors: &CONTAINERS_MAP_ERRORS,
336};
337
338pub const CONTAINERS_MAP_SUBSREF_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
339 signatures: &CONTAINERS_MAP_SUBSREF_SIGNATURES,
340 output_mode: BuiltinOutputMode::Fixed,
341 completion_policy: BuiltinCompletionPolicy::Public,
342 errors: &CONTAINERS_MAP_ERRORS,
343};
344
345pub const CONTAINERS_MAP_SUBSASGN_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
346 signatures: &CONTAINERS_MAP_SUBSASGN_SIGNATURES,
347 output_mode: BuiltinOutputMode::Fixed,
348 completion_policy: BuiltinCompletionPolicy::Public,
349 errors: &CONTAINERS_MAP_ERRORS,
350};
351
352#[runmat_macros::register_gpu_spec(
353 builtin_path = "crate::builtins::containers::map::containers_map"
354)]
355pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
356 name: "containers.Map",
357 op_kind: GpuOpKind::Custom("map"),
358 supported_precisions: &[],
359 broadcast: BroadcastSemantics::None,
360 provider_hooks: &[],
361 constant_strategy: ConstantStrategy::InlineLiteral,
362 residency: ResidencyPolicy::GatherImmediately,
363 nan_mode: ReductionNaN::Include,
364 two_pass_threshold: None,
365 workgroup_size: None,
366 accepts_nan_mode: false,
367 notes: "Map storage is host-resident; GPU inputs are gathered only when split into multiple entries.",
368};
369
370fn map_error_with_detail(
371 error: &'static BuiltinErrorDescriptor,
372 detail: impl AsRef<str>,
373 builtin: &'static str,
374) -> RuntimeError {
375 let raw = detail.as_ref().trim();
376 let normalized = raw
377 .strip_prefix("containers.Map:")
378 .map(str::trim)
379 .unwrap_or(raw);
380 let message = if normalized.is_empty() {
381 error.message.to_string()
382 } else {
383 format!("{}: {}", error.message, normalized)
384 };
385 let mut builder = build_runtime_error(message).with_builtin(builtin);
386 if let Some(identifier) = error.identifier {
387 builder = builder.with_identifier(identifier);
388 }
389 builder.build()
390}
391
392fn map_descriptor_error(
393 error: &'static BuiltinErrorDescriptor,
394 builtin: &'static str,
395) -> RuntimeError {
396 map_error_with_detail(error, "", builtin)
397}
398
399fn map_invalid(detail: impl AsRef<str>, builtin: &'static str) -> RuntimeError {
400 map_error_with_detail(&CONTAINERS_MAP_ERROR_INVALID_ARGUMENT, detail, builtin)
401}
402
403fn map_internal(detail: impl AsRef<str>, builtin: &'static str) -> RuntimeError {
404 map_error_with_detail(&CONTAINERS_MAP_ERROR_INTERNAL, detail, builtin)
405}
406
407fn map_error(message: impl Into<String>, builtin: &'static str) -> RuntimeError {
408 map_invalid(message.into(), builtin)
409}
410
411fn attach_builtin_context(mut error: RuntimeError, builtin: &'static str) -> RuntimeError {
412 if error.context.builtin.is_none() {
413 error.context = error.context.with_builtin(builtin);
414 }
415 error
416}
417
418#[runmat_macros::register_fusion_spec(
419 builtin_path = "crate::builtins::containers::map::containers_map"
420)]
421pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
422 name: "containers.Map",
423 shape: ShapeRequirements::Any,
424 constant_strategy: ConstantStrategy::InlineLiteral,
425 elementwise: None,
426 reduction: None,
427 emits_nan: false,
428 notes: "Handles act as fusion sinks; map construction terminates GPU fusion plans.",
429};
430
431static NEXT_ID: AtomicU64 = AtomicU64::new(1);
432
433thread_local! {
434 static MAP_REGISTRY: RefCell<HashMap<u64, MapStore>> = RefCell::new(HashMap::new());
435 static CONTAINERS_MAP_CLASS_REGISTERED: Cell<bool> = const { Cell::new(false) };
436 static MAP_ROOT_STATE: RefCell<Option<MapRootState>> = const { RefCell::new(None) };
437}
438
439struct MapRootState {
440 root_id: RootId,
441 active: Arc<AtomicBool>,
442}
443
444struct MapRegistryRoot {
445 active: Arc<AtomicBool>,
446}
447
448impl GcRoot for MapRegistryRoot {
449 fn scan(&self) -> Vec<GcHandle> {
450 struct RootCollector {
451 roots: Vec<GcHandle>,
452 }
453
454 impl Tracer for RootCollector {
455 fn mark(&mut self, handle: GcHandle) {
456 self.roots.push(handle);
457 }
458 }
459
460 MAP_REGISTRY.with(|registry| {
461 let registry = registry.borrow();
462 let mut collector = RootCollector { roots: Vec::new() };
463 for store in registry.values() {
464 if let Some(storage) = store.storage {
465 collector.mark(storage);
466 }
467 for entry in &store.entries {
468 entry.key_value.trace(&mut collector);
469 entry.value.trace(&mut collector);
470 }
471 }
472 collector.roots
473 })
474 }
475
476 fn description(&self) -> String {
477 "containers.Map registry values".to_string()
478 }
479
480 fn is_active(&self) -> bool {
481 self.active.load(AtomicOrdering::Acquire)
482 && MAP_REGISTRY.with(|registry| !registry.borrow().is_empty())
483 }
484}
485
486fn ensure_map_registry_root_registered(builtin: &'static str) -> BuiltinResult<()> {
487 MAP_ROOT_STATE.with(|state| {
488 if state
489 .borrow()
490 .as_ref()
491 .is_some_and(|state| state.active.load(AtomicOrdering::Acquire))
492 {
493 return Ok(());
494 }
495
496 let active = Arc::new(AtomicBool::new(true));
497 let root_id = runmat_gc::gc_register_root(Box::new(MapRegistryRoot {
498 active: Arc::clone(&active),
499 }))
500 .map_err(|e| {
501 map_internal(
502 format!("containers.Map: failed to register GC root: {e}"),
503 builtin,
504 )
505 })?;
506 *state.borrow_mut() = Some(MapRootState { root_id, active });
507 Ok(())
508 })
509}
510
511fn deactivate_map_registry_root_if_empty() {
512 let empty = MAP_REGISTRY.with(|registry| {
513 registry
514 .try_borrow()
515 .map(|registry| registry.is_empty())
516 .unwrap_or(false)
517 });
518 if empty {
519 MAP_ROOT_STATE.with(|state| {
520 if let Some(state) = state.borrow_mut().take() {
521 state.active.store(false, AtomicOrdering::Release);
522 if let Err(err) = runmat_gc::gc_unregister_root(state.root_id) {
523 log::warn!("containers.Map: failed to unregister empty registry root: {err}");
524 }
525 }
526 });
527 }
528}
529
530fn ensure_containers_map_class_registered() {
531 CONTAINERS_MAP_CLASS_REGISTERED.with(|registered| {
532 if registered.get() {
533 return;
534 }
535 let mut properties = HashMap::new();
536 for name in ["Count", "KeyType", "ValueType"] {
537 properties.insert(
538 name.to_string(),
539 PropertyDef {
540 name: name.to_string(),
541 is_static: false,
542 is_constant: false,
543 is_dependent: true,
544 get_access: Access::Public,
545 set_access: Access::Private,
546 default_value: None,
547 },
548 );
549 }
550
551 let mut methods = HashMap::new();
552 for (name, function_name) in [
553 ("keys", BUILTIN_KEYS),
554 ("values", BUILTIN_VALUES),
555 ("isKey", BUILTIN_IS_KEY),
556 ("remove", BUILTIN_REMOVE),
557 (OBJECT_SUBSREF_METHOD, BUILTIN_SUBSREF),
558 (OBJECT_SUBSASGN_METHOD, BUILTIN_SUBSASGN),
559 ] {
560 methods.insert(
561 name.to_string(),
562 MethodDef {
563 name: name.to_string(),
564 is_static: false,
565 is_abstract: false,
566 is_sealed: false,
567 access: Access::Public,
568 function_name: function_name.to_string(),
569 implicit_class_argument: None,
570 },
571 );
572 }
573
574 runmat_builtins::register_class(ClassDef {
575 name: CLASS_NAME.to_string(),
576 parent: None,
577 properties,
578 methods,
579 });
580 registered.set(true);
581 });
582}
583
584#[derive(Clone, Copy, Debug, PartialEq, Eq)]
585enum KeyType {
586 Char,
587 String,
588 Double,
589 Single,
590 Int32,
591 UInt32,
592 Int64,
593 UInt64,
594 Logical,
595}
596
597impl KeyType {
598 fn matlab_name(self) -> &'static str {
599 match self {
600 KeyType::Char => "char",
601 KeyType::String => "string",
602 KeyType::Double => "double",
603 KeyType::Single => "single",
604 KeyType::Int32 => "int32",
605 KeyType::UInt32 => "uint32",
606 KeyType::Int64 => "int64",
607 KeyType::UInt64 => "uint64",
608 KeyType::Logical => "logical",
609 }
610 }
611
612 fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
613 let text = string_from_value(value, "containers.Map: expected a KeyType string", builtin)?;
614 match text.to_ascii_lowercase().as_str() {
615 "char" | "character" => Ok(KeyType::Char),
616 "string" => Ok(KeyType::String),
617 "double" => Ok(KeyType::Double),
618 "single" => Ok(KeyType::Single),
619 "int32" => Ok(KeyType::Int32),
620 "uint32" => Ok(KeyType::UInt32),
621 "int64" => Ok(KeyType::Int64),
622 "uint64" => Ok(KeyType::UInt64),
623 "logical" => Ok(KeyType::Logical),
624 other => Err(map_error(
625 format!(
626 "containers.Map: unsupported KeyType '{other}'. Valid types: char, string, double, int32, uint32, int64, uint64, logical."
627 ),
628 builtin,
629 )),
630 }
631 }
632}
633
634#[derive(Clone, Copy, Debug, PartialEq, Eq)]
635enum ValueType {
636 Any,
637 Char,
638 String,
639 Double,
640 Single,
641 Logical,
642}
643
644impl ValueType {
645 fn matlab_name(self) -> &'static str {
646 match self {
647 ValueType::Any => "any",
648 ValueType::Char => "char",
649 ValueType::String => "string",
650 ValueType::Double => "double",
651 ValueType::Single => "single",
652 ValueType::Logical => "logical",
653 }
654 }
655
656 fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
657 let text = string_from_value(
658 value,
659 "containers.Map: expected a ValueType string",
660 builtin,
661 )?;
662 match text.to_ascii_lowercase().as_str() {
663 "any" => Ok(ValueType::Any),
664 "char" | "character" => Ok(ValueType::Char),
665 "string" => Ok(ValueType::String),
666 "double" => Ok(ValueType::Double),
667 "single" => Ok(ValueType::Single),
668 "logical" => Ok(ValueType::Logical),
669 other => Err(map_error(
670 format!(
671 "containers.Map: unsupported ValueType '{other}'. Valid types: any, char, string, double, single, logical."
672 ),
673 builtin,
674 )),
675 }
676 }
677
678 fn normalize(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
679 match self {
680 ValueType::Any => Ok(value),
681 ValueType::Char => {
682 let chars = char_array_from_value(&value, builtin)?;
683 Ok(Value::CharArray(chars))
684 }
685 ValueType::String => {
686 let text = string_from_value(
687 &value,
688 "containers.Map: values must be string scalars",
689 builtin,
690 )?;
691 Ok(Value::String(text))
692 }
693 ValueType::Double | ValueType::Single => normalize_numeric_value(value, builtin),
694 ValueType::Logical => normalize_logical_value(value, builtin),
695 }
696 }
697}
698
699#[derive(Clone, PartialEq, Eq, Hash)]
700enum NormalizedKey {
701 String(String),
702 Float(u64),
703 Int(i64),
704 UInt(u64),
705 Bool(bool),
706}
707
708#[derive(Clone)]
709struct MapEntry {
710 normalized: NormalizedKey,
711 key_value: Value,
712 value: Value,
713}
714
715struct MapStore {
716 storage: Option<GcHandle>,
717 key_type: KeyType,
718 value_type: ValueType,
719 uniform_values: bool,
720 uniform_class: Option<ValueClass>,
721 entries: Vec<MapEntry>,
722 index: HashMap<NormalizedKey, usize>,
723}
724
725impl MapStore {
726 fn new(key_type: KeyType, value_type: ValueType, uniform_values: bool) -> Self {
727 Self {
728 storage: None,
729 key_type,
730 value_type,
731 uniform_values,
732 uniform_class: None,
733 entries: Vec::new(),
734 index: HashMap::new(),
735 }
736 }
737
738 fn len(&self) -> usize {
739 self.entries.len()
740 }
741
742 fn contains(&self, key: &NormalizedKey) -> bool {
743 self.index.contains_key(key)
744 }
745
746 fn get(&self, key: &NormalizedKey) -> Option<Value> {
747 self.index
748 .get(key)
749 .map(|&idx| self.entries[idx].value.clone())
750 }
751
752 fn insert_new(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
753 if self.index.contains_key(&entry.normalized) {
754 return Err(map_error(
755 "containers.Map: Duplicate key name was provided.",
756 builtin,
757 ));
758 }
759 entry.value = self.normalize_value(entry.value, builtin)?;
760 self.track_uniform_class(&entry.value, builtin)?;
761 let idx = self.entries.len();
762 self.entries.push(entry.clone());
763 self.index.insert(entry.normalized, idx);
764 Ok(())
765 }
766
767 fn set(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
768 entry.value = self.normalize_value(entry.value, builtin)?;
769 self.track_uniform_class(&entry.value, builtin)?;
770 if let Some(&idx) = self.index.get(&entry.normalized) {
771 self.entries[idx].value = entry.value.clone();
772 self.entries[idx].key_value = entry.key_value;
773 } else {
774 let idx = self.entries.len();
775 self.entries.push(entry.clone());
776 self.index.insert(entry.normalized, idx);
777 }
778 Ok(())
779 }
780
781 fn remove(&mut self, key: &NormalizedKey, builtin: &'static str) -> BuiltinResult<()> {
782 let idx = match self.index.get(key) {
783 Some(&idx) => idx,
784 None => {
785 return Err(map_descriptor_error(
786 &CONTAINERS_MAP_ERROR_MISSING_KEY,
787 builtin,
788 ));
789 }
790 };
791 self.entries.remove(idx);
792 self.index.clear();
793 for (pos, entry) in self.entries.iter().enumerate() {
794 self.index.insert(entry.normalized.clone(), pos);
795 }
796 if self.entries.is_empty() {
797 self.uniform_class = None;
798 }
799 Ok(())
800 }
801
802 fn keys(&self) -> Vec<Value> {
803 self.entries
804 .iter()
805 .map(|entry| entry.key_value.clone())
806 .collect()
807 }
808
809 fn values(&self) -> Vec<Value> {
810 self.entries
811 .iter()
812 .map(|entry| entry.value.clone())
813 .collect()
814 }
815
816 fn normalize_value(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
817 self.value_type.normalize(value, builtin)
818 }
819
820 fn track_uniform_class(&mut self, value: &Value, builtin: &'static str) -> BuiltinResult<()> {
821 if !self.uniform_values {
822 return Ok(());
823 }
824 let class = ValueClass::from_value(value);
825 if let Some(existing) = &self.uniform_class {
826 if existing != &class {
827 return Err(map_error(
828 "containers.Map: UniformValues=true requires all values to share the same MATLAB class.",
829 builtin,
830 ));
831 }
832 } else {
833 self.uniform_class = Some(class);
834 }
835 Ok(())
836 }
837}
838
839#[derive(Clone, Debug, PartialEq, Eq)]
840enum ValueClass {
841 Char,
842 String,
843 Double,
844 Logical,
845 Int,
846 UInt,
847 Cell,
848 Struct,
849 Object,
850 Other(&'static str),
851}
852
853impl ValueClass {
854 fn from_value(value: &Value) -> Self {
855 match value {
856 Value::CharArray(_) => ValueClass::Char,
857 Value::String(_) | Value::StringArray(_) => ValueClass::String,
858 Value::Num(_) | Value::Tensor(_) | Value::ComplexTensor(_) => ValueClass::Double,
859 Value::Bool(_) | Value::LogicalArray(_) => ValueClass::Logical,
860 Value::Int(i) => match i {
861 IntValue::I8(_) | IntValue::I16(_) | IntValue::I32(_) | IntValue::I64(_) => {
862 ValueClass::Int
863 }
864 IntValue::U8(_) | IntValue::U16(_) | IntValue::U32(_) | IntValue::U64(_) => {
865 ValueClass::UInt
866 }
867 },
868 Value::Cell(_) => ValueClass::Cell,
869 Value::Struct(_) => ValueClass::Struct,
870 Value::Object(_) | Value::HandleObject(_) | Value::Listener(_) => ValueClass::Object,
871 _ => ValueClass::Other("other"),
872 }
873 }
874}
875
876struct ConstructorArgs {
877 key_type: KeyType,
878 value_type: ValueType,
879 uniform_values: bool,
880 keys: Vec<KeyCandidate>,
881 values: Vec<Value>,
882}
883
884struct KeyCandidate {
885 normalized: NormalizedKey,
886 canonical: Value,
887}
888
889#[runtime_builtin(
890 name = "containers.Map",
891 category = "containers/map",
892 summary = "Create key-value dictionary objects.",
893 keywords = "map,containers.Map,dictionary,hash map,lookup",
894 accel = "metadata",
895 sink = true,
896 type_resolver(map_handle_type),
897 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_DESCRIPTOR),
898 builtin_path = "crate::builtins::containers::map::containers_map"
899)]
900async fn containers_map_builtin(args: Vec<Value>) -> crate::BuiltinResult<Value> {
901 let parsed = parse_constructor_args(args, BUILTIN_CONSTRUCTOR).await?;
902 let store = build_store(parsed, BUILTIN_CONSTRUCTOR)?;
903 allocate_handle(store, BUILTIN_CONSTRUCTOR)
904}
905
906#[runtime_builtin(
907 name = "containers.Map.keys",
908 type_resolver(map_cell_type),
909 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_KEYS_DESCRIPTOR),
910 builtin_path = "crate::builtins::containers::map::containers_map"
911)]
912async fn containers_map_keys(map: Value) -> crate::BuiltinResult<Value> {
913 with_store(&map, BUILTIN_KEYS, |store| {
914 let values = store.keys();
915 make_row_cell(values, BUILTIN_KEYS)
916 })
917}
918
919#[runtime_builtin(
920 name = "containers.Map.values",
921 type_resolver(map_cell_type),
922 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_VALUES_DESCRIPTOR),
923 builtin_path = "crate::builtins::containers::map::containers_map"
924)]
925async fn containers_map_values(map: Value) -> crate::BuiltinResult<Value> {
926 with_store(&map, BUILTIN_VALUES, |store| {
927 let values = store.values();
928 make_row_cell(values, BUILTIN_VALUES)
929 })
930}
931
932#[runtime_builtin(
933 name = "containers.Map.isKey",
934 type_resolver(map_is_key_type),
935 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_ISKEY_DESCRIPTOR),
936 builtin_path = "crate::builtins::containers::map::containers_map"
937)]
938async fn containers_map_is_key(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
939 let key_type = with_store(&map, BUILTIN_IS_KEY, |store| Ok(store.key_type))?;
940 let collection = collect_key_spec(&key_spec, key_type, BUILTIN_IS_KEY).await?;
941 with_store(&map, BUILTIN_IS_KEY, |store| {
942 let mut flags = Vec::with_capacity(collection.values.len());
943 for value in &collection.values {
944 let normalized = normalize_key(value, store.key_type, BUILTIN_IS_KEY)?;
945 flags.push(store.contains(&normalized));
946 }
947 if collection.values.len() == 1 {
948 Ok(Value::Bool(flags[0]))
949 } else {
950 let data: Vec<u8> = flags.into_iter().map(|b| if b { 1 } else { 0 }).collect();
951 let logical = LogicalArray::new(data, collection.shape)
952 .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_IS_KEY))?;
953 Ok(Value::LogicalArray(logical))
954 }
955 })
956}
957
958#[runtime_builtin(
959 name = "containers.Map.remove",
960 type_resolver(map_handle_type),
961 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_REMOVE_DESCRIPTOR),
962 builtin_path = "crate::builtins::containers::map::containers_map"
963)]
964async fn containers_map_remove(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
965 let key_type = with_store(&map, BUILTIN_REMOVE, |store| Ok(store.key_type))?;
966 let collection = collect_key_spec(&key_spec, key_type, BUILTIN_REMOVE).await?;
967 with_store_mut(&map, BUILTIN_REMOVE, |store| {
968 for value in &collection.values {
969 let normalized = normalize_key(value, store.key_type, BUILTIN_REMOVE)?;
970 store.remove(&normalized, BUILTIN_REMOVE)?;
971 }
972 Ok(())
973 })?;
974 Ok(map)
975}
976
977#[runtime_builtin(
978 name = "containers.Map.subsref",
979 type_resolver(map_unknown_type),
980 descriptor(
981 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSREF_DESCRIPTOR
982 ),
983 builtin_path = "crate::builtins::containers::map::containers_map"
984)]
985async fn containers_map_subsref(
986 map: Value,
987 kind: String,
988 payload: Value,
989) -> crate::BuiltinResult<Value> {
990 if !matches!(map, Value::HandleObject(_)) {
991 return Err(map_error(
992 format!("containers.Map: subsref expects a containers.Map handle, got {map:?}"),
993 BUILTIN_SUBSREF,
994 ));
995 }
996 match kind.as_str() {
997 OBJECT_INDEX_PAREN => {
998 let mut args = extract_key_arguments(&payload, BUILTIN_SUBSREF)?;
999 if args.is_empty() {
1000 return Err(map_error(
1001 "containers.Map: indexing requires at least one key",
1002 BUILTIN_SUBSREF,
1003 ));
1004 }
1005 if args.len() != 1 {
1006 return Err(map_error(
1007 "containers.Map: indexing expects a single key argument",
1008 BUILTIN_SUBSREF,
1009 ));
1010 }
1011 let key_arg = args.remove(0);
1012 let key_type = with_store(&map, BUILTIN_SUBSREF, |store| Ok(store.key_type))?;
1013 let collection = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSREF).await?;
1014 with_store(&map, BUILTIN_SUBSREF, |store| {
1015 if collection.values.is_empty() {
1016 return crate::make_cell_with_shape(Vec::new(), collection.shape.clone())
1017 .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_SUBSREF));
1018 }
1019 if collection.values.len() == 1 {
1020 let normalized =
1021 normalize_key(&collection.values[0], store.key_type, BUILTIN_SUBSREF)?;
1022 store.get(&normalized).ok_or_else(|| {
1023 map_descriptor_error(&CONTAINERS_MAP_ERROR_MISSING_KEY, BUILTIN_SUBSREF)
1024 })
1025 } else {
1026 let mut results = Vec::with_capacity(collection.values.len());
1027 for value in &collection.values {
1028 let normalized = normalize_key(value, store.key_type, BUILTIN_SUBSREF)?;
1029 let stored = store.get(&normalized).ok_or_else(|| {
1030 map_descriptor_error(&CONTAINERS_MAP_ERROR_MISSING_KEY, BUILTIN_SUBSREF)
1031 })?;
1032 results.push(stored);
1033 }
1034 crate::make_cell_with_shape(results, collection.shape.clone())
1035 .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_SUBSREF))
1036 }
1037 })
1038 }
1039 OBJECT_INDEX_MEMBER => {
1040 let field = string_from_value(
1041 &payload,
1042 "containers.Map: property name must be text",
1043 BUILTIN_SUBSREF,
1044 )?;
1045 with_store(&map, BUILTIN_SUBSREF, |store| {
1046 match field.to_ascii_lowercase().as_str() {
1047 "count" => Ok(Value::Num(store.len() as f64)),
1048 "keytype" => char_array_value(store.key_type.matlab_name(), BUILTIN_SUBSREF),
1049 "valuetype" => {
1050 char_array_value(store.value_type.matlab_name(), BUILTIN_SUBSREF)
1051 }
1052 other => Err(map_error(
1053 format!("containers.Map: no such property '{other}'"),
1054 BUILTIN_SUBSREF,
1055 )),
1056 }
1057 })
1058 }
1059 OBJECT_INDEX_BRACE => Err(map_error(
1060 "containers.Map: curly-brace indexing is not supported.",
1061 BUILTIN_SUBSREF,
1062 )),
1063 other => Err(map_error(
1064 format!("containers.Map: unsupported indexing kind '{other}'"),
1065 BUILTIN_SUBSREF,
1066 )),
1067 }
1068}
1069
1070#[runtime_builtin(
1071 name = "containers.Map.subsasgn",
1072 type_resolver(map_handle_type),
1073 descriptor(
1074 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSASGN_DESCRIPTOR
1075 ),
1076 builtin_path = "crate::builtins::containers::map::containers_map"
1077)]
1078async fn containers_map_subsasgn(
1079 map: Value,
1080 kind: String,
1081 payload: Value,
1082 rhs: Value,
1083) -> crate::BuiltinResult<Value> {
1084 if !matches!(map, Value::HandleObject(_)) {
1085 return Err(map_error(
1086 format!("containers.Map: subsasgn expects a containers.Map handle, got {map:?}"),
1087 BUILTIN_SUBSASGN,
1088 ));
1089 }
1090 match kind.as_str() {
1091 OBJECT_INDEX_PAREN => {
1092 let mut args = extract_key_arguments(&payload, BUILTIN_SUBSASGN)?;
1093 if args.is_empty() {
1094 return Err(map_error(
1095 "containers.Map: assignment requires at least one key",
1096 BUILTIN_SUBSASGN,
1097 ));
1098 }
1099 if args.len() != 1 {
1100 return Err(map_error(
1101 "containers.Map: assignment expects a single key argument",
1102 BUILTIN_SUBSASGN,
1103 ));
1104 }
1105 let key_arg = args.remove(0);
1106 let key_type = with_store(&map, BUILTIN_SUBSASGN, |store| Ok(store.key_type))?;
1107 let KeyCollection {
1108 values: key_values, ..
1109 } = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSASGN).await?;
1110 let values =
1111 expand_assignment_values(rhs.clone(), key_values.len(), BUILTIN_SUBSASGN).await?;
1112 with_store_mut(&map, BUILTIN_SUBSASGN, move |store| {
1113 for (key_raw, value) in key_values.into_iter().zip(values.into_iter()) {
1114 let (normalized, canonical) =
1115 canonicalize_key(key_raw, store.key_type, BUILTIN_SUBSASGN)?;
1116 let entry = MapEntry {
1117 normalized,
1118 key_value: canonical,
1119 value,
1120 };
1121 store.set(entry, BUILTIN_SUBSASGN)?;
1122 }
1123 Ok(())
1124 })?;
1125 Ok(map)
1126 }
1127 OBJECT_INDEX_MEMBER => Err(map_error(
1128 "containers.Map: property assignments are not supported.",
1129 BUILTIN_SUBSASGN,
1130 )),
1131 OBJECT_INDEX_BRACE => Err(map_error(
1132 "containers.Map: curly-brace assignment is not supported.",
1133 BUILTIN_SUBSASGN,
1134 )),
1135 other => Err(map_error(
1136 format!("containers.Map: unsupported assignment kind '{other}'"),
1137 BUILTIN_SUBSASGN,
1138 )),
1139 }
1140}
1141
1142async fn parse_constructor_args(
1143 args: Vec<Value>,
1144 builtin: &'static str,
1145) -> BuiltinResult<ConstructorArgs> {
1146 let mut index = 0usize;
1147 let mut keys_input: Option<Value> = None;
1148 let mut values_input: Option<Value> = None;
1149
1150 if index < args.len() && keyword_of(&args[index]).is_none() {
1151 if args.len() < 2 {
1152 return Err(map_error(
1153 "containers.Map: constructor requires both keys and values when either is provided.",
1154 builtin,
1155 ));
1156 }
1157 keys_input = Some(args[index].clone());
1158 values_input = Some(args[index + 1].clone());
1159 index += 2;
1160 }
1161
1162 let mut key_type = KeyType::Char;
1163 let mut value_type = ValueType::Any;
1164 let mut uniform_values = false;
1165 while index < args.len() {
1166 let keyword = keyword_of(&args[index]).ok_or_else(|| {
1167 map_error(
1168 "containers.Map: expected option name (e.g. 'KeyType')",
1169 builtin,
1170 )
1171 })?;
1172 index += 1;
1173 let Some(value) = args.get(index) else {
1174 return Err(map_error(
1175 format!("containers.Map: missing value for option '{keyword}'"),
1176 builtin,
1177 ));
1178 };
1179 index += 1;
1180 match keyword.as_str() {
1181 "keytype" => key_type = KeyType::parse(value, builtin)?,
1182 "valuetype" => value_type = ValueType::parse(value, builtin)?,
1183 "uniformvalues" => {
1184 uniform_values = bool_from_value(
1185 value,
1186 "containers.Map: UniformValues must be logical",
1187 builtin,
1188 )?
1189 }
1190 "comparisonmethod" => {
1191 let text = string_from_value(
1192 value,
1193 "containers.Map: ComparisonMethod must be a string",
1194 builtin,
1195 )?;
1196 let lowered = text.to_ascii_lowercase();
1197 if lowered != "strcmp" {
1198 return Err(map_error(
1199 "containers.Map: only ComparisonMethod='strcmp' is supported.",
1200 builtin,
1201 ));
1202 }
1203 }
1204 other => {
1205 return Err(map_error(
1206 format!("containers.Map: unrecognised option '{other}'"),
1207 builtin,
1208 ));
1209 }
1210 }
1211 }
1212
1213 let keys = match keys_input {
1214 Some(value) => prepare_keys(value, key_type, builtin).await?,
1215 None => Vec::new(),
1216 };
1217
1218 let values = match values_input {
1219 Some(value) => prepare_values(value, builtin).await?,
1220 None => Vec::new(),
1221 };
1222
1223 if keys.len() != values.len() {
1224 return Err(map_error(
1225 format!(
1226 "containers.Map: number of keys ({}) must match number of values ({})",
1227 keys.len(),
1228 values.len()
1229 ),
1230 builtin,
1231 ));
1232 }
1233
1234 Ok(ConstructorArgs {
1235 key_type,
1236 value_type,
1237 uniform_values,
1238 keys,
1239 values,
1240 })
1241}
1242
1243fn build_store(args: ConstructorArgs, builtin: &'static str) -> BuiltinResult<MapStore> {
1244 let mut store = MapStore::new(args.key_type, args.value_type, args.uniform_values);
1245 for (candidate, value) in args.keys.into_iter().zip(args.values.into_iter()) {
1246 store.insert_new(
1247 MapEntry {
1248 normalized: candidate.normalized,
1249 key_value: candidate.canonical,
1250 value,
1251 },
1252 builtin,
1253 )?;
1254 }
1255 Ok(store)
1256}
1257
1258fn allocate_handle(store: MapStore, builtin: &'static str) -> BuiltinResult<Value> {
1259 ensure_containers_map_class_registered();
1260
1261 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
1262 ensure_map_registry_root_registered(builtin)?;
1263 MAP_REGISTRY.with(|registry| {
1264 registry
1265 .try_borrow_mut()
1266 .map_err(|_| map_internal("containers.Map: registry is already borrowed", builtin))?
1267 .insert(id, store);
1268 Ok::<(), RuntimeError>(())
1269 })?;
1270 let mut storage = ObjectInstance::new(CLASS_NAME.to_string());
1271 storage
1272 .properties
1273 .insert("id".to_string(), Value::Int(IntValue::U64(id)));
1274 let gc = match runmat_gc::gc_allocate(Value::Object(storage)) {
1275 Ok(gc) => gc,
1276 Err(e) => {
1277 MAP_REGISTRY.with(|registry| {
1278 if let Ok(mut registry) = registry.try_borrow_mut() {
1279 registry.remove(&id);
1280 }
1281 });
1282 deactivate_map_registry_root_if_empty();
1283 return Err(map_error(format!("containers.Map: {e}"), builtin));
1284 }
1285 };
1286 MAP_REGISTRY.with(|registry| {
1287 let mut registry = registry
1288 .try_borrow_mut()
1289 .map_err(|_| map_internal("containers.Map: registry is already borrowed", builtin))?;
1290 let store = registry
1291 .get_mut(&id)
1292 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1293 store.storage = Some(gc);
1294 Ok::<(), RuntimeError>(())
1295 })?;
1296 Ok(Value::HandleObject(HandleRef {
1297 class_name: CLASS_NAME.to_string(),
1298 target: gc,
1299 valid: true,
1300 }))
1301}
1302
1303fn with_store<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
1304where
1305 F: FnOnce(&MapStore) -> BuiltinResult<R>,
1306{
1307 let handle = extract_handle(map, builtin)?;
1308 ensure_handle(handle, builtin)?;
1309 let id = map_id(handle, builtin)?;
1310 MAP_REGISTRY.with(|registry| {
1311 let registry = registry
1312 .try_borrow()
1313 .map_err(|_| map_internal("containers.Map: registry already borrowed", builtin))?;
1314 let store = registry
1315 .get(&id)
1316 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1317 f(store)
1318 })
1319}
1320
1321fn with_store_mut<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
1322where
1323 F: FnOnce(&mut MapStore) -> BuiltinResult<R>,
1324{
1325 let handle = extract_handle(map, builtin)?;
1326 ensure_handle(handle, builtin)?;
1327 let id = map_id(handle, builtin)?;
1328 MAP_REGISTRY.with(|registry| {
1329 let mut registry = registry
1330 .try_borrow_mut()
1331 .map_err(|_| map_internal("containers.Map: registry already borrowed", builtin))?;
1332 let store = registry
1333 .get_mut(&id)
1334 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1335 f(store)
1336 })
1337}
1338
1339fn extract_handle<'a>(value: &'a Value, builtin: &'static str) -> BuiltinResult<&'a HandleRef> {
1340 match value {
1341 Value::HandleObject(handle) => Ok(handle),
1342 _ => Err(map_error(
1343 "containers.Map: expected a containers.Map handle",
1344 builtin,
1345 )),
1346 }
1347}
1348
1349fn ensure_handle(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<()> {
1350 if !crate::is_handle_valid(handle) {
1351 return Err(map_error("containers.Map: handle is invalid", builtin));
1352 }
1353 if handle.class_name != CLASS_NAME {
1354 return Err(map_error(
1355 format!(
1356 "containers.Map: expected handle of class '{}', got '{}'",
1357 CLASS_NAME, handle.class_name
1358 ),
1359 builtin,
1360 ));
1361 }
1362 Ok(())
1363}
1364
1365fn map_id(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<u64> {
1366 let storage = runmat_gc::gc_clone_value(&handle.target).map_err(|e| {
1367 map_internal(
1368 format!("containers.Map: invalid handle storage: {e}"),
1369 builtin,
1370 )
1371 })?;
1372 let id_value = match &storage {
1373 Value::Object(object) if object.class_name == CLASS_NAME => object.properties.get("id"),
1374 Value::Struct(StructValue { fields }) => fields.get("id"),
1375 other => {
1376 return Err(map_internal(
1377 format!("containers.Map: internal storage has unexpected shape {other:?}"),
1378 builtin,
1379 ));
1380 }
1381 };
1382 match id_value {
1383 Some(Value::Int(IntValue::U64(id))) => Ok(*id),
1384 Some(Value::Int(other)) => {
1385 let id = other.to_i64();
1386 if id < 0 {
1387 Err(map_internal(
1388 "containers.Map: negative map identifier",
1389 builtin,
1390 ))
1391 } else {
1392 Ok(id as u64)
1393 }
1394 }
1395 Some(Value::Num(n)) if n.is_finite() && *n >= 0.0 && n.fract() == 0.0 => {
1396 if *n >= u64::MAX as f64 {
1397 Err(map_internal(
1398 "containers.Map: map identifier out of range",
1399 builtin,
1400 ))
1401 } else {
1402 Ok(*n as u64)
1403 }
1404 }
1405 _ => Err(map_internal(
1406 "containers.Map: corrupted storage identifier",
1407 builtin,
1408 )),
1409 }
1410}
1411
1412async fn prepare_keys(
1413 value: Value,
1414 key_type: KeyType,
1415 builtin: &'static str,
1416) -> BuiltinResult<Vec<KeyCandidate>> {
1417 let host = gather_if_needed_async(&value)
1418 .await
1419 .map_err(|err| attach_builtin_context(err, builtin))?;
1420 let flattened = flatten_keys(&host, key_type, builtin).await?;
1421 let mut out = Vec::with_capacity(flattened.len());
1422 for raw_key in flattened {
1423 let (normalized, canonical) = canonicalize_key(raw_key, key_type, builtin)?;
1424 out.push(KeyCandidate {
1425 normalized,
1426 canonical,
1427 });
1428 }
1429 Ok(out)
1430}
1431
1432async fn prepare_values(value: Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1433 let host = gather_if_needed_async(&value)
1434 .await
1435 .map_err(|err| attach_builtin_context(err, builtin))?;
1436 flatten_values(&host, builtin).await
1437}
1438
1439async fn flatten_keys(
1440 value: &Value,
1441 key_type: KeyType,
1442 builtin: &'static str,
1443) -> BuiltinResult<Vec<Value>> {
1444 match value {
1445 Value::Cell(cell) => {
1446 let mut out = Vec::with_capacity(cell.data.len());
1447 for ptr in &cell.data {
1448 let element = ptr;
1449 if matches!(element, Value::Cell(_)) {
1450 return Err(map_error(
1451 "containers.Map: nested cell arrays are not supported for keys",
1452 builtin,
1453 ));
1454 }
1455 out.push(
1456 gather_if_needed_async(element)
1457 .await
1458 .map_err(|err| attach_builtin_context(err, builtin))?,
1459 );
1460 }
1461 Ok(out)
1462 }
1463 Value::StringArray(sa) => Ok(sa
1464 .data
1465 .iter()
1466 .map(|text| Value::String(text.clone()))
1467 .collect()),
1468 Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
1469 Value::LogicalArray(arr) => {
1470 if key_type != KeyType::Logical {
1471 return Err(map_error(
1472 "containers.Map: logical arrays can only be used with KeyType='logical'",
1473 builtin,
1474 ));
1475 }
1476 Ok(arr.data.iter().map(|&b| Value::Bool(b != 0)).collect())
1477 }
1478 Value::Tensor(t) => {
1479 if !t.shape.is_empty() && t.data.len() != 1 && !is_vector_shape(&t.shape) {
1480 return Err(map_error(
1481 "containers.Map: numeric keys must be scalar or vector shaped",
1482 builtin,
1483 ));
1484 }
1485 Ok(t.data.iter().map(|&v| Value::Num(v)).collect())
1486 }
1487 Value::Num(_) | Value::Int(_) | Value::Bool(_) | Value::String(_) => {
1488 Ok(vec![value.clone()])
1489 }
1490 Value::GpuTensor(_) => Err(map_error(
1491 "containers.Map: GPU keys must be gathered to the host before construction",
1492 builtin,
1493 )),
1494 other => Err(map_error(
1495 format!("containers.Map: unsupported key container {other:?}"),
1496 builtin,
1497 )),
1498 }
1499}
1500
1501async fn flatten_values(value: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1502 match value {
1503 Value::Cell(cell) => {
1504 let mut out = Vec::with_capacity(cell.data.len());
1505 for ptr in &cell.data {
1506 out.push(
1507 gather_if_needed_async(ptr)
1508 .await
1509 .map_err(|err| attach_builtin_context(err, builtin))?,
1510 );
1511 }
1512 Ok(out)
1513 }
1514 Value::StringArray(sa) => Ok(sa
1515 .data
1516 .iter()
1517 .map(|text| Value::String(text.clone()))
1518 .collect()),
1519 Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
1520 Value::LogicalArray(arr) => Ok(arr.data.iter().map(|&b| Value::Bool(b != 0)).collect()),
1521 Value::Tensor(t) => {
1522 if !t.shape.is_empty() && !is_vector_shape(&t.shape) && t.data.len() != 1 {
1523 return Err(map_error(
1524 "containers.Map: numeric values must be scalar or vector shaped",
1525 builtin,
1526 ));
1527 }
1528 Ok(t.data.iter().map(|&v| Value::Num(v)).collect())
1529 }
1530 _ => Ok(vec![value.clone()]),
1531 }
1532}
1533
1534fn char_array_rows(ca: &CharArray, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1535 if ca.rows == 0 {
1536 return Ok(Vec::new());
1537 }
1538 let mut out = Vec::with_capacity(ca.rows);
1539 for row in 0..ca.rows {
1540 let mut text = String::with_capacity(ca.cols);
1541 for col in 0..ca.cols {
1542 text.push(ca.data[row * ca.cols + col]);
1543 }
1544 let chars: Vec<char> = text.chars().collect();
1545 let array = CharArray::new(chars.clone(), 1, chars.len())
1546 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1547 out.push(Value::CharArray(array));
1548 }
1549 Ok(out)
1550}
1551
1552fn is_vector_shape(shape: &[usize]) -> bool {
1553 match shape.len() {
1554 0 => true,
1555 1 => true,
1556 2 => shape[0] == 1 || shape[1] == 1,
1557 _ => false,
1558 }
1559}
1560
1561fn canonicalize_key(
1562 value: Value,
1563 key_type: KeyType,
1564 builtin: &'static str,
1565) -> BuiltinResult<(NormalizedKey, Value)> {
1566 let normalized = normalize_key(&value, key_type, builtin)?;
1567 let canonical = match key_type {
1568 KeyType::Char => Value::CharArray(char_array_from_value(&value, builtin)?),
1569 KeyType::String => Value::String(string_from_value(
1570 &value,
1571 "containers.Map: keys must be string scalars",
1572 builtin,
1573 )?),
1574 KeyType::Double => Value::Num(numeric_from_value(
1575 &value,
1576 "containers.Map: keys must be numeric scalars",
1577 builtin,
1578 )?),
1579 KeyType::Single => Value::Num(numeric_from_value(
1580 &value,
1581 "containers.Map: keys must be numeric scalars",
1582 builtin,
1583 )?),
1584 KeyType::Int32 => Value::Int(IntValue::I32(integer_from_value(
1585 &value,
1586 i32::MIN as i64,
1587 i32::MAX as i64,
1588 "containers.Map: int32 keys must be integers",
1589 builtin,
1590 )? as i32)),
1591 KeyType::UInt32 => Value::Int(IntValue::U32(unsigned_from_value(
1592 &value,
1593 u32::MAX as u64,
1594 "containers.Map: uint32 keys must be unsigned integers",
1595 builtin,
1596 )? as u32)),
1597 KeyType::Int64 => Value::Int(IntValue::I64(integer_from_value(
1598 &value,
1599 i64::MIN,
1600 i64::MAX,
1601 "containers.Map: int64 keys must be integers",
1602 builtin,
1603 )?)),
1604 KeyType::UInt64 => Value::Int(IntValue::U64(unsigned_from_value(
1605 &value,
1606 u64::MAX,
1607 "containers.Map: uint64 keys must be unsigned integers",
1608 builtin,
1609 )?)),
1610 KeyType::Logical => Value::Bool(bool_from_value(
1611 &value,
1612 "containers.Map: logical keys must be logical scalars",
1613 builtin,
1614 )?),
1615 };
1616 Ok((normalized, canonical))
1617}
1618
1619fn normalize_key(
1620 value: &Value,
1621 key_type: KeyType,
1622 builtin: &'static str,
1623) -> BuiltinResult<NormalizedKey> {
1624 match key_type {
1625 KeyType::Char | KeyType::String => {
1626 let text =
1627 string_from_value(value, "containers.Map: keys must be text scalars", builtin)?;
1628 Ok(NormalizedKey::String(text))
1629 }
1630 KeyType::Double | KeyType::Single => {
1631 let numeric = numeric_from_value(
1632 value,
1633 "containers.Map: keys must be numeric scalars",
1634 builtin,
1635 )?;
1636 if !numeric.is_finite() {
1637 return Err(map_error(
1638 "containers.Map: keys must be finite numeric scalars",
1639 builtin,
1640 ));
1641 }
1642 let canonical = if numeric == 0.0 { 0.0 } else { numeric };
1643 Ok(NormalizedKey::Float(canonical.to_bits()))
1644 }
1645 KeyType::Int32 | KeyType::Int64 => {
1646 let bounds = if key_type == KeyType::Int32 {
1647 (i32::MIN as i64, i32::MAX as i64)
1648 } else {
1649 (i64::MIN, i64::MAX)
1650 };
1651 let value = integer_from_value(
1652 value,
1653 bounds.0,
1654 bounds.1,
1655 "containers.Map: integer keys must be whole numbers",
1656 builtin,
1657 )?;
1658 Ok(NormalizedKey::Int(value))
1659 }
1660 KeyType::UInt32 | KeyType::UInt64 => {
1661 let limit = if key_type == KeyType::UInt32 {
1662 u32::MAX as u64
1663 } else {
1664 u64::MAX
1665 };
1666 let value = unsigned_from_value(
1667 value,
1668 limit,
1669 "containers.Map: unsigned keys must be non-negative integers",
1670 builtin,
1671 )?;
1672 Ok(NormalizedKey::UInt(value))
1673 }
1674 KeyType::Logical => {
1675 let flag = bool_from_value(
1676 value,
1677 "containers.Map: logical keys must be logical scalars",
1678 builtin,
1679 )?;
1680 Ok(NormalizedKey::Bool(flag))
1681 }
1682 }
1683}
1684
1685fn string_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<String> {
1686 match value {
1687 Value::String(s) => Ok(s.clone()),
1688 Value::StringArray(sa) if sa.data.len() == 1 => Ok(sa.data[0].clone()),
1689 Value::CharArray(ca) if ca.rows == 1 => Ok(ca.data.iter().collect()),
1690 _ => Err(map_error(context, builtin)),
1691 }
1692}
1693
1694fn char_array_from_value(value: &Value, builtin: &'static str) -> BuiltinResult<CharArray> {
1695 match value {
1696 Value::CharArray(ca) if ca.rows == 1 => Ok(ca.clone()),
1697 Value::String(s) => {
1698 let chars: Vec<char> = s.chars().collect();
1699 CharArray::new(chars.clone(), 1, chars.len())
1700 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1701 }
1702 Value::StringArray(sa) if sa.data.len() == 1 => {
1703 let chars: Vec<char> = sa.data[0].chars().collect();
1704 CharArray::new(chars.clone(), 1, chars.len())
1705 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1706 }
1707 _ => Err(map_error(
1708 "containers.Map: keys must be character vectors",
1709 builtin,
1710 )),
1711 }
1712}
1713
1714fn char_array_value(text: &str, builtin: &'static str) -> BuiltinResult<Value> {
1715 let chars: Vec<char> = text.chars().collect();
1716 CharArray::new(chars.clone(), 1, chars.len())
1717 .map(Value::CharArray)
1718 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1719}
1720
1721fn normalize_numeric_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1722 match value {
1723 Value::Num(_) | Value::Tensor(_) => Ok(value),
1724 Value::Int(i) => Ok(Value::Num(i.to_f64())),
1725 Value::Bool(b) => Ok(Value::Num(if b { 1.0 } else { 0.0 })),
1726 Value::LogicalArray(arr) => {
1727 let data: Vec<f64> = arr
1728 .data
1729 .iter()
1730 .map(|&b| if b != 0 { 1.0 } else { 0.0 })
1731 .collect();
1732 let tensor = Tensor::new(data, arr.shape.clone())
1733 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1734 Ok(Value::Tensor(tensor))
1735 }
1736 Value::Cell(_)
1737 | Value::SparseTensor(_)
1738 | Value::Struct(_)
1739 | Value::Object(_)
1740 | Value::HandleObject(_)
1741 | Value::Listener(_)
1742 | Value::String(_)
1743 | Value::StringArray(_)
1744 | Value::CharArray(_)
1745 | Value::Complex(_, _)
1746 | Value::ComplexTensor(_)
1747 | Value::Symbolic(_)
1748 | Value::FunctionHandle(_)
1749 | Value::ExternalFunctionHandle(_)
1750 | Value::MethodFunctionHandle(_)
1751 | Value::BoundFunctionHandle { .. }
1752 | Value::Closure(_)
1753 | Value::ClassRef(_)
1754 | Value::MException(_)
1755 | Value::GpuTensor(_)
1756 | Value::OutputList(_) => Err(map_error(
1757 "containers.Map: values must be numeric when ValueType is 'double' or 'single'",
1758 builtin,
1759 )),
1760 }
1761}
1762
1763fn normalize_logical_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1764 match value {
1765 Value::Bool(_) | Value::LogicalArray(_) => Ok(value),
1766 Value::Int(i) => Ok(Value::Bool(i.to_i64() != 0)),
1767 Value::Num(n) => Ok(Value::Bool(n != 0.0)),
1768 Value::Tensor(t) => {
1769 let flags: Vec<u8> = t
1770 .data
1771 .iter()
1772 .map(|&v| if v != 0.0 { 1 } else { 0 })
1773 .collect();
1774 let logical = LogicalArray::new(flags, t.shape.clone())
1775 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1776 Ok(Value::LogicalArray(logical))
1777 }
1778 Value::CharArray(_)
1779 | Value::SparseTensor(_)
1780 | Value::String(_)
1781 | Value::StringArray(_)
1782 | Value::Struct(_)
1783 | Value::Cell(_)
1784 | Value::Object(_)
1785 | Value::HandleObject(_)
1786 | Value::Listener(_)
1787 | Value::Complex(_, _)
1788 | Value::ComplexTensor(_)
1789 | Value::Symbolic(_)
1790 | Value::FunctionHandle(_)
1791 | Value::ExternalFunctionHandle(_)
1792 | Value::MethodFunctionHandle(_)
1793 | Value::BoundFunctionHandle { .. }
1794 | Value::Closure(_)
1795 | Value::ClassRef(_)
1796 | Value::MException(_)
1797 | Value::GpuTensor(_)
1798 | Value::OutputList(_) => Err(map_error(
1799 "containers.Map: values must be logical when ValueType is 'logical'",
1800 builtin,
1801 )),
1802 }
1803}
1804
1805fn numeric_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<f64> {
1806 match value {
1807 Value::Num(n) => Ok(*n),
1808 Value::Int(i) => Ok(i.to_f64()),
1809 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
1810 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
1811 Value::LogicalArray(arr) if arr.data.len() == 1 => {
1812 Ok(if arr.data[0] != 0 { 1.0 } else { 0.0 })
1813 }
1814 _ => Err(map_error(context, builtin)),
1815 }
1816}
1817
1818fn integer_from_value(
1819 value: &Value,
1820 min: i64,
1821 max: i64,
1822 context: &str,
1823 builtin: &'static str,
1824) -> BuiltinResult<i64> {
1825 match value {
1826 Value::Int(i) => {
1827 let v = i.to_i64();
1828 if v < min || v > max {
1829 return Err(map_error(context, builtin));
1830 }
1831 Ok(v)
1832 }
1833 Value::Num(n) => {
1834 if !n.is_finite() {
1835 return Err(map_error(context, builtin));
1836 }
1837 if (*n < min as f64) || (*n > max as f64) {
1838 return Err(map_error(context, builtin));
1839 }
1840 if (n.round() - n).abs() > f64::EPSILON {
1841 return Err(map_error(context, builtin));
1842 }
1843 Ok(n.round() as i64)
1844 }
1845 Value::Bool(b) => {
1846 let v = if *b { 1 } else { 0 };
1847 if v < min || v > max {
1848 return Err(map_error(context, builtin));
1849 }
1850 Ok(v)
1851 }
1852 _ => Err(map_error(context, builtin)),
1853 }
1854}
1855
1856fn unsigned_from_value(
1857 value: &Value,
1858 max: u64,
1859 context: &str,
1860 builtin: &'static str,
1861) -> BuiltinResult<u64> {
1862 match value {
1863 Value::Int(i) => {
1864 let v = i.to_i64();
1865 if v < 0 || v as u64 > max {
1866 return Err(map_error(context, builtin));
1867 }
1868 Ok(v as u64)
1869 }
1870 Value::Num(n) => {
1871 if !n.is_finite() || *n < 0.0 || *n > max as f64 {
1872 return Err(map_error(context, builtin));
1873 }
1874 if (n.round() - n).abs() > f64::EPSILON {
1875 return Err(map_error(context, builtin));
1876 }
1877 Ok(n.round() as u64)
1878 }
1879 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
1880 _ => Err(map_error(context, builtin)),
1881 }
1882}
1883
1884fn bool_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<bool> {
1885 match value {
1886 Value::Bool(b) => Ok(*b),
1887 Value::LogicalArray(arr) if arr.data.len() == 1 => Ok(arr.data[0] != 0),
1888 Value::Int(i) => Ok(i.to_i64() != 0),
1889 Value::Num(n) => Ok(*n != 0.0),
1890 _ => Err(map_error(context, builtin)),
1891 }
1892}
1893
1894fn make_row_cell(values: Vec<Value>, builtin: &'static str) -> BuiltinResult<Value> {
1895 let cols = values.len();
1896 crate::make_cell_with_shape(values, vec![1, cols])
1897 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1898}
1899
1900fn extract_key_arguments(payload: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1901 match payload {
1902 Value::Cell(cell) => {
1903 let mut out = Vec::with_capacity(cell.data.len());
1904 for ptr in &cell.data {
1905 out.push(ptr.clone());
1906 }
1907 Ok(out)
1908 }
1909 other => Err(map_error(
1910 format!("containers.Map: expected key arguments in a cell array, got {other:?}"),
1911 builtin,
1912 )),
1913 }
1914}
1915
1916async fn expand_assignment_values(
1917 value: Value,
1918 expected: usize,
1919 builtin: &'static str,
1920) -> BuiltinResult<Vec<Value>> {
1921 let host = gather_if_needed_async(&value)
1922 .await
1923 .map_err(|err| attach_builtin_context(err, builtin))?;
1924 let values = flatten_values(&host, builtin).await?;
1925 if expected == 1 {
1926 if values.is_empty() {
1927 return Err(map_error(
1928 "containers.Map: assignment requires a value",
1929 builtin,
1930 ));
1931 }
1932 Ok(vec![values.into_iter().next().unwrap()])
1933 } else {
1934 if values.len() != expected {
1935 return Err(map_error(
1936 format!(
1937 "containers.Map: assignment with {} keys requires {} values (got {})",
1938 expected,
1939 expected,
1940 values.len()
1941 ),
1942 builtin,
1943 ));
1944 }
1945 Ok(values)
1946 }
1947}
1948
1949struct KeyCollection {
1950 values: Vec<Value>,
1951 shape: Vec<usize>,
1952}
1953
1954async fn collect_key_spec(
1955 value: &Value,
1956 key_type: KeyType,
1957 builtin: &'static str,
1958) -> BuiltinResult<KeyCollection> {
1959 let host = gather_if_needed_async(value)
1960 .await
1961 .map_err(|err| attach_builtin_context(err, builtin))?;
1962 match &host {
1963 Value::Cell(cell) => {
1964 let mut values = Vec::with_capacity(cell.data.len());
1965 for ptr in &cell.data {
1966 values.push(
1967 gather_if_needed_async(ptr)
1968 .await
1969 .map_err(|err| attach_builtin_context(err, builtin))?,
1970 );
1971 }
1972 Ok(KeyCollection {
1973 values,
1974 shape: vec![cell.rows, cell.cols],
1975 })
1976 }
1977 Value::StringArray(sa) => Ok(KeyCollection {
1978 values: sa.data.iter().map(|s| Value::String(s.clone())).collect(),
1979 shape: vec![sa.rows(), sa.cols()],
1980 }),
1981 Value::CharArray(ca) => {
1982 let rows = if ca.rows == 0 { 0 } else { ca.rows };
1983 Ok(KeyCollection {
1984 values: char_array_rows(ca, builtin)?,
1985 shape: vec![rows, 1],
1986 })
1987 }
1988 Value::LogicalArray(arr) if key_type == KeyType::Logical => Ok(KeyCollection {
1989 values: arr.data.iter().map(|&b| Value::Bool(b != 0)).collect(),
1990 shape: arr.shape.clone(),
1991 }),
1992 Value::Tensor(t) if key_type != KeyType::Char && key_type != KeyType::String => {
1993 Ok(KeyCollection {
1994 values: t.data.iter().map(|&n| Value::Num(n)).collect(),
1995 shape: t.shape.clone(),
1996 })
1997 }
1998 _ => Ok(KeyCollection {
1999 values: vec![host.clone()],
2000 shape: vec![1, 1],
2001 }),
2002 }
2003}
2004
2005pub fn map_length(value: &Value) -> Option<usize> {
2006 if let Value::HandleObject(handle) = value {
2007 if crate::is_handle_valid(handle) && handle.class_name == CLASS_NAME {
2008 if let Ok(id) = map_id(handle, BUILTIN_CONSTRUCTOR) {
2009 return MAP_REGISTRY.with(|registry| {
2010 registry
2011 .try_borrow()
2012 .ok()
2013 .and_then(|registry| registry.get(&id).map(|store| store.len()))
2014 });
2015 }
2016 }
2017 }
2018 None
2019}
2020
2021#[cfg(test)]
2022pub(crate) mod tests {
2023 use super::*;
2024 use crate::builtins::common::test_support;
2025 use futures::executor::block_on;
2026 use runmat_builtins::{ResolveContext, Type};
2027
2028 fn error_message(err: crate::RuntimeError) -> String {
2029 err.message.clone()
2030 }
2031
2032 fn containers_map_builtin(args: Vec<Value>) -> BuiltinResult<Value> {
2033 block_on(super::containers_map_builtin(args))
2034 }
2035
2036 fn containers_map_keys(map: Value) -> BuiltinResult<Value> {
2037 block_on(super::containers_map_keys(map))
2038 }
2039
2040 fn containers_map_is_key(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2041 block_on(super::containers_map_is_key(map, key_spec))
2042 }
2043
2044 fn containers_map_remove(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2045 block_on(super::containers_map_remove(map, key_spec))
2046 }
2047
2048 fn containers_map_subsref(map: Value, kind: String, payload: Value) -> BuiltinResult<Value> {
2049 block_on(super::containers_map_subsref(map, kind, payload))
2050 }
2051
2052 fn containers_map_subsasgn(
2053 map: Value,
2054 kind: String,
2055 payload: Value,
2056 rhs: Value,
2057 ) -> BuiltinResult<Value> {
2058 block_on(super::containers_map_subsasgn(map, kind, payload, rhs))
2059 }
2060
2061 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2062 #[test]
2063 fn construct_empty_map_defaults() {
2064 let map = containers_map_builtin(Vec::new()).expect("map");
2065 let count = containers_map_subsref(
2066 map.clone(),
2067 ".".to_string(),
2068 Value::from("Count".to_string()),
2069 )
2070 .expect("Count");
2071 assert_eq!(count, Value::Num(0.0));
2072
2073 let key_type = containers_map_subsref(
2074 map.clone(),
2075 ".".to_string(),
2076 Value::from("KeyType".to_string()),
2077 )
2078 .expect("KeyType");
2079 assert_eq!(
2080 key_type,
2081 Value::CharArray(CharArray::new("char".chars().collect(), 1, 4).unwrap())
2082 );
2083
2084 let value_type = containers_map_subsref(
2085 map.clone(),
2086 ".".to_string(),
2087 Value::from("ValueType".to_string()),
2088 )
2089 .expect("ValueType");
2090 assert_eq!(
2091 value_type,
2092 Value::CharArray(CharArray::new("any".chars().collect(), 1, 3).unwrap())
2093 );
2094 }
2095
2096 #[test]
2097 fn map_type_resolvers_basics() {
2098 let ctx = ResolveContext::new(Vec::new());
2099 assert_eq!(map_handle_type(&[Type::Unknown], &ctx), Type::Unknown);
2100 assert_eq!(map_cell_type(&[], &ctx), Type::cell());
2101 assert_eq!(map_is_key_type(&[Type::String], &ctx), Type::logical());
2102 assert_eq!(map_unknown_type(&[], &ctx), Type::Unknown);
2103 }
2104
2105 #[test]
2106 fn containers_map_descriptor_includes_constructor_and_method_signatures() {
2107 let constructor_labels: Vec<&str> = CONTAINERS_MAP_DESCRIPTOR
2108 .signatures
2109 .iter()
2110 .map(|sig| sig.label)
2111 .collect();
2112 assert!(constructor_labels.contains(&"M = containers.Map()"));
2113 assert!(constructor_labels.contains(&"M = containers.Map(keys, values)"));
2114
2115 let method_labels: Vec<&str> = CONTAINERS_MAP_SUBSREF_DESCRIPTOR
2116 .signatures
2117 .iter()
2118 .map(|sig| sig.label)
2119 .collect();
2120 assert!(method_labels.contains(&"value = containers.Map.subsref(M, kind, payload)"));
2121 }
2122
2123 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2124 #[test]
2125 fn constructor_with_cells_lookup() {
2126 let keys = crate::make_cell(vec![Value::from("apple"), Value::from("pear")], 1, 2).unwrap();
2127 let values = crate::make_cell(vec![Value::Num(5.0), Value::Num(7.0)], 1, 2).unwrap();
2128 let map = containers_map_builtin(vec![keys, values]).expect("map");
2129 let apple = containers_map_subsref(
2130 map.clone(),
2131 "()".to_string(),
2132 crate::make_cell(vec![Value::from("apple")], 1, 1).unwrap(),
2133 )
2134 .expect("lookup");
2135 assert_eq!(apple, Value::Num(5.0));
2136 }
2137
2138 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2139 #[test]
2140 fn constructor_rejects_duplicate_keys() {
2141 let keys = crate::make_cell(vec![Value::from("dup"), Value::from("dup")], 1, 2).unwrap();
2142 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2143 let err = containers_map_builtin(vec![keys, values]).expect_err("duplicate check");
2144 let message = error_message(err);
2145 assert!(message.contains("Duplicate key name"));
2146 }
2147
2148 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2149 #[test]
2150 fn constructor_errors_when_value_count_mismatch() {
2151 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2152 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2153 let err = containers_map_builtin(vec![keys, values]).expect_err("count mismatch");
2154 let message = error_message(err);
2155 assert!(message.contains("number of keys"));
2156 }
2157
2158 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2159 #[test]
2160 fn comparison_method_rejects_unknown_values() {
2161 let keys = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
2162 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2163 let err = containers_map_builtin(vec![
2164 keys,
2165 values,
2166 Value::from("ComparisonMethod"),
2167 Value::from("caseinsensitive"),
2168 ])
2169 .expect_err("comparison method");
2170 let message = error_message(err);
2171 assert!(message.contains("ComparisonMethod"));
2172 }
2173
2174 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2175 #[test]
2176 fn key_type_single_roundtrip() {
2177 let map = containers_map_builtin(vec![Value::from("KeyType"), Value::from("single")])
2178 .expect("map");
2179 let key_type = containers_map_subsref(map.clone(), ".".to_string(), Value::from("KeyType"))
2180 .expect("keytype");
2181 assert_eq!(
2182 key_type,
2183 Value::CharArray(CharArray::new("single".chars().collect(), 1, 6).unwrap())
2184 );
2185
2186 let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2187 let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(7.0))
2188 .expect("assign");
2189 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2190 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
2191 }
2192
2193 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2194 #[test]
2195 fn value_type_double_converts_integers() {
2196 let keys = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2197 let values = crate::make_cell(vec![Value::Int(IntValue::I32(7))], 1, 1).unwrap();
2198 let map = containers_map_builtin(vec![
2199 keys,
2200 values,
2201 Value::from("KeyType"),
2202 Value::from("double"),
2203 Value::from("ValueType"),
2204 Value::from("double"),
2205 ])
2206 .expect("map");
2207 let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2208 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2209 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
2210 }
2211
2212 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2213 #[test]
2214 fn value_type_logical_converts_numeric_arrays() {
2215 let keys = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
2216 let tensor = Tensor::new(vec![0.0, 2.0, -3.0], vec![3, 1]).unwrap();
2217 let values = crate::make_cell(vec![Value::Tensor(tensor.clone())], 1, 1).unwrap();
2218 let map = containers_map_builtin(vec![
2219 keys,
2220 values,
2221 Value::from("ValueType"),
2222 Value::from("logical"),
2223 ])
2224 .expect("map");
2225 let payload = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
2226 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2227 match value {
2228 Value::LogicalArray(arr) => {
2229 assert_eq!(arr.shape, vec![3, 1]);
2230 assert_eq!(arr.data, vec![0, 1, 1]);
2231 }
2232 other => panic!("expected logical array, got {:?}", other),
2233 }
2234 }
2235
2236 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2237 #[test]
2238 fn uniform_values_enforced_on_assignment() {
2239 let map = containers_map_builtin(vec![Value::from("UniformValues"), Value::from(true)])
2240 .expect("map");
2241 let payload = crate::make_cell(vec![Value::from("x")], 1, 1).unwrap();
2242 let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(1.0))
2243 .expect("assign");
2244 let err = containers_map_subsasgn(map, "()".to_string(), payload, Value::from("text"))
2245 .expect_err("uniform enforcement");
2246 let message = error_message(err);
2247 assert!(message.contains("UniformValues"));
2248 }
2249
2250 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2251 #[test]
2252 fn assignment_updates_and_inserts() {
2253 let map = containers_map_builtin(Vec::new()).expect("map");
2254 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2255 let updated = containers_map_subsasgn(
2256 map.clone(),
2257 "()".to_string(),
2258 payload.clone(),
2259 Value::Num(1.0),
2260 )
2261 .expect("assign");
2262 let updated = containers_map_subsasgn(
2263 updated.clone(),
2264 "()".to_string(),
2265 payload.clone(),
2266 Value::Num(5.0),
2267 )
2268 .expect("update");
2269 let beta_payload = crate::make_cell(vec![Value::from("beta")], 1, 1).unwrap();
2270 let updated = containers_map_subsasgn(
2271 updated.clone(),
2272 "()".to_string(),
2273 beta_payload,
2274 Value::Num(9.0),
2275 )
2276 .expect("insert");
2277 let value = containers_map_subsref(updated, "()".to_string(), payload).expect("lookup");
2278 assert_eq!(value, Value::Num(5.0));
2279 }
2280
2281 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2282 #[test]
2283 fn subsref_multiple_keys_preserves_shape() {
2284 let keys = crate::make_cell(
2285 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2286 1,
2287 3,
2288 )
2289 .unwrap();
2290 let values = crate::make_cell(
2291 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2292 1,
2293 3,
2294 )
2295 .unwrap();
2296 let map = containers_map_builtin(vec![keys, values]).expect("map");
2297 let request = crate::make_cell(vec![Value::from("a"), Value::from("c")], 1, 2).unwrap();
2298 let payload = crate::make_cell(vec![request], 1, 1).unwrap();
2299 let result =
2300 containers_map_subsref(map.clone(), "()".to_string(), payload).expect("lookup");
2301 match result {
2302 Value::Cell(cell) => {
2303 assert_eq!(cell.rows, 1);
2304 assert_eq!(cell.cols, 2);
2305 assert_eq!(cell.get(0, 0).expect("cell 0,0"), Value::Num(1.0));
2306 assert_eq!(cell.get(0, 1).expect("cell 0,1"), Value::Num(3.0));
2307 }
2308 other => panic!("expected cell array, got {other:?}"),
2309 }
2310 }
2311
2312 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2313 #[test]
2314 fn subsref_empty_key_collection_returns_empty_cell() {
2315 let keys = crate::make_cell(vec![Value::from("z")], 1, 1).unwrap();
2316 let values = crate::make_cell(vec![Value::Num(42.0)], 1, 1).unwrap();
2317 let map = containers_map_builtin(vec![keys, values]).expect("map");
2318 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
2319 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
2320 let result = containers_map_subsref(map, "()".to_string(), payload).expect("lookup empty");
2321 match result {
2322 Value::Cell(cell) => {
2323 assert_eq!(cell.rows, 1);
2324 assert_eq!(cell.cols, 0);
2325 assert!(cell.data.is_empty());
2326 }
2327 other => panic!("expected empty cell, got {other:?}"),
2328 }
2329 }
2330
2331 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2332 #[test]
2333 fn subsasgn_with_cell_keys_updates_all_targets() {
2334 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2335 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2336 let map = containers_map_builtin(vec![keys, values]).expect("map");
2337 let key_spec = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2338 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
2339 let new_values = crate::make_cell(vec![Value::Num(10.0), Value::Num(20.0)], 1, 2).unwrap();
2340 let updated = containers_map_subsasgn(map.clone(), "()".to_string(), payload, new_values)
2341 .expect("assign");
2342 let a_payload = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
2343 let b_payload = crate::make_cell(vec![Value::from("b")], 1, 1).unwrap();
2344 let a_value =
2345 containers_map_subsref(updated.clone(), "()".to_string(), a_payload).expect("a lookup");
2346 let b_value =
2347 containers_map_subsref(updated, "()".to_string(), b_payload).expect("b lookup");
2348 assert_eq!(a_value, Value::Num(10.0));
2349 assert_eq!(b_value, Value::Num(20.0));
2350 }
2351
2352 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2353 #[test]
2354 fn assignment_value_count_mismatch_errors() {
2355 let keys = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
2356 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2357 let map = containers_map_builtin(vec![keys, values]).expect("map");
2358 let key_spec = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
2359 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
2360 let rhs = crate::make_cell(vec![Value::Num(99.0)], 1, 1).unwrap();
2361 let err =
2362 containers_map_subsasgn(map, "()".to_string(), payload, rhs).expect_err("value count");
2363 let message = error_message(err);
2364 assert!(message.contains("requires 2 values"));
2365 }
2366
2367 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2368 #[test]
2369 fn subsasgn_empty_key_collection_is_noop() {
2370 let keys = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
2371 let values = crate::make_cell(vec![Value::Num(7.0)], 1, 1).unwrap();
2372 let map = containers_map_builtin(vec![keys, values]).expect("map");
2373 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
2374 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
2375 let rhs = crate::make_cell(Vec::new(), 1, 0).unwrap();
2376 let updated =
2377 containers_map_subsasgn(map.clone(), "()".to_string(), payload, rhs).expect("assign");
2378 let lookup_payload = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
2379 let value =
2380 containers_map_subsref(updated, "()".to_string(), lookup_payload).expect("lookup");
2381 assert_eq!(value, Value::Num(7.0));
2382 }
2383
2384 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2385 #[test]
2386 fn keys_values_iskey_remove() {
2387 let keys = crate::make_cell(
2388 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2389 1,
2390 3,
2391 )
2392 .unwrap();
2393 let values = crate::make_cell(
2394 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2395 1,
2396 3,
2397 )
2398 .unwrap();
2399 let map = containers_map_builtin(vec![keys, values]).expect("map");
2400 let key_list = containers_map_keys(map.clone()).expect("keys");
2401 match key_list {
2402 Value::Cell(cell) => assert_eq!(cell.data.len(), 3),
2403 other => panic!("expected cell array, got {other:?}"),
2404 }
2405 let mask = containers_map_is_key(
2406 map.clone(),
2407 crate::make_cell(vec![Value::from("a"), Value::from("z")], 1, 2).unwrap(),
2408 )
2409 .expect("mask");
2410 match mask {
2411 Value::LogicalArray(arr) => {
2412 assert_eq!(arr.data, vec![1, 0]);
2413 }
2414 other => panic!("expected logical array, got {:?}", other),
2415 }
2416 let removed = containers_map_remove(
2417 map.clone(),
2418 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
2419 )
2420 .expect("remove");
2421 let mask = containers_map_is_key(
2422 removed,
2423 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
2424 )
2425 .expect("mask");
2426 assert_eq!(mask, Value::Bool(false));
2427 }
2428
2429 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2430 #[test]
2431 fn remove_missing_key_returns_error() {
2432 let keys = crate::make_cell(vec![Value::from("key")], 1, 1).unwrap();
2433 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2434 let map = containers_map_builtin(vec![keys, values]).expect("map");
2435 let err = containers_map_remove(
2436 map,
2437 crate::make_cell(vec![Value::from("missing")], 1, 1).unwrap(),
2438 )
2439 .expect_err("remove missing");
2440 assert_eq!(
2441 err.identifier(),
2442 CONTAINERS_MAP_ERROR_MISSING_KEY.identifier
2443 );
2444 let message = error_message(err);
2445 assert_eq!(message, CONTAINERS_MAP_ERROR_MISSING_KEY.message);
2446 }
2447
2448 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2449 #[test]
2450 fn length_delegates_to_map_count() {
2451 let keys = crate::make_cell(
2452 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2453 1,
2454 3,
2455 )
2456 .unwrap();
2457 let values = crate::make_cell(
2458 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2459 1,
2460 3,
2461 )
2462 .unwrap();
2463 let map = containers_map_builtin(vec![keys, values]).expect("map");
2464 assert_eq!(map_length(&map), Some(3));
2465 }
2466
2467 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2468 #[test]
2469 fn map_id_rejects_corrupted_numeric_identifiers() {
2470 for id_value in [
2471 Value::Num(1.9),
2472 Value::Num(f64::INFINITY),
2473 Value::Num(u64::MAX as f64),
2474 ] {
2475 let mut storage = ObjectInstance::new(CLASS_NAME.to_string());
2476 storage.properties.insert("id".to_string(), id_value);
2477 let target = runmat_gc::gc_allocate(Value::Object(storage)).expect("storage");
2478 let handle = HandleRef {
2479 class_name: CLASS_NAME.to_string(),
2480 target,
2481 valid: true,
2482 };
2483
2484 let err =
2485 map_id(&handle, BUILTIN_CONSTRUCTOR).expect_err("corrupted map id should reject");
2486 assert_eq!(err.identifier(), CONTAINERS_MAP_ERROR_INTERNAL.identifier);
2487 }
2488 }
2489
2490 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2491 #[test]
2492 fn map_constructor_gathers_gpu_values() {
2493 test_support::with_test_provider(|provider| {
2494 let keys = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2495 let data = vec![1.0, 2.0, 3.0];
2496 let shape = vec![3, 1];
2497 let view = runmat_accelerate_api::HostTensorView {
2498 data: &data,
2499 shape: &shape,
2500 };
2501 let handle = provider.upload(&view).expect("upload");
2502 let values = crate::make_cell(vec![Value::GpuTensor(handle)], 1, 1).unwrap();
2503 let map = containers_map_builtin(vec![keys, values]).expect("map");
2504 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2505 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2506 match value {
2507 Value::Tensor(t) => {
2508 assert_eq!(t.shape, shape);
2509 assert_eq!(t.data, data);
2510 }
2511 other => panic!("expected tensor, got {:?}", other),
2512 }
2513 });
2514 }
2515}