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::SymbolicArray(_)
1749 | Value::FunctionHandle(_)
1750 | Value::ExternalFunctionHandle(_)
1751 | Value::MethodFunctionHandle(_)
1752 | Value::BoundFunctionHandle { .. }
1753 | Value::Closure(_)
1754 | Value::ClassRef(_)
1755 | Value::MException(_)
1756 | Value::GpuTensor(_)
1757 | Value::OutputList(_) => Err(map_error(
1758 "containers.Map: values must be numeric when ValueType is 'double' or 'single'",
1759 builtin,
1760 )),
1761 }
1762}
1763
1764fn normalize_logical_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1765 match value {
1766 Value::Bool(_) | Value::LogicalArray(_) => Ok(value),
1767 Value::Int(i) => Ok(Value::Bool(i.to_i64() != 0)),
1768 Value::Num(n) => Ok(Value::Bool(n != 0.0)),
1769 Value::Tensor(t) => {
1770 let flags: Vec<u8> = t
1771 .data
1772 .iter()
1773 .map(|&v| if v != 0.0 { 1 } else { 0 })
1774 .collect();
1775 let logical = LogicalArray::new(flags, t.shape.clone())
1776 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1777 Ok(Value::LogicalArray(logical))
1778 }
1779 Value::CharArray(_)
1780 | Value::SparseTensor(_)
1781 | Value::String(_)
1782 | Value::StringArray(_)
1783 | Value::Struct(_)
1784 | Value::Cell(_)
1785 | Value::Object(_)
1786 | Value::HandleObject(_)
1787 | Value::Listener(_)
1788 | Value::Complex(_, _)
1789 | Value::ComplexTensor(_)
1790 | Value::Symbolic(_)
1791 | Value::SymbolicArray(_)
1792 | Value::FunctionHandle(_)
1793 | Value::ExternalFunctionHandle(_)
1794 | Value::MethodFunctionHandle(_)
1795 | Value::BoundFunctionHandle { .. }
1796 | Value::Closure(_)
1797 | Value::ClassRef(_)
1798 | Value::MException(_)
1799 | Value::GpuTensor(_)
1800 | Value::OutputList(_) => Err(map_error(
1801 "containers.Map: values must be logical when ValueType is 'logical'",
1802 builtin,
1803 )),
1804 }
1805}
1806
1807fn numeric_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<f64> {
1808 match value {
1809 Value::Num(n) => Ok(*n),
1810 Value::Int(i) => Ok(i.to_f64()),
1811 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
1812 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
1813 Value::LogicalArray(arr) if arr.data.len() == 1 => {
1814 Ok(if arr.data[0] != 0 { 1.0 } else { 0.0 })
1815 }
1816 _ => Err(map_error(context, builtin)),
1817 }
1818}
1819
1820fn integer_from_value(
1821 value: &Value,
1822 min: i64,
1823 max: i64,
1824 context: &str,
1825 builtin: &'static str,
1826) -> BuiltinResult<i64> {
1827 match value {
1828 Value::Int(i) => {
1829 let v = i.to_i64();
1830 if v < min || v > max {
1831 return Err(map_error(context, builtin));
1832 }
1833 Ok(v)
1834 }
1835 Value::Num(n) => {
1836 if !n.is_finite() {
1837 return Err(map_error(context, builtin));
1838 }
1839 if (*n < min as f64) || (*n > max as f64) {
1840 return Err(map_error(context, builtin));
1841 }
1842 if (n.round() - n).abs() > f64::EPSILON {
1843 return Err(map_error(context, builtin));
1844 }
1845 Ok(n.round() as i64)
1846 }
1847 Value::Bool(b) => {
1848 let v = if *b { 1 } else { 0 };
1849 if v < min || v > max {
1850 return Err(map_error(context, builtin));
1851 }
1852 Ok(v)
1853 }
1854 _ => Err(map_error(context, builtin)),
1855 }
1856}
1857
1858fn unsigned_from_value(
1859 value: &Value,
1860 max: u64,
1861 context: &str,
1862 builtin: &'static str,
1863) -> BuiltinResult<u64> {
1864 match value {
1865 Value::Int(i) => {
1866 let v = i.to_i64();
1867 if v < 0 || v as u64 > max {
1868 return Err(map_error(context, builtin));
1869 }
1870 Ok(v as u64)
1871 }
1872 Value::Num(n) => {
1873 if !n.is_finite() || *n < 0.0 || *n > max as f64 {
1874 return Err(map_error(context, builtin));
1875 }
1876 if (n.round() - n).abs() > f64::EPSILON {
1877 return Err(map_error(context, builtin));
1878 }
1879 Ok(n.round() as u64)
1880 }
1881 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
1882 _ => Err(map_error(context, builtin)),
1883 }
1884}
1885
1886fn bool_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<bool> {
1887 match value {
1888 Value::Bool(b) => Ok(*b),
1889 Value::LogicalArray(arr) if arr.data.len() == 1 => Ok(arr.data[0] != 0),
1890 Value::Int(i) => Ok(i.to_i64() != 0),
1891 Value::Num(n) => Ok(*n != 0.0),
1892 _ => Err(map_error(context, builtin)),
1893 }
1894}
1895
1896fn make_row_cell(values: Vec<Value>, builtin: &'static str) -> BuiltinResult<Value> {
1897 let cols = values.len();
1898 crate::make_cell_with_shape(values, vec![1, cols])
1899 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1900}
1901
1902fn extract_key_arguments(payload: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1903 match payload {
1904 Value::Cell(cell) => {
1905 let mut out = Vec::with_capacity(cell.data.len());
1906 for ptr in &cell.data {
1907 out.push(ptr.clone());
1908 }
1909 Ok(out)
1910 }
1911 other => Err(map_error(
1912 format!("containers.Map: expected key arguments in a cell array, got {other:?}"),
1913 builtin,
1914 )),
1915 }
1916}
1917
1918async fn expand_assignment_values(
1919 value: Value,
1920 expected: usize,
1921 builtin: &'static str,
1922) -> BuiltinResult<Vec<Value>> {
1923 let host = gather_if_needed_async(&value)
1924 .await
1925 .map_err(|err| attach_builtin_context(err, builtin))?;
1926 let values = flatten_values(&host, builtin).await?;
1927 if expected == 1 {
1928 if values.is_empty() {
1929 return Err(map_error(
1930 "containers.Map: assignment requires a value",
1931 builtin,
1932 ));
1933 }
1934 Ok(vec![values.into_iter().next().unwrap()])
1935 } else {
1936 if values.len() != expected {
1937 return Err(map_error(
1938 format!(
1939 "containers.Map: assignment with {} keys requires {} values (got {})",
1940 expected,
1941 expected,
1942 values.len()
1943 ),
1944 builtin,
1945 ));
1946 }
1947 Ok(values)
1948 }
1949}
1950
1951struct KeyCollection {
1952 values: Vec<Value>,
1953 shape: Vec<usize>,
1954}
1955
1956async fn collect_key_spec(
1957 value: &Value,
1958 key_type: KeyType,
1959 builtin: &'static str,
1960) -> BuiltinResult<KeyCollection> {
1961 let host = gather_if_needed_async(value)
1962 .await
1963 .map_err(|err| attach_builtin_context(err, builtin))?;
1964 match &host {
1965 Value::Cell(cell) => {
1966 let mut values = Vec::with_capacity(cell.data.len());
1967 for ptr in &cell.data {
1968 values.push(
1969 gather_if_needed_async(ptr)
1970 .await
1971 .map_err(|err| attach_builtin_context(err, builtin))?,
1972 );
1973 }
1974 Ok(KeyCollection {
1975 values,
1976 shape: vec![cell.rows, cell.cols],
1977 })
1978 }
1979 Value::StringArray(sa) => Ok(KeyCollection {
1980 values: sa.data.iter().map(|s| Value::String(s.clone())).collect(),
1981 shape: vec![sa.rows(), sa.cols()],
1982 }),
1983 Value::CharArray(ca) => {
1984 let rows = if ca.rows == 0 { 0 } else { ca.rows };
1985 Ok(KeyCollection {
1986 values: char_array_rows(ca, builtin)?,
1987 shape: vec![rows, 1],
1988 })
1989 }
1990 Value::LogicalArray(arr) if key_type == KeyType::Logical => Ok(KeyCollection {
1991 values: arr.data.iter().map(|&b| Value::Bool(b != 0)).collect(),
1992 shape: arr.shape.clone(),
1993 }),
1994 Value::Tensor(t) if key_type != KeyType::Char && key_type != KeyType::String => {
1995 Ok(KeyCollection {
1996 values: t.data.iter().map(|&n| Value::Num(n)).collect(),
1997 shape: t.shape.clone(),
1998 })
1999 }
2000 _ => Ok(KeyCollection {
2001 values: vec![host.clone()],
2002 shape: vec![1, 1],
2003 }),
2004 }
2005}
2006
2007pub fn map_length(value: &Value) -> Option<usize> {
2008 if let Value::HandleObject(handle) = value {
2009 if crate::is_handle_valid(handle) && handle.class_name == CLASS_NAME {
2010 if let Ok(id) = map_id(handle, BUILTIN_CONSTRUCTOR) {
2011 return MAP_REGISTRY.with(|registry| {
2012 registry
2013 .try_borrow()
2014 .ok()
2015 .and_then(|registry| registry.get(&id).map(|store| store.len()))
2016 });
2017 }
2018 }
2019 }
2020 None
2021}
2022
2023#[cfg(test)]
2024pub(crate) mod tests {
2025 use super::*;
2026 use crate::builtins::common::test_support;
2027 use futures::executor::block_on;
2028 use runmat_builtins::{ResolveContext, Type};
2029
2030 fn error_message(err: crate::RuntimeError) -> String {
2031 err.message.clone()
2032 }
2033
2034 fn containers_map_builtin(args: Vec<Value>) -> BuiltinResult<Value> {
2035 block_on(super::containers_map_builtin(args))
2036 }
2037
2038 fn containers_map_keys(map: Value) -> BuiltinResult<Value> {
2039 block_on(super::containers_map_keys(map))
2040 }
2041
2042 fn containers_map_is_key(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2043 block_on(super::containers_map_is_key(map, key_spec))
2044 }
2045
2046 fn containers_map_remove(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2047 block_on(super::containers_map_remove(map, key_spec))
2048 }
2049
2050 fn containers_map_subsref(map: Value, kind: String, payload: Value) -> BuiltinResult<Value> {
2051 block_on(super::containers_map_subsref(map, kind, payload))
2052 }
2053
2054 fn containers_map_subsasgn(
2055 map: Value,
2056 kind: String,
2057 payload: Value,
2058 rhs: Value,
2059 ) -> BuiltinResult<Value> {
2060 block_on(super::containers_map_subsasgn(map, kind, payload, rhs))
2061 }
2062
2063 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2064 #[test]
2065 fn construct_empty_map_defaults() {
2066 let map = containers_map_builtin(Vec::new()).expect("map");
2067 let count = containers_map_subsref(
2068 map.clone(),
2069 ".".to_string(),
2070 Value::from("Count".to_string()),
2071 )
2072 .expect("Count");
2073 assert_eq!(count, Value::Num(0.0));
2074
2075 let key_type = containers_map_subsref(
2076 map.clone(),
2077 ".".to_string(),
2078 Value::from("KeyType".to_string()),
2079 )
2080 .expect("KeyType");
2081 assert_eq!(
2082 key_type,
2083 Value::CharArray(CharArray::new("char".chars().collect(), 1, 4).unwrap())
2084 );
2085
2086 let value_type = containers_map_subsref(
2087 map.clone(),
2088 ".".to_string(),
2089 Value::from("ValueType".to_string()),
2090 )
2091 .expect("ValueType");
2092 assert_eq!(
2093 value_type,
2094 Value::CharArray(CharArray::new("any".chars().collect(), 1, 3).unwrap())
2095 );
2096 }
2097
2098 #[test]
2099 fn map_type_resolvers_basics() {
2100 let ctx = ResolveContext::new(Vec::new());
2101 assert_eq!(map_handle_type(&[Type::Unknown], &ctx), Type::Unknown);
2102 assert_eq!(map_cell_type(&[], &ctx), Type::cell());
2103 assert_eq!(map_is_key_type(&[Type::String], &ctx), Type::logical());
2104 assert_eq!(map_unknown_type(&[], &ctx), Type::Unknown);
2105 }
2106
2107 #[test]
2108 fn containers_map_descriptor_includes_constructor_and_method_signatures() {
2109 let constructor_labels: Vec<&str> = CONTAINERS_MAP_DESCRIPTOR
2110 .signatures
2111 .iter()
2112 .map(|sig| sig.label)
2113 .collect();
2114 assert!(constructor_labels.contains(&"M = containers.Map()"));
2115 assert!(constructor_labels.contains(&"M = containers.Map(keys, values)"));
2116
2117 let method_labels: Vec<&str> = CONTAINERS_MAP_SUBSREF_DESCRIPTOR
2118 .signatures
2119 .iter()
2120 .map(|sig| sig.label)
2121 .collect();
2122 assert!(method_labels.contains(&"value = containers.Map.subsref(M, kind, payload)"));
2123 }
2124
2125 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2126 #[test]
2127 fn constructor_with_cells_lookup() {
2128 let keys = crate::make_cell(vec![Value::from("apple"), Value::from("pear")], 1, 2).unwrap();
2129 let values = crate::make_cell(vec![Value::Num(5.0), Value::Num(7.0)], 1, 2).unwrap();
2130 let map = containers_map_builtin(vec![keys, values]).expect("map");
2131 let apple = containers_map_subsref(
2132 map.clone(),
2133 "()".to_string(),
2134 crate::make_cell(vec![Value::from("apple")], 1, 1).unwrap(),
2135 )
2136 .expect("lookup");
2137 assert_eq!(apple, Value::Num(5.0));
2138 }
2139
2140 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2141 #[test]
2142 fn constructor_rejects_duplicate_keys() {
2143 let keys = crate::make_cell(vec![Value::from("dup"), Value::from("dup")], 1, 2).unwrap();
2144 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2145 let err = containers_map_builtin(vec![keys, values]).expect_err("duplicate check");
2146 let message = error_message(err);
2147 assert!(message.contains("Duplicate key name"));
2148 }
2149
2150 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2151 #[test]
2152 fn constructor_errors_when_value_count_mismatch() {
2153 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2154 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2155 let err = containers_map_builtin(vec![keys, values]).expect_err("count mismatch");
2156 let message = error_message(err);
2157 assert!(message.contains("number of keys"));
2158 }
2159
2160 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2161 #[test]
2162 fn comparison_method_rejects_unknown_values() {
2163 let keys = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
2164 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2165 let err = containers_map_builtin(vec![
2166 keys,
2167 values,
2168 Value::from("ComparisonMethod"),
2169 Value::from("caseinsensitive"),
2170 ])
2171 .expect_err("comparison method");
2172 let message = error_message(err);
2173 assert!(message.contains("ComparisonMethod"));
2174 }
2175
2176 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2177 #[test]
2178 fn key_type_single_roundtrip() {
2179 let map = containers_map_builtin(vec![Value::from("KeyType"), Value::from("single")])
2180 .expect("map");
2181 let key_type = containers_map_subsref(map.clone(), ".".to_string(), Value::from("KeyType"))
2182 .expect("keytype");
2183 assert_eq!(
2184 key_type,
2185 Value::CharArray(CharArray::new("single".chars().collect(), 1, 6).unwrap())
2186 );
2187
2188 let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2189 let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(7.0))
2190 .expect("assign");
2191 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2192 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
2193 }
2194
2195 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2196 #[test]
2197 fn value_type_double_converts_integers() {
2198 let keys = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2199 let values = crate::make_cell(vec![Value::Int(IntValue::I32(7))], 1, 1).unwrap();
2200 let map = containers_map_builtin(vec![
2201 keys,
2202 values,
2203 Value::from("KeyType"),
2204 Value::from("double"),
2205 Value::from("ValueType"),
2206 Value::from("double"),
2207 ])
2208 .expect("map");
2209 let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2210 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2211 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
2212 }
2213
2214 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2215 #[test]
2216 fn value_type_logical_converts_numeric_arrays() {
2217 let keys = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
2218 let tensor = Tensor::new(vec![0.0, 2.0, -3.0], vec![3, 1]).unwrap();
2219 let values = crate::make_cell(vec![Value::Tensor(tensor.clone())], 1, 1).unwrap();
2220 let map = containers_map_builtin(vec![
2221 keys,
2222 values,
2223 Value::from("ValueType"),
2224 Value::from("logical"),
2225 ])
2226 .expect("map");
2227 let payload = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
2228 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2229 match value {
2230 Value::LogicalArray(arr) => {
2231 assert_eq!(arr.shape, vec![3, 1]);
2232 assert_eq!(arr.data, vec![0, 1, 1]);
2233 }
2234 other => panic!("expected logical array, got {:?}", other),
2235 }
2236 }
2237
2238 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2239 #[test]
2240 fn uniform_values_enforced_on_assignment() {
2241 let map = containers_map_builtin(vec![Value::from("UniformValues"), Value::from(true)])
2242 .expect("map");
2243 let payload = crate::make_cell(vec![Value::from("x")], 1, 1).unwrap();
2244 let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(1.0))
2245 .expect("assign");
2246 let err = containers_map_subsasgn(map, "()".to_string(), payload, Value::from("text"))
2247 .expect_err("uniform enforcement");
2248 let message = error_message(err);
2249 assert!(message.contains("UniformValues"));
2250 }
2251
2252 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2253 #[test]
2254 fn assignment_updates_and_inserts() {
2255 let map = containers_map_builtin(Vec::new()).expect("map");
2256 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2257 let updated = containers_map_subsasgn(
2258 map.clone(),
2259 "()".to_string(),
2260 payload.clone(),
2261 Value::Num(1.0),
2262 )
2263 .expect("assign");
2264 let updated = containers_map_subsasgn(
2265 updated.clone(),
2266 "()".to_string(),
2267 payload.clone(),
2268 Value::Num(5.0),
2269 )
2270 .expect("update");
2271 let beta_payload = crate::make_cell(vec![Value::from("beta")], 1, 1).unwrap();
2272 let updated = containers_map_subsasgn(
2273 updated.clone(),
2274 "()".to_string(),
2275 beta_payload,
2276 Value::Num(9.0),
2277 )
2278 .expect("insert");
2279 let value = containers_map_subsref(updated, "()".to_string(), payload).expect("lookup");
2280 assert_eq!(value, Value::Num(5.0));
2281 }
2282
2283 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2284 #[test]
2285 fn subsref_multiple_keys_preserves_shape() {
2286 let keys = crate::make_cell(
2287 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2288 1,
2289 3,
2290 )
2291 .unwrap();
2292 let values = crate::make_cell(
2293 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2294 1,
2295 3,
2296 )
2297 .unwrap();
2298 let map = containers_map_builtin(vec![keys, values]).expect("map");
2299 let request = crate::make_cell(vec![Value::from("a"), Value::from("c")], 1, 2).unwrap();
2300 let payload = crate::make_cell(vec![request], 1, 1).unwrap();
2301 let result =
2302 containers_map_subsref(map.clone(), "()".to_string(), payload).expect("lookup");
2303 match result {
2304 Value::Cell(cell) => {
2305 assert_eq!(cell.rows, 1);
2306 assert_eq!(cell.cols, 2);
2307 assert_eq!(cell.get(0, 0).expect("cell 0,0"), Value::Num(1.0));
2308 assert_eq!(cell.get(0, 1).expect("cell 0,1"), Value::Num(3.0));
2309 }
2310 other => panic!("expected cell array, got {other:?}"),
2311 }
2312 }
2313
2314 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2315 #[test]
2316 fn subsref_empty_key_collection_returns_empty_cell() {
2317 let keys = crate::make_cell(vec![Value::from("z")], 1, 1).unwrap();
2318 let values = crate::make_cell(vec![Value::Num(42.0)], 1, 1).unwrap();
2319 let map = containers_map_builtin(vec![keys, values]).expect("map");
2320 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
2321 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
2322 let result = containers_map_subsref(map, "()".to_string(), payload).expect("lookup empty");
2323 match result {
2324 Value::Cell(cell) => {
2325 assert_eq!(cell.rows, 1);
2326 assert_eq!(cell.cols, 0);
2327 assert!(cell.data.is_empty());
2328 }
2329 other => panic!("expected empty cell, got {other:?}"),
2330 }
2331 }
2332
2333 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2334 #[test]
2335 fn subsasgn_with_cell_keys_updates_all_targets() {
2336 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2337 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2338 let map = containers_map_builtin(vec![keys, values]).expect("map");
2339 let key_spec = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
2340 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
2341 let new_values = crate::make_cell(vec![Value::Num(10.0), Value::Num(20.0)], 1, 2).unwrap();
2342 let updated = containers_map_subsasgn(map.clone(), "()".to_string(), payload, new_values)
2343 .expect("assign");
2344 let a_payload = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
2345 let b_payload = crate::make_cell(vec![Value::from("b")], 1, 1).unwrap();
2346 let a_value =
2347 containers_map_subsref(updated.clone(), "()".to_string(), a_payload).expect("a lookup");
2348 let b_value =
2349 containers_map_subsref(updated, "()".to_string(), b_payload).expect("b lookup");
2350 assert_eq!(a_value, Value::Num(10.0));
2351 assert_eq!(b_value, Value::Num(20.0));
2352 }
2353
2354 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2355 #[test]
2356 fn assignment_value_count_mismatch_errors() {
2357 let keys = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
2358 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2359 let map = containers_map_builtin(vec![keys, values]).expect("map");
2360 let key_spec = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
2361 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
2362 let rhs = crate::make_cell(vec![Value::Num(99.0)], 1, 1).unwrap();
2363 let err =
2364 containers_map_subsasgn(map, "()".to_string(), payload, rhs).expect_err("value count");
2365 let message = error_message(err);
2366 assert!(message.contains("requires 2 values"));
2367 }
2368
2369 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2370 #[test]
2371 fn subsasgn_empty_key_collection_is_noop() {
2372 let keys = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
2373 let values = crate::make_cell(vec![Value::Num(7.0)], 1, 1).unwrap();
2374 let map = containers_map_builtin(vec![keys, values]).expect("map");
2375 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
2376 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
2377 let rhs = crate::make_cell(Vec::new(), 1, 0).unwrap();
2378 let updated =
2379 containers_map_subsasgn(map.clone(), "()".to_string(), payload, rhs).expect("assign");
2380 let lookup_payload = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
2381 let value =
2382 containers_map_subsref(updated, "()".to_string(), lookup_payload).expect("lookup");
2383 assert_eq!(value, Value::Num(7.0));
2384 }
2385
2386 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2387 #[test]
2388 fn keys_values_iskey_remove() {
2389 let keys = crate::make_cell(
2390 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2391 1,
2392 3,
2393 )
2394 .unwrap();
2395 let values = crate::make_cell(
2396 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2397 1,
2398 3,
2399 )
2400 .unwrap();
2401 let map = containers_map_builtin(vec![keys, values]).expect("map");
2402 let key_list = containers_map_keys(map.clone()).expect("keys");
2403 match key_list {
2404 Value::Cell(cell) => assert_eq!(cell.data.len(), 3),
2405 other => panic!("expected cell array, got {other:?}"),
2406 }
2407 let mask = containers_map_is_key(
2408 map.clone(),
2409 crate::make_cell(vec![Value::from("a"), Value::from("z")], 1, 2).unwrap(),
2410 )
2411 .expect("mask");
2412 match mask {
2413 Value::LogicalArray(arr) => {
2414 assert_eq!(arr.data, vec![1, 0]);
2415 }
2416 other => panic!("expected logical array, got {:?}", other),
2417 }
2418 let removed = containers_map_remove(
2419 map.clone(),
2420 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
2421 )
2422 .expect("remove");
2423 let mask = containers_map_is_key(
2424 removed,
2425 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
2426 )
2427 .expect("mask");
2428 assert_eq!(mask, Value::Bool(false));
2429 }
2430
2431 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2432 #[test]
2433 fn remove_missing_key_returns_error() {
2434 let keys = crate::make_cell(vec![Value::from("key")], 1, 1).unwrap();
2435 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
2436 let map = containers_map_builtin(vec![keys, values]).expect("map");
2437 let err = containers_map_remove(
2438 map,
2439 crate::make_cell(vec![Value::from("missing")], 1, 1).unwrap(),
2440 )
2441 .expect_err("remove missing");
2442 assert_eq!(
2443 err.identifier(),
2444 CONTAINERS_MAP_ERROR_MISSING_KEY.identifier
2445 );
2446 let message = error_message(err);
2447 assert_eq!(message, CONTAINERS_MAP_ERROR_MISSING_KEY.message);
2448 }
2449
2450 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2451 #[test]
2452 fn length_delegates_to_map_count() {
2453 let keys = crate::make_cell(
2454 vec![Value::from("a"), Value::from("b"), Value::from("c")],
2455 1,
2456 3,
2457 )
2458 .unwrap();
2459 let values = crate::make_cell(
2460 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
2461 1,
2462 3,
2463 )
2464 .unwrap();
2465 let map = containers_map_builtin(vec![keys, values]).expect("map");
2466 assert_eq!(map_length(&map), Some(3));
2467 }
2468
2469 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2470 #[test]
2471 fn map_id_rejects_corrupted_numeric_identifiers() {
2472 for id_value in [
2473 Value::Num(1.9),
2474 Value::Num(f64::INFINITY),
2475 Value::Num(u64::MAX as f64),
2476 ] {
2477 let mut storage = ObjectInstance::new(CLASS_NAME.to_string());
2478 storage.properties.insert("id".to_string(), id_value);
2479 let target = runmat_gc::gc_allocate(Value::Object(storage)).expect("storage");
2480 let handle = HandleRef {
2481 class_name: CLASS_NAME.to_string(),
2482 target,
2483 valid: true,
2484 };
2485
2486 let err =
2487 map_id(&handle, BUILTIN_CONSTRUCTOR).expect_err("corrupted map id should reject");
2488 assert_eq!(err.identifier(), CONTAINERS_MAP_ERROR_INTERNAL.identifier);
2489 }
2490 }
2491
2492 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2493 #[test]
2494 fn map_constructor_gathers_gpu_values() {
2495 test_support::with_test_provider(|provider| {
2496 let keys = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2497 let data = vec![1.0, 2.0, 3.0];
2498 let shape = vec![3, 1];
2499 let view = runmat_accelerate_api::HostTensorView {
2500 data: &data,
2501 shape: &shape,
2502 };
2503 let handle = provider.upload(&view).expect("upload");
2504 let values = crate::make_cell(vec![Value::GpuTensor(handle)], 1, 1).unwrap();
2505 let map = containers_map_builtin(vec![keys, values]).expect("map");
2506 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
2507 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
2508 match value {
2509 Value::Tensor(t) => {
2510 assert_eq!(t.shape, shape);
2511 assert_eq!(t.data, data);
2512 }
2513 other => panic!("expected tensor, got {:?}", other),
2514 }
2515 });
2516 }
2517}