Skip to main content

runmat_runtime/builtins/containers/map/
containers.map.rs

1//! MATLAB-compatible `containers.Map` constructor and methods for RunMat.
2
3use std::collections::HashMap;
4use std::sync::{
5    atomic::{AtomicU64, Ordering},
6    RwLock,
7};
8
9use once_cell::sync::Lazy;
10use runmat_builtins::{CharArray, HandleRef, IntValue, LogicalArray, StructValue, Tensor, Value};
11use runmat_macros::runtime_builtin;
12
13use crate::builtins::common::random_args::keyword_of;
14use crate::builtins::common::spec::{
15    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
16    ReductionNaN, ResidencyPolicy, ShapeRequirements,
17};
18use crate::builtins::containers::type_resolvers::{
19    map_cell_type, map_handle_type, map_is_key_type, map_unknown_type,
20};
21use crate::{build_runtime_error, gather_if_needed_async, BuiltinResult, RuntimeError};
22
23const CLASS_NAME: &str = "containers.Map";
24const MISSING_KEY_ERR: &str = "containers.Map: The specified key is not present in this container.";
25const BUILTIN_CONSTRUCTOR: &str = "containers.Map";
26const BUILTIN_KEYS: &str = "containers.Map.keys";
27const BUILTIN_VALUES: &str = "containers.Map.values";
28const BUILTIN_IS_KEY: &str = "containers.Map.isKey";
29const BUILTIN_REMOVE: &str = "containers.Map.remove";
30const BUILTIN_SUBSREF: &str = "containers.Map.subsref";
31const BUILTIN_SUBSASGN: &str = "containers.Map.subsasgn";
32
33#[runmat_macros::register_gpu_spec(
34    builtin_path = "crate::builtins::containers::map::containers_map"
35)]
36pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
37    name: "containers.Map",
38    op_kind: GpuOpKind::Custom("map"),
39    supported_precisions: &[],
40    broadcast: BroadcastSemantics::None,
41    provider_hooks: &[],
42    constant_strategy: ConstantStrategy::InlineLiteral,
43    residency: ResidencyPolicy::GatherImmediately,
44    nan_mode: ReductionNaN::Include,
45    two_pass_threshold: None,
46    workgroup_size: None,
47    accepts_nan_mode: false,
48    notes: "Map storage is host-resident; GPU inputs are gathered only when split into multiple entries.",
49};
50
51fn map_error(message: impl Into<String>, builtin: &'static str) -> RuntimeError {
52    build_runtime_error(message).with_builtin(builtin).build()
53}
54
55fn attach_builtin_context(mut error: RuntimeError, builtin: &'static str) -> RuntimeError {
56    if error.context.builtin.is_none() {
57        error.context = error.context.with_builtin(builtin);
58    }
59    error
60}
61
62#[runmat_macros::register_fusion_spec(
63    builtin_path = "crate::builtins::containers::map::containers_map"
64)]
65pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
66    name: "containers.Map",
67    shape: ShapeRequirements::Any,
68    constant_strategy: ConstantStrategy::InlineLiteral,
69    elementwise: None,
70    reduction: None,
71    emits_nan: false,
72    notes: "Handles act as fusion sinks; map construction terminates GPU fusion plans.",
73};
74
75static NEXT_ID: AtomicU64 = AtomicU64::new(1);
76static MAP_REGISTRY: Lazy<RwLock<HashMap<u64, MapStore>>> =
77    Lazy::new(|| RwLock::new(HashMap::new()));
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80enum KeyType {
81    Char,
82    String,
83    Double,
84    Single,
85    Int32,
86    UInt32,
87    Int64,
88    UInt64,
89    Logical,
90}
91
92impl KeyType {
93    fn matlab_name(self) -> &'static str {
94        match self {
95            KeyType::Char => "char",
96            KeyType::String => "string",
97            KeyType::Double => "double",
98            KeyType::Single => "single",
99            KeyType::Int32 => "int32",
100            KeyType::UInt32 => "uint32",
101            KeyType::Int64 => "int64",
102            KeyType::UInt64 => "uint64",
103            KeyType::Logical => "logical",
104        }
105    }
106
107    fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
108        let text = string_from_value(value, "containers.Map: expected a KeyType string", builtin)?;
109        match text.to_ascii_lowercase().as_str() {
110            "char" | "character" => Ok(KeyType::Char),
111            "string" => Ok(KeyType::String),
112            "double" => Ok(KeyType::Double),
113            "single" => Ok(KeyType::Single),
114            "int32" => Ok(KeyType::Int32),
115            "uint32" => Ok(KeyType::UInt32),
116            "int64" => Ok(KeyType::Int64),
117            "uint64" => Ok(KeyType::UInt64),
118            "logical" => Ok(KeyType::Logical),
119            other => Err(map_error(
120                format!(
121                    "containers.Map: unsupported KeyType '{other}'. Valid types: char, string, double, int32, uint32, int64, uint64, logical."
122                ),
123                builtin,
124            )),
125        }
126    }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130enum ValueType {
131    Any,
132    Char,
133    String,
134    Double,
135    Single,
136    Logical,
137}
138
139impl ValueType {
140    fn matlab_name(self) -> &'static str {
141        match self {
142            ValueType::Any => "any",
143            ValueType::Char => "char",
144            ValueType::String => "string",
145            ValueType::Double => "double",
146            ValueType::Single => "single",
147            ValueType::Logical => "logical",
148        }
149    }
150
151    fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
152        let text = string_from_value(
153            value,
154            "containers.Map: expected a ValueType string",
155            builtin,
156        )?;
157        match text.to_ascii_lowercase().as_str() {
158            "any" => Ok(ValueType::Any),
159            "char" | "character" => Ok(ValueType::Char),
160            "string" => Ok(ValueType::String),
161            "double" => Ok(ValueType::Double),
162            "single" => Ok(ValueType::Single),
163            "logical" => Ok(ValueType::Logical),
164            other => Err(map_error(
165                format!(
166                    "containers.Map: unsupported ValueType '{other}'. Valid types: any, char, string, double, single, logical."
167                ),
168                builtin,
169            )),
170        }
171    }
172
173    fn normalize(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
174        match self {
175            ValueType::Any => Ok(value),
176            ValueType::Char => {
177                let chars = char_array_from_value(&value, builtin)?;
178                Ok(Value::CharArray(chars))
179            }
180            ValueType::String => {
181                let text = string_from_value(
182                    &value,
183                    "containers.Map: values must be string scalars",
184                    builtin,
185                )?;
186                Ok(Value::String(text))
187            }
188            ValueType::Double | ValueType::Single => normalize_numeric_value(value, builtin),
189            ValueType::Logical => normalize_logical_value(value, builtin),
190        }
191    }
192}
193
194#[derive(Clone, PartialEq, Eq, Hash)]
195enum NormalizedKey {
196    String(String),
197    Float(u64),
198    Int(i64),
199    UInt(u64),
200    Bool(bool),
201}
202
203#[derive(Clone)]
204struct MapEntry {
205    normalized: NormalizedKey,
206    key_value: Value,
207    value: Value,
208}
209
210struct MapStore {
211    key_type: KeyType,
212    value_type: ValueType,
213    uniform_values: bool,
214    uniform_class: Option<ValueClass>,
215    entries: Vec<MapEntry>,
216    index: HashMap<NormalizedKey, usize>,
217}
218
219impl MapStore {
220    fn new(key_type: KeyType, value_type: ValueType, uniform_values: bool) -> Self {
221        Self {
222            key_type,
223            value_type,
224            uniform_values,
225            uniform_class: None,
226            entries: Vec::new(),
227            index: HashMap::new(),
228        }
229    }
230
231    fn len(&self) -> usize {
232        self.entries.len()
233    }
234
235    fn contains(&self, key: &NormalizedKey) -> bool {
236        self.index.contains_key(key)
237    }
238
239    fn get(&self, key: &NormalizedKey) -> Option<Value> {
240        self.index
241            .get(key)
242            .map(|&idx| self.entries[idx].value.clone())
243    }
244
245    fn insert_new(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
246        if self.index.contains_key(&entry.normalized) {
247            return Err(map_error(
248                "containers.Map: Duplicate key name was provided.",
249                builtin,
250            ));
251        }
252        entry.value = self.normalize_value(entry.value, builtin)?;
253        self.track_uniform_class(&entry.value, builtin)?;
254        let idx = self.entries.len();
255        self.entries.push(entry.clone());
256        self.index.insert(entry.normalized, idx);
257        Ok(())
258    }
259
260    fn set(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
261        entry.value = self.normalize_value(entry.value, builtin)?;
262        self.track_uniform_class(&entry.value, builtin)?;
263        if let Some(&idx) = self.index.get(&entry.normalized) {
264            self.entries[idx].value = entry.value.clone();
265            self.entries[idx].key_value = entry.key_value;
266        } else {
267            let idx = self.entries.len();
268            self.entries.push(entry.clone());
269            self.index.insert(entry.normalized, idx);
270        }
271        Ok(())
272    }
273
274    fn remove(&mut self, key: &NormalizedKey, builtin: &'static str) -> BuiltinResult<()> {
275        let idx = match self.index.get(key) {
276            Some(&idx) => idx,
277            None => {
278                return Err(map_error(MISSING_KEY_ERR, builtin));
279            }
280        };
281        self.entries.remove(idx);
282        self.index.clear();
283        for (pos, entry) in self.entries.iter().enumerate() {
284            self.index.insert(entry.normalized.clone(), pos);
285        }
286        if self.entries.is_empty() {
287            self.uniform_class = None;
288        }
289        Ok(())
290    }
291
292    fn keys(&self) -> Vec<Value> {
293        self.entries
294            .iter()
295            .map(|entry| entry.key_value.clone())
296            .collect()
297    }
298
299    fn values(&self) -> Vec<Value> {
300        self.entries
301            .iter()
302            .map(|entry| entry.value.clone())
303            .collect()
304    }
305
306    fn normalize_value(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
307        self.value_type.normalize(value, builtin)
308    }
309
310    fn track_uniform_class(&mut self, value: &Value, builtin: &'static str) -> BuiltinResult<()> {
311        if !self.uniform_values {
312            return Ok(());
313        }
314        let class = ValueClass::from_value(value);
315        if let Some(existing) = &self.uniform_class {
316            if existing != &class {
317                return Err(map_error(
318                    "containers.Map: UniformValues=true requires all values to share the same MATLAB class.",
319                    builtin,
320                ));
321            }
322        } else {
323            self.uniform_class = Some(class);
324        }
325        Ok(())
326    }
327}
328
329#[derive(Clone, Debug, PartialEq, Eq)]
330enum ValueClass {
331    Char,
332    String,
333    Double,
334    Logical,
335    Int,
336    UInt,
337    Cell,
338    Struct,
339    Object,
340    Other(&'static str),
341}
342
343impl ValueClass {
344    fn from_value(value: &Value) -> Self {
345        match value {
346            Value::CharArray(_) => ValueClass::Char,
347            Value::String(_) | Value::StringArray(_) => ValueClass::String,
348            Value::Num(_) | Value::Tensor(_) | Value::ComplexTensor(_) => ValueClass::Double,
349            Value::Bool(_) | Value::LogicalArray(_) => ValueClass::Logical,
350            Value::Int(i) => match i {
351                IntValue::I8(_) | IntValue::I16(_) | IntValue::I32(_) | IntValue::I64(_) => {
352                    ValueClass::Int
353                }
354                IntValue::U8(_) | IntValue::U16(_) | IntValue::U32(_) | IntValue::U64(_) => {
355                    ValueClass::UInt
356                }
357            },
358            Value::Cell(_) => ValueClass::Cell,
359            Value::Struct(_) => ValueClass::Struct,
360            Value::Object(_) | Value::HandleObject(_) | Value::Listener(_) => ValueClass::Object,
361            _ => ValueClass::Other("other"),
362        }
363    }
364}
365
366struct ConstructorArgs {
367    key_type: KeyType,
368    value_type: ValueType,
369    uniform_values: bool,
370    keys: Vec<KeyCandidate>,
371    values: Vec<Value>,
372}
373
374struct KeyCandidate {
375    normalized: NormalizedKey,
376    canonical: Value,
377}
378
379#[runtime_builtin(
380    name = "containers.Map",
381    category = "containers/map",
382    summary = "Create MATLAB-style dictionary objects that map keys to values.",
383    keywords = "map,containers.Map,dictionary,hash map,lookup",
384    accel = "metadata",
385    sink = true,
386    type_resolver(map_handle_type),
387    builtin_path = "crate::builtins::containers::map::containers_map"
388)]
389async fn containers_map_builtin(args: Vec<Value>) -> crate::BuiltinResult<Value> {
390    let parsed = parse_constructor_args(args, BUILTIN_CONSTRUCTOR).await?;
391    let store = build_store(parsed, BUILTIN_CONSTRUCTOR)?;
392    allocate_handle(store, BUILTIN_CONSTRUCTOR)
393}
394
395#[runtime_builtin(
396    name = "containers.Map.keys",
397    type_resolver(map_cell_type),
398    builtin_path = "crate::builtins::containers::map::containers_map"
399)]
400async fn containers_map_keys(map: Value) -> crate::BuiltinResult<Value> {
401    with_store(&map, BUILTIN_KEYS, |store| {
402        let values = store.keys();
403        make_row_cell(values, BUILTIN_KEYS)
404    })
405}
406
407#[runtime_builtin(
408    name = "containers.Map.values",
409    type_resolver(map_cell_type),
410    builtin_path = "crate::builtins::containers::map::containers_map"
411)]
412async fn containers_map_values(map: Value) -> crate::BuiltinResult<Value> {
413    with_store(&map, BUILTIN_VALUES, |store| {
414        let values = store.values();
415        make_row_cell(values, BUILTIN_VALUES)
416    })
417}
418
419#[runtime_builtin(
420    name = "containers.Map.isKey",
421    type_resolver(map_is_key_type),
422    builtin_path = "crate::builtins::containers::map::containers_map"
423)]
424async fn containers_map_is_key(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
425    let key_type = with_store(&map, BUILTIN_IS_KEY, |store| Ok(store.key_type))?;
426    let collection = collect_key_spec(&key_spec, key_type, BUILTIN_IS_KEY).await?;
427    with_store(&map, BUILTIN_IS_KEY, |store| {
428        let mut flags = Vec::with_capacity(collection.values.len());
429        for value in &collection.values {
430            let normalized = normalize_key(value, store.key_type, BUILTIN_IS_KEY)?;
431            flags.push(store.contains(&normalized));
432        }
433        if collection.values.len() == 1 {
434            Ok(Value::Bool(flags[0]))
435        } else {
436            let data: Vec<u8> = flags.into_iter().map(|b| if b { 1 } else { 0 }).collect();
437            let logical = LogicalArray::new(data, collection.shape)
438                .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_IS_KEY))?;
439            Ok(Value::LogicalArray(logical))
440        }
441    })
442}
443
444#[runtime_builtin(
445    name = "containers.Map.remove",
446    type_resolver(map_handle_type),
447    builtin_path = "crate::builtins::containers::map::containers_map"
448)]
449async fn containers_map_remove(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
450    let key_type = with_store(&map, BUILTIN_REMOVE, |store| Ok(store.key_type))?;
451    let collection = collect_key_spec(&key_spec, key_type, BUILTIN_REMOVE).await?;
452    with_store_mut(&map, BUILTIN_REMOVE, |store| {
453        for value in &collection.values {
454            let normalized = normalize_key(value, store.key_type, BUILTIN_REMOVE)?;
455            store.remove(&normalized, BUILTIN_REMOVE)?;
456        }
457        Ok(())
458    })?;
459    Ok(map)
460}
461
462#[runtime_builtin(
463    name = "containers.Map.subsref",
464    type_resolver(map_unknown_type),
465    builtin_path = "crate::builtins::containers::map::containers_map"
466)]
467async fn containers_map_subsref(
468    map: Value,
469    kind: String,
470    payload: Value,
471) -> crate::BuiltinResult<Value> {
472    if !matches!(map, Value::HandleObject(_)) {
473        return Err(map_error(
474            format!("containers.Map: subsref expects a containers.Map handle, got {map:?}"),
475            BUILTIN_SUBSREF,
476        ));
477    }
478    match kind.as_str() {
479        "()" => {
480            let mut args = extract_key_arguments(&payload, BUILTIN_SUBSREF)?;
481            if args.is_empty() {
482                return Err(map_error(
483                    "containers.Map: indexing requires at least one key",
484                    BUILTIN_SUBSREF,
485                ));
486            }
487            if args.len() != 1 {
488                return Err(map_error(
489                    "containers.Map: indexing expects a single key argument",
490                    BUILTIN_SUBSREF,
491                ));
492            }
493            let key_arg = args.remove(0);
494            let key_type = with_store(&map, BUILTIN_SUBSREF, |store| Ok(store.key_type))?;
495            let collection = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSREF).await?;
496            with_store(&map, BUILTIN_SUBSREF, |store| {
497                if collection.values.is_empty() {
498                    return crate::make_cell_with_shape(Vec::new(), collection.shape.clone())
499                        .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_SUBSREF));
500                }
501                if collection.values.len() == 1 {
502                    let normalized =
503                        normalize_key(&collection.values[0], store.key_type, BUILTIN_SUBSREF)?;
504                    store
505                        .get(&normalized)
506                        .ok_or_else(|| map_error(MISSING_KEY_ERR, BUILTIN_SUBSREF))
507                } else {
508                    let mut results = Vec::with_capacity(collection.values.len());
509                    for value in &collection.values {
510                        let normalized = normalize_key(value, store.key_type, BUILTIN_SUBSREF)?;
511                        let stored = store
512                            .get(&normalized)
513                            .ok_or_else(|| map_error(MISSING_KEY_ERR, BUILTIN_SUBSREF))?;
514                        results.push(stored);
515                    }
516                    crate::make_cell_with_shape(results, collection.shape.clone())
517                        .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_SUBSREF))
518                }
519            })
520        }
521        "." => {
522            let field = string_from_value(
523                &payload,
524                "containers.Map: property name must be text",
525                BUILTIN_SUBSREF,
526            )?;
527            with_store(&map, BUILTIN_SUBSREF, |store| {
528                match field.to_ascii_lowercase().as_str() {
529                    "count" => Ok(Value::Num(store.len() as f64)),
530                    "keytype" => char_array_value(store.key_type.matlab_name(), BUILTIN_SUBSREF),
531                    "valuetype" => {
532                        char_array_value(store.value_type.matlab_name(), BUILTIN_SUBSREF)
533                    }
534                    other => Err(map_error(
535                        format!("containers.Map: no such property '{other}'"),
536                        BUILTIN_SUBSREF,
537                    )),
538                }
539            })
540        }
541        "{}" => Err(map_error(
542            "containers.Map: curly-brace indexing is not supported.",
543            BUILTIN_SUBSREF,
544        )),
545        other => Err(map_error(
546            format!("containers.Map: unsupported indexing kind '{other}'"),
547            BUILTIN_SUBSREF,
548        )),
549    }
550}
551
552#[runtime_builtin(
553    name = "containers.Map.subsasgn",
554    type_resolver(map_handle_type),
555    builtin_path = "crate::builtins::containers::map::containers_map"
556)]
557async fn containers_map_subsasgn(
558    map: Value,
559    kind: String,
560    payload: Value,
561    rhs: Value,
562) -> crate::BuiltinResult<Value> {
563    if !matches!(map, Value::HandleObject(_)) {
564        return Err(map_error(
565            format!("containers.Map: subsasgn expects a containers.Map handle, got {map:?}"),
566            BUILTIN_SUBSASGN,
567        ));
568    }
569    match kind.as_str() {
570        "()" => {
571            let mut args = extract_key_arguments(&payload, BUILTIN_SUBSASGN)?;
572            if args.is_empty() {
573                return Err(map_error(
574                    "containers.Map: assignment requires at least one key",
575                    BUILTIN_SUBSASGN,
576                ));
577            }
578            if args.len() != 1 {
579                return Err(map_error(
580                    "containers.Map: assignment expects a single key argument",
581                    BUILTIN_SUBSASGN,
582                ));
583            }
584            let key_arg = args.remove(0);
585            let key_type = with_store(&map, BUILTIN_SUBSASGN, |store| Ok(store.key_type))?;
586            let KeyCollection {
587                values: key_values, ..
588            } = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSASGN).await?;
589            let values =
590                expand_assignment_values(rhs.clone(), key_values.len(), BUILTIN_SUBSASGN).await?;
591            with_store_mut(&map, BUILTIN_SUBSASGN, move |store| {
592                for (key_raw, value) in key_values.into_iter().zip(values.into_iter()) {
593                    let (normalized, canonical) =
594                        canonicalize_key(key_raw, store.key_type, BUILTIN_SUBSASGN)?;
595                    let entry = MapEntry {
596                        normalized,
597                        key_value: canonical,
598                        value,
599                    };
600                    store.set(entry, BUILTIN_SUBSASGN)?;
601                }
602                Ok(())
603            })?;
604            Ok(map)
605        }
606        "." => Err(map_error(
607            "containers.Map: property assignments are not supported.",
608            BUILTIN_SUBSASGN,
609        )),
610        "{}" => Err(map_error(
611            "containers.Map: curly-brace assignment is not supported.",
612            BUILTIN_SUBSASGN,
613        )),
614        other => Err(map_error(
615            format!("containers.Map: unsupported assignment kind '{other}'"),
616            BUILTIN_SUBSASGN,
617        )),
618    }
619}
620
621async fn parse_constructor_args(
622    args: Vec<Value>,
623    builtin: &'static str,
624) -> BuiltinResult<ConstructorArgs> {
625    let mut index = 0usize;
626    let mut keys_input: Option<Value> = None;
627    let mut values_input: Option<Value> = None;
628
629    if index < args.len() && keyword_of(&args[index]).is_none() {
630        if args.len() < 2 {
631            return Err(map_error(
632                "containers.Map: constructor requires both keys and values when either is provided.",
633                builtin,
634            ));
635        }
636        keys_input = Some(args[index].clone());
637        values_input = Some(args[index + 1].clone());
638        index += 2;
639    }
640
641    let mut key_type = KeyType::Char;
642    let mut value_type = ValueType::Any;
643    let mut uniform_values = false;
644    while index < args.len() {
645        let keyword = keyword_of(&args[index]).ok_or_else(|| {
646            map_error(
647                "containers.Map: expected option name (e.g. 'KeyType')",
648                builtin,
649            )
650        })?;
651        index += 1;
652        let Some(value) = args.get(index) else {
653            return Err(map_error(
654                format!("containers.Map: missing value for option '{keyword}'"),
655                builtin,
656            ));
657        };
658        index += 1;
659        match keyword.as_str() {
660            "keytype" => key_type = KeyType::parse(value, builtin)?,
661            "valuetype" => value_type = ValueType::parse(value, builtin)?,
662            "uniformvalues" => {
663                uniform_values = bool_from_value(
664                    value,
665                    "containers.Map: UniformValues must be logical",
666                    builtin,
667                )?
668            }
669            "comparisonmethod" => {
670                let text = string_from_value(
671                    value,
672                    "containers.Map: ComparisonMethod must be a string",
673                    builtin,
674                )?;
675                let lowered = text.to_ascii_lowercase();
676                if lowered != "strcmp" {
677                    return Err(map_error(
678                        "containers.Map: only ComparisonMethod='strcmp' is supported.",
679                        builtin,
680                    ));
681                }
682            }
683            other => {
684                return Err(map_error(
685                    format!("containers.Map: unrecognised option '{other}'"),
686                    builtin,
687                ));
688            }
689        }
690    }
691
692    let keys = match keys_input {
693        Some(value) => prepare_keys(value, key_type, builtin).await?,
694        None => Vec::new(),
695    };
696
697    let values = match values_input {
698        Some(value) => prepare_values(value, builtin).await?,
699        None => Vec::new(),
700    };
701
702    if keys.len() != values.len() {
703        return Err(map_error(
704            format!(
705                "containers.Map: number of keys ({}) must match number of values ({})",
706                keys.len(),
707                values.len()
708            ),
709            builtin,
710        ));
711    }
712
713    Ok(ConstructorArgs {
714        key_type,
715        value_type,
716        uniform_values,
717        keys,
718        values,
719    })
720}
721
722fn build_store(args: ConstructorArgs, builtin: &'static str) -> BuiltinResult<MapStore> {
723    let mut store = MapStore::new(args.key_type, args.value_type, args.uniform_values);
724    for (candidate, value) in args.keys.into_iter().zip(args.values.into_iter()) {
725        store.insert_new(
726            MapEntry {
727                normalized: candidate.normalized,
728                key_value: candidate.canonical,
729                value,
730            },
731            builtin,
732        )?;
733    }
734    Ok(store)
735}
736
737fn allocate_handle(store: MapStore, builtin: &'static str) -> BuiltinResult<Value> {
738    let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
739    MAP_REGISTRY
740        .write()
741        .map_err(|_| map_error("containers.Map: registry lock poisoned", builtin))?
742        .insert(id, store);
743    let mut struct_value = StructValue::new();
744    struct_value
745        .fields
746        .insert("id".to_string(), Value::Int(IntValue::U64(id)));
747    let storage = Value::Struct(struct_value);
748    let gc = runmat_gc::gc_allocate(storage)
749        .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
750    Ok(Value::HandleObject(HandleRef {
751        class_name: CLASS_NAME.to_string(),
752        target: gc,
753        valid: true,
754    }))
755}
756
757fn with_store<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
758where
759    F: FnOnce(&MapStore) -> BuiltinResult<R>,
760{
761    let handle = extract_handle(map, builtin)?;
762    ensure_handle(handle, builtin)?;
763    let id = map_id(handle, builtin)?;
764    let guard = MAP_REGISTRY
765        .read()
766        .map_err(|_| map_error("containers.Map: registry lock poisoned", builtin))?;
767    let store = guard
768        .get(&id)
769        .ok_or_else(|| map_error("containers.Map: internal storage not found", builtin))?;
770    f(store)
771}
772
773fn with_store_mut<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
774where
775    F: FnOnce(&mut MapStore) -> BuiltinResult<R>,
776{
777    let handle = extract_handle(map, builtin)?;
778    ensure_handle(handle, builtin)?;
779    let id = map_id(handle, builtin)?;
780    let mut guard = MAP_REGISTRY
781        .write()
782        .map_err(|_| map_error("containers.Map: registry lock poisoned", builtin))?;
783    let store = guard
784        .get_mut(&id)
785        .ok_or_else(|| map_error("containers.Map: internal storage not found", builtin))?;
786    f(store)
787}
788
789fn extract_handle<'a>(value: &'a Value, builtin: &'static str) -> BuiltinResult<&'a HandleRef> {
790    match value {
791        Value::HandleObject(handle) => Ok(handle),
792        _ => Err(map_error(
793            "containers.Map: expected a containers.Map handle",
794            builtin,
795        )),
796    }
797}
798
799fn ensure_handle(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<()> {
800    if !handle.valid {
801        return Err(map_error("containers.Map: handle is invalid", builtin));
802    }
803    if handle.class_name != CLASS_NAME {
804        return Err(map_error(
805            format!(
806                "containers.Map: expected handle of class '{}', got '{}'",
807                CLASS_NAME, handle.class_name
808            ),
809            builtin,
810        ));
811    }
812    Ok(())
813}
814
815fn map_id(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<u64> {
816    let storage = unsafe { &*handle.target.as_raw() };
817    match storage {
818        Value::Struct(StructValue { fields }) => match fields.get("id") {
819            Some(Value::Int(IntValue::U64(id))) => Ok(*id),
820            Some(Value::Int(other)) => {
821                let id = other.to_i64();
822                if id < 0 {
823                    Err(map_error(
824                        "containers.Map: negative map identifier",
825                        builtin,
826                    ))
827                } else {
828                    Ok(id as u64)
829                }
830            }
831            Some(Value::Num(n)) if *n >= 0.0 => Ok(*n as u64),
832            _ => Err(map_error(
833                "containers.Map: corrupted storage identifier",
834                builtin,
835            )),
836        },
837        other => Err(map_error(
838            format!("containers.Map: internal storage has unexpected shape {other:?}"),
839            builtin,
840        )),
841    }
842}
843
844async fn prepare_keys(
845    value: Value,
846    key_type: KeyType,
847    builtin: &'static str,
848) -> BuiltinResult<Vec<KeyCandidate>> {
849    let host = gather_if_needed_async(&value)
850        .await
851        .map_err(|err| attach_builtin_context(err, builtin))?;
852    let flattened = flatten_keys(&host, key_type, builtin).await?;
853    let mut out = Vec::with_capacity(flattened.len());
854    for raw_key in flattened {
855        let (normalized, canonical) = canonicalize_key(raw_key, key_type, builtin)?;
856        out.push(KeyCandidate {
857            normalized,
858            canonical,
859        });
860    }
861    Ok(out)
862}
863
864async fn prepare_values(value: Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
865    let host = gather_if_needed_async(&value)
866        .await
867        .map_err(|err| attach_builtin_context(err, builtin))?;
868    flatten_values(&host, builtin).await
869}
870
871async fn flatten_keys(
872    value: &Value,
873    key_type: KeyType,
874    builtin: &'static str,
875) -> BuiltinResult<Vec<Value>> {
876    match value {
877        Value::Cell(cell) => {
878            let mut out = Vec::with_capacity(cell.data.len());
879            for ptr in &cell.data {
880                let element = unsafe { &*ptr.as_raw() };
881                if matches!(element, Value::Cell(_)) {
882                    return Err(map_error(
883                        "containers.Map: nested cell arrays are not supported for keys",
884                        builtin,
885                    ));
886                }
887                out.push(
888                    gather_if_needed_async(element)
889                        .await
890                        .map_err(|err| attach_builtin_context(err, builtin))?,
891                );
892            }
893            Ok(out)
894        }
895        Value::StringArray(sa) => Ok(sa
896            .data
897            .iter()
898            .map(|text| Value::String(text.clone()))
899            .collect()),
900        Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
901        Value::LogicalArray(arr) => {
902            if key_type != KeyType::Logical {
903                return Err(map_error(
904                    "containers.Map: logical arrays can only be used with KeyType='logical'",
905                    builtin,
906                ));
907            }
908            Ok(arr.data.iter().map(|&b| Value::Bool(b != 0)).collect())
909        }
910        Value::Tensor(t) => {
911            if !t.shape.is_empty() && t.data.len() != 1 && !is_vector_shape(&t.shape) {
912                return Err(map_error(
913                    "containers.Map: numeric keys must be scalar or vector shaped",
914                    builtin,
915                ));
916            }
917            Ok(t.data.iter().map(|&v| Value::Num(v)).collect())
918        }
919        Value::Num(_) | Value::Int(_) | Value::Bool(_) | Value::String(_) => {
920            Ok(vec![value.clone()])
921        }
922        Value::GpuTensor(_) => Err(map_error(
923            "containers.Map: GPU keys must be gathered to the host before construction",
924            builtin,
925        )),
926        other => Err(map_error(
927            format!("containers.Map: unsupported key container {other:?}"),
928            builtin,
929        )),
930    }
931}
932
933async fn flatten_values(value: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
934    match value {
935        Value::Cell(cell) => {
936            let mut out = Vec::with_capacity(cell.data.len());
937            for ptr in &cell.data {
938                out.push(
939                    gather_if_needed_async(unsafe { &*ptr.as_raw() })
940                        .await
941                        .map_err(|err| attach_builtin_context(err, builtin))?,
942                );
943            }
944            Ok(out)
945        }
946        Value::StringArray(sa) => Ok(sa
947            .data
948            .iter()
949            .map(|text| Value::String(text.clone()))
950            .collect()),
951        Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
952        Value::LogicalArray(arr) => Ok(arr.data.iter().map(|&b| Value::Bool(b != 0)).collect()),
953        Value::Tensor(t) => {
954            if !t.shape.is_empty() && !is_vector_shape(&t.shape) && t.data.len() != 1 {
955                return Err(map_error(
956                    "containers.Map: numeric values must be scalar or vector shaped",
957                    builtin,
958                ));
959            }
960            Ok(t.data.iter().map(|&v| Value::Num(v)).collect())
961        }
962        _ => Ok(vec![value.clone()]),
963    }
964}
965
966fn char_array_rows(ca: &CharArray, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
967    if ca.rows == 0 {
968        return Ok(Vec::new());
969    }
970    let mut out = Vec::with_capacity(ca.rows);
971    for row in 0..ca.rows {
972        let mut text = String::with_capacity(ca.cols);
973        for col in 0..ca.cols {
974            text.push(ca.data[row * ca.cols + col]);
975        }
976        let chars: Vec<char> = text.chars().collect();
977        let array = CharArray::new(chars.clone(), 1, chars.len())
978            .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
979        out.push(Value::CharArray(array));
980    }
981    Ok(out)
982}
983
984fn is_vector_shape(shape: &[usize]) -> bool {
985    match shape.len() {
986        0 => true,
987        1 => true,
988        2 => shape[0] == 1 || shape[1] == 1,
989        _ => false,
990    }
991}
992
993fn canonicalize_key(
994    value: Value,
995    key_type: KeyType,
996    builtin: &'static str,
997) -> BuiltinResult<(NormalizedKey, Value)> {
998    let normalized = normalize_key(&value, key_type, builtin)?;
999    let canonical = match key_type {
1000        KeyType::Char => Value::CharArray(char_array_from_value(&value, builtin)?),
1001        KeyType::String => Value::String(string_from_value(
1002            &value,
1003            "containers.Map: keys must be string scalars",
1004            builtin,
1005        )?),
1006        KeyType::Double => Value::Num(numeric_from_value(
1007            &value,
1008            "containers.Map: keys must be numeric scalars",
1009            builtin,
1010        )?),
1011        KeyType::Single => Value::Num(numeric_from_value(
1012            &value,
1013            "containers.Map: keys must be numeric scalars",
1014            builtin,
1015        )?),
1016        KeyType::Int32 => Value::Int(IntValue::I32(integer_from_value(
1017            &value,
1018            i32::MIN as i64,
1019            i32::MAX as i64,
1020            "containers.Map: int32 keys must be integers",
1021            builtin,
1022        )? as i32)),
1023        KeyType::UInt32 => Value::Int(IntValue::U32(unsigned_from_value(
1024            &value,
1025            u32::MAX as u64,
1026            "containers.Map: uint32 keys must be unsigned integers",
1027            builtin,
1028        )? as u32)),
1029        KeyType::Int64 => Value::Int(IntValue::I64(integer_from_value(
1030            &value,
1031            i64::MIN,
1032            i64::MAX,
1033            "containers.Map: int64 keys must be integers",
1034            builtin,
1035        )?)),
1036        KeyType::UInt64 => Value::Int(IntValue::U64(unsigned_from_value(
1037            &value,
1038            u64::MAX,
1039            "containers.Map: uint64 keys must be unsigned integers",
1040            builtin,
1041        )?)),
1042        KeyType::Logical => Value::Bool(bool_from_value(
1043            &value,
1044            "containers.Map: logical keys must be logical scalars",
1045            builtin,
1046        )?),
1047    };
1048    Ok((normalized, canonical))
1049}
1050
1051fn normalize_key(
1052    value: &Value,
1053    key_type: KeyType,
1054    builtin: &'static str,
1055) -> BuiltinResult<NormalizedKey> {
1056    match key_type {
1057        KeyType::Char | KeyType::String => {
1058            let text =
1059                string_from_value(value, "containers.Map: keys must be text scalars", builtin)?;
1060            Ok(NormalizedKey::String(text))
1061        }
1062        KeyType::Double | KeyType::Single => {
1063            let numeric = numeric_from_value(
1064                value,
1065                "containers.Map: keys must be numeric scalars",
1066                builtin,
1067            )?;
1068            if !numeric.is_finite() {
1069                return Err(map_error(
1070                    "containers.Map: keys must be finite numeric scalars",
1071                    builtin,
1072                ));
1073            }
1074            let canonical = if numeric == 0.0 { 0.0 } else { numeric };
1075            Ok(NormalizedKey::Float(canonical.to_bits()))
1076        }
1077        KeyType::Int32 | KeyType::Int64 => {
1078            let bounds = if key_type == KeyType::Int32 {
1079                (i32::MIN as i64, i32::MAX as i64)
1080            } else {
1081                (i64::MIN, i64::MAX)
1082            };
1083            let value = integer_from_value(
1084                value,
1085                bounds.0,
1086                bounds.1,
1087                "containers.Map: integer keys must be whole numbers",
1088                builtin,
1089            )?;
1090            Ok(NormalizedKey::Int(value))
1091        }
1092        KeyType::UInt32 | KeyType::UInt64 => {
1093            let limit = if key_type == KeyType::UInt32 {
1094                u32::MAX as u64
1095            } else {
1096                u64::MAX
1097            };
1098            let value = unsigned_from_value(
1099                value,
1100                limit,
1101                "containers.Map: unsigned keys must be non-negative integers",
1102                builtin,
1103            )?;
1104            Ok(NormalizedKey::UInt(value))
1105        }
1106        KeyType::Logical => {
1107            let flag = bool_from_value(
1108                value,
1109                "containers.Map: logical keys must be logical scalars",
1110                builtin,
1111            )?;
1112            Ok(NormalizedKey::Bool(flag))
1113        }
1114    }
1115}
1116
1117fn string_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<String> {
1118    match value {
1119        Value::String(s) => Ok(s.clone()),
1120        Value::StringArray(sa) if sa.data.len() == 1 => Ok(sa.data[0].clone()),
1121        Value::CharArray(ca) if ca.rows == 1 => Ok(ca.data.iter().collect()),
1122        _ => Err(map_error(context, builtin)),
1123    }
1124}
1125
1126fn char_array_from_value(value: &Value, builtin: &'static str) -> BuiltinResult<CharArray> {
1127    match value {
1128        Value::CharArray(ca) if ca.rows == 1 => Ok(ca.clone()),
1129        Value::String(s) => {
1130            let chars: Vec<char> = s.chars().collect();
1131            CharArray::new(chars.clone(), 1, chars.len())
1132                .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1133        }
1134        Value::StringArray(sa) if sa.data.len() == 1 => {
1135            let chars: Vec<char> = sa.data[0].chars().collect();
1136            CharArray::new(chars.clone(), 1, chars.len())
1137                .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1138        }
1139        _ => Err(map_error(
1140            "containers.Map: keys must be character vectors",
1141            builtin,
1142        )),
1143    }
1144}
1145
1146fn char_array_value(text: &str, builtin: &'static str) -> BuiltinResult<Value> {
1147    let chars: Vec<char> = text.chars().collect();
1148    CharArray::new(chars.clone(), 1, chars.len())
1149        .map(Value::CharArray)
1150        .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1151}
1152
1153fn normalize_numeric_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1154    match value {
1155        Value::Num(_) | Value::Tensor(_) => Ok(value),
1156        Value::Int(i) => Ok(Value::Num(i.to_f64())),
1157        Value::Bool(b) => Ok(Value::Num(if b { 1.0 } else { 0.0 })),
1158        Value::LogicalArray(arr) => {
1159            let data: Vec<f64> = arr
1160                .data
1161                .iter()
1162                .map(|&b| if b != 0 { 1.0 } else { 0.0 })
1163                .collect();
1164            let tensor = Tensor::new(data, arr.shape.clone())
1165                .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1166            Ok(Value::Tensor(tensor))
1167        }
1168        Value::Cell(_)
1169        | Value::Struct(_)
1170        | Value::Object(_)
1171        | Value::HandleObject(_)
1172        | Value::Listener(_)
1173        | Value::String(_)
1174        | Value::StringArray(_)
1175        | Value::CharArray(_)
1176        | Value::Complex(_, _)
1177        | Value::ComplexTensor(_)
1178        | Value::FunctionHandle(_)
1179        | Value::Closure(_)
1180        | Value::ClassRef(_)
1181        | Value::MException(_)
1182        | Value::GpuTensor(_)
1183        | Value::OutputList(_) => Err(map_error(
1184            "containers.Map: values must be numeric when ValueType is 'double' or 'single'",
1185            builtin,
1186        )),
1187    }
1188}
1189
1190fn normalize_logical_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1191    match value {
1192        Value::Bool(_) | Value::LogicalArray(_) => Ok(value),
1193        Value::Int(i) => Ok(Value::Bool(i.to_i64() != 0)),
1194        Value::Num(n) => Ok(Value::Bool(n != 0.0)),
1195        Value::Tensor(t) => {
1196            let flags: Vec<u8> = t
1197                .data
1198                .iter()
1199                .map(|&v| if v != 0.0 { 1 } else { 0 })
1200                .collect();
1201            let logical = LogicalArray::new(flags, t.shape.clone())
1202                .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
1203            Ok(Value::LogicalArray(logical))
1204        }
1205        Value::CharArray(_)
1206        | Value::String(_)
1207        | Value::StringArray(_)
1208        | Value::Struct(_)
1209        | Value::Cell(_)
1210        | Value::Object(_)
1211        | Value::HandleObject(_)
1212        | Value::Listener(_)
1213        | Value::Complex(_, _)
1214        | Value::ComplexTensor(_)
1215        | Value::FunctionHandle(_)
1216        | Value::Closure(_)
1217        | Value::ClassRef(_)
1218        | Value::MException(_)
1219        | Value::GpuTensor(_)
1220        | Value::OutputList(_) => Err(map_error(
1221            "containers.Map: values must be logical when ValueType is 'logical'",
1222            builtin,
1223        )),
1224    }
1225}
1226
1227fn numeric_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<f64> {
1228    match value {
1229        Value::Num(n) => Ok(*n),
1230        Value::Int(i) => Ok(i.to_f64()),
1231        Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
1232        Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
1233        Value::LogicalArray(arr) if arr.data.len() == 1 => {
1234            Ok(if arr.data[0] != 0 { 1.0 } else { 0.0 })
1235        }
1236        _ => Err(map_error(context, builtin)),
1237    }
1238}
1239
1240fn integer_from_value(
1241    value: &Value,
1242    min: i64,
1243    max: i64,
1244    context: &str,
1245    builtin: &'static str,
1246) -> BuiltinResult<i64> {
1247    match value {
1248        Value::Int(i) => {
1249            let v = i.to_i64();
1250            if v < min || v > max {
1251                return Err(map_error(context, builtin));
1252            }
1253            Ok(v)
1254        }
1255        Value::Num(n) => {
1256            if !n.is_finite() {
1257                return Err(map_error(context, builtin));
1258            }
1259            if (*n < min as f64) || (*n > max as f64) {
1260                return Err(map_error(context, builtin));
1261            }
1262            if (n.round() - n).abs() > f64::EPSILON {
1263                return Err(map_error(context, builtin));
1264            }
1265            Ok(n.round() as i64)
1266        }
1267        Value::Bool(b) => {
1268            let v = if *b { 1 } else { 0 };
1269            if v < min || v > max {
1270                return Err(map_error(context, builtin));
1271            }
1272            Ok(v)
1273        }
1274        _ => Err(map_error(context, builtin)),
1275    }
1276}
1277
1278fn unsigned_from_value(
1279    value: &Value,
1280    max: u64,
1281    context: &str,
1282    builtin: &'static str,
1283) -> BuiltinResult<u64> {
1284    match value {
1285        Value::Int(i) => {
1286            let v = i.to_i64();
1287            if v < 0 || v as u64 > max {
1288                return Err(map_error(context, builtin));
1289            }
1290            Ok(v as u64)
1291        }
1292        Value::Num(n) => {
1293            if !n.is_finite() || *n < 0.0 || *n > max as f64 {
1294                return Err(map_error(context, builtin));
1295            }
1296            if (n.round() - n).abs() > f64::EPSILON {
1297                return Err(map_error(context, builtin));
1298            }
1299            Ok(n.round() as u64)
1300        }
1301        Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
1302        _ => Err(map_error(context, builtin)),
1303    }
1304}
1305
1306fn bool_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<bool> {
1307    match value {
1308        Value::Bool(b) => Ok(*b),
1309        Value::LogicalArray(arr) if arr.data.len() == 1 => Ok(arr.data[0] != 0),
1310        Value::Int(i) => Ok(i.to_i64() != 0),
1311        Value::Num(n) => Ok(*n != 0.0),
1312        _ => Err(map_error(context, builtin)),
1313    }
1314}
1315
1316fn make_row_cell(values: Vec<Value>, builtin: &'static str) -> BuiltinResult<Value> {
1317    let cols = values.len();
1318    crate::make_cell_with_shape(values, vec![1, cols])
1319        .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
1320}
1321
1322fn extract_key_arguments(payload: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
1323    match payload {
1324        Value::Cell(cell) => {
1325            let mut out = Vec::with_capacity(cell.data.len());
1326            for ptr in &cell.data {
1327                out.push(unsafe { &*ptr.as_raw() }.clone());
1328            }
1329            Ok(out)
1330        }
1331        other => Err(map_error(
1332            format!("containers.Map: expected key arguments in a cell array, got {other:?}"),
1333            builtin,
1334        )),
1335    }
1336}
1337
1338async fn expand_assignment_values(
1339    value: Value,
1340    expected: usize,
1341    builtin: &'static str,
1342) -> BuiltinResult<Vec<Value>> {
1343    let host = gather_if_needed_async(&value)
1344        .await
1345        .map_err(|err| attach_builtin_context(err, builtin))?;
1346    let values = flatten_values(&host, builtin).await?;
1347    if expected == 1 {
1348        if values.is_empty() {
1349            return Err(map_error(
1350                "containers.Map: assignment requires a value",
1351                builtin,
1352            ));
1353        }
1354        Ok(vec![values.into_iter().next().unwrap()])
1355    } else {
1356        if values.len() != expected {
1357            return Err(map_error(
1358                format!(
1359                    "containers.Map: assignment with {} keys requires {} values (got {})",
1360                    expected,
1361                    expected,
1362                    values.len()
1363                ),
1364                builtin,
1365            ));
1366        }
1367        Ok(values)
1368    }
1369}
1370
1371struct KeyCollection {
1372    values: Vec<Value>,
1373    shape: Vec<usize>,
1374}
1375
1376async fn collect_key_spec(
1377    value: &Value,
1378    key_type: KeyType,
1379    builtin: &'static str,
1380) -> BuiltinResult<KeyCollection> {
1381    let host = gather_if_needed_async(value)
1382        .await
1383        .map_err(|err| attach_builtin_context(err, builtin))?;
1384    match &host {
1385        Value::Cell(cell) => {
1386            let mut values = Vec::with_capacity(cell.data.len());
1387            for ptr in &cell.data {
1388                values.push(
1389                    gather_if_needed_async(unsafe { &*ptr.as_raw() })
1390                        .await
1391                        .map_err(|err| attach_builtin_context(err, builtin))?,
1392                );
1393            }
1394            Ok(KeyCollection {
1395                values,
1396                shape: vec![cell.rows, cell.cols],
1397            })
1398        }
1399        Value::StringArray(sa) => Ok(KeyCollection {
1400            values: sa.data.iter().map(|s| Value::String(s.clone())).collect(),
1401            shape: vec![sa.rows(), sa.cols()],
1402        }),
1403        Value::CharArray(ca) => {
1404            let rows = if ca.rows == 0 { 0 } else { ca.rows };
1405            Ok(KeyCollection {
1406                values: char_array_rows(ca, builtin)?,
1407                shape: vec![rows, 1],
1408            })
1409        }
1410        Value::LogicalArray(arr) if key_type == KeyType::Logical => Ok(KeyCollection {
1411            values: arr.data.iter().map(|&b| Value::Bool(b != 0)).collect(),
1412            shape: arr.shape.clone(),
1413        }),
1414        Value::Tensor(t) if key_type != KeyType::Char && key_type != KeyType::String => {
1415            Ok(KeyCollection {
1416                values: t.data.iter().map(|&n| Value::Num(n)).collect(),
1417                shape: t.shape.clone(),
1418            })
1419        }
1420        _ => Ok(KeyCollection {
1421            values: vec![host.clone()],
1422            shape: vec![1, 1],
1423        }),
1424    }
1425}
1426
1427pub fn map_length(value: &Value) -> Option<usize> {
1428    if let Value::HandleObject(handle) = value {
1429        if handle.valid && handle.class_name == CLASS_NAME {
1430            if let Ok(id) = map_id(handle, BUILTIN_CONSTRUCTOR) {
1431                if let Ok(registry) = MAP_REGISTRY.read() {
1432                    return registry.get(&id).map(|store| store.len());
1433                }
1434            }
1435        }
1436    }
1437    None
1438}
1439
1440#[cfg(test)]
1441pub(crate) mod tests {
1442    use super::*;
1443    use crate::builtins::common::test_support;
1444    use futures::executor::block_on;
1445    use runmat_builtins::{ResolveContext, Type};
1446
1447    fn error_message(err: crate::RuntimeError) -> String {
1448        err.message.clone()
1449    }
1450
1451    fn containers_map_builtin(args: Vec<Value>) -> BuiltinResult<Value> {
1452        block_on(super::containers_map_builtin(args))
1453    }
1454
1455    fn containers_map_keys(map: Value) -> BuiltinResult<Value> {
1456        block_on(super::containers_map_keys(map))
1457    }
1458
1459    fn containers_map_is_key(map: Value, key_spec: Value) -> BuiltinResult<Value> {
1460        block_on(super::containers_map_is_key(map, key_spec))
1461    }
1462
1463    fn containers_map_remove(map: Value, key_spec: Value) -> BuiltinResult<Value> {
1464        block_on(super::containers_map_remove(map, key_spec))
1465    }
1466
1467    fn containers_map_subsref(map: Value, kind: String, payload: Value) -> BuiltinResult<Value> {
1468        block_on(super::containers_map_subsref(map, kind, payload))
1469    }
1470
1471    fn containers_map_subsasgn(
1472        map: Value,
1473        kind: String,
1474        payload: Value,
1475        rhs: Value,
1476    ) -> BuiltinResult<Value> {
1477        block_on(super::containers_map_subsasgn(map, kind, payload, rhs))
1478    }
1479
1480    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1481    #[test]
1482    fn construct_empty_map_defaults() {
1483        let map = containers_map_builtin(Vec::new()).expect("map");
1484        let count = containers_map_subsref(
1485            map.clone(),
1486            ".".to_string(),
1487            Value::from("Count".to_string()),
1488        )
1489        .expect("Count");
1490        assert_eq!(count, Value::Num(0.0));
1491
1492        let key_type = containers_map_subsref(
1493            map.clone(),
1494            ".".to_string(),
1495            Value::from("KeyType".to_string()),
1496        )
1497        .expect("KeyType");
1498        assert_eq!(
1499            key_type,
1500            Value::CharArray(CharArray::new("char".chars().collect(), 1, 4).unwrap())
1501        );
1502
1503        let value_type = containers_map_subsref(
1504            map.clone(),
1505            ".".to_string(),
1506            Value::from("ValueType".to_string()),
1507        )
1508        .expect("ValueType");
1509        assert_eq!(
1510            value_type,
1511            Value::CharArray(CharArray::new("any".chars().collect(), 1, 3).unwrap())
1512        );
1513    }
1514
1515    #[test]
1516    fn map_type_resolvers_basics() {
1517        let ctx = ResolveContext::new(Vec::new());
1518        assert_eq!(map_handle_type(&[Type::Unknown], &ctx), Type::Unknown);
1519        assert_eq!(map_cell_type(&[], &ctx), Type::cell());
1520        assert_eq!(map_is_key_type(&[Type::String], &ctx), Type::logical());
1521        assert_eq!(map_unknown_type(&[], &ctx), Type::Unknown);
1522    }
1523
1524    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1525    #[test]
1526    fn constructor_with_cells_lookup() {
1527        let keys = crate::make_cell(vec![Value::from("apple"), Value::from("pear")], 1, 2).unwrap();
1528        let values = crate::make_cell(vec![Value::Num(5.0), Value::Num(7.0)], 1, 2).unwrap();
1529        let map = containers_map_builtin(vec![keys, values]).expect("map");
1530        let apple = containers_map_subsref(
1531            map.clone(),
1532            "()".to_string(),
1533            crate::make_cell(vec![Value::from("apple")], 1, 1).unwrap(),
1534        )
1535        .expect("lookup");
1536        assert_eq!(apple, Value::Num(5.0));
1537    }
1538
1539    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1540    #[test]
1541    fn constructor_rejects_duplicate_keys() {
1542        let keys = crate::make_cell(vec![Value::from("dup"), Value::from("dup")], 1, 2).unwrap();
1543        let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
1544        let err = containers_map_builtin(vec![keys, values]).expect_err("duplicate check");
1545        let message = error_message(err);
1546        assert!(message.contains("Duplicate key name"));
1547    }
1548
1549    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1550    #[test]
1551    fn constructor_errors_when_value_count_mismatch() {
1552        let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
1553        let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1554        let err = containers_map_builtin(vec![keys, values]).expect_err("count mismatch");
1555        let message = error_message(err);
1556        assert!(message.contains("number of keys"));
1557    }
1558
1559    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1560    #[test]
1561    fn comparison_method_rejects_unknown_values() {
1562        let keys = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
1563        let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1564        let err = containers_map_builtin(vec![
1565            keys,
1566            values,
1567            Value::from("ComparisonMethod"),
1568            Value::from("caseinsensitive"),
1569        ])
1570        .expect_err("comparison method");
1571        let message = error_message(err);
1572        assert!(message.contains("ComparisonMethod"));
1573    }
1574
1575    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1576    #[test]
1577    fn key_type_single_roundtrip() {
1578        let map = containers_map_builtin(vec![Value::from("KeyType"), Value::from("single")])
1579            .expect("map");
1580        let key_type = containers_map_subsref(map.clone(), ".".to_string(), Value::from("KeyType"))
1581            .expect("keytype");
1582        assert_eq!(
1583            key_type,
1584            Value::CharArray(CharArray::new("single".chars().collect(), 1, 6).unwrap())
1585        );
1586
1587        let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1588        let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(7.0))
1589            .expect("assign");
1590        let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
1591        assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
1592    }
1593
1594    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1595    #[test]
1596    fn value_type_double_converts_integers() {
1597        let keys = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1598        let values = crate::make_cell(vec![Value::Int(IntValue::I32(7))], 1, 1).unwrap();
1599        let map = containers_map_builtin(vec![
1600            keys,
1601            values,
1602            Value::from("KeyType"),
1603            Value::from("double"),
1604            Value::from("ValueType"),
1605            Value::from("double"),
1606        ])
1607        .expect("map");
1608        let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1609        let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
1610        assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
1611    }
1612
1613    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1614    #[test]
1615    fn value_type_logical_converts_numeric_arrays() {
1616        let keys = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
1617        let tensor = Tensor::new(vec![0.0, 2.0, -3.0], vec![3, 1]).unwrap();
1618        let values = crate::make_cell(vec![Value::Tensor(tensor.clone())], 1, 1).unwrap();
1619        let map = containers_map_builtin(vec![
1620            keys,
1621            values,
1622            Value::from("ValueType"),
1623            Value::from("logical"),
1624        ])
1625        .expect("map");
1626        let payload = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
1627        let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
1628        match value {
1629            Value::LogicalArray(arr) => {
1630                assert_eq!(arr.shape, vec![3, 1]);
1631                assert_eq!(arr.data, vec![0, 1, 1]);
1632            }
1633            other => panic!("expected logical array, got {:?}", other),
1634        }
1635    }
1636
1637    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1638    #[test]
1639    fn uniform_values_enforced_on_assignment() {
1640        let map = containers_map_builtin(vec![Value::from("UniformValues"), Value::from(true)])
1641            .expect("map");
1642        let payload = crate::make_cell(vec![Value::from("x")], 1, 1).unwrap();
1643        let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(1.0))
1644            .expect("assign");
1645        let err = containers_map_subsasgn(map, "()".to_string(), payload, Value::from("text"))
1646            .expect_err("uniform enforcement");
1647        let message = error_message(err);
1648        assert!(message.contains("UniformValues"));
1649    }
1650
1651    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1652    #[test]
1653    fn assignment_updates_and_inserts() {
1654        let map = containers_map_builtin(Vec::new()).expect("map");
1655        let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
1656        let updated = containers_map_subsasgn(
1657            map.clone(),
1658            "()".to_string(),
1659            payload.clone(),
1660            Value::Num(1.0),
1661        )
1662        .expect("assign");
1663        let updated = containers_map_subsasgn(
1664            updated.clone(),
1665            "()".to_string(),
1666            payload.clone(),
1667            Value::Num(5.0),
1668        )
1669        .expect("update");
1670        let beta_payload = crate::make_cell(vec![Value::from("beta")], 1, 1).unwrap();
1671        let updated = containers_map_subsasgn(
1672            updated.clone(),
1673            "()".to_string(),
1674            beta_payload,
1675            Value::Num(9.0),
1676        )
1677        .expect("insert");
1678        let value = containers_map_subsref(updated, "()".to_string(), payload).expect("lookup");
1679        assert_eq!(value, Value::Num(5.0));
1680    }
1681
1682    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1683    #[test]
1684    fn subsref_multiple_keys_preserves_shape() {
1685        let keys = crate::make_cell(
1686            vec![Value::from("a"), Value::from("b"), Value::from("c")],
1687            1,
1688            3,
1689        )
1690        .unwrap();
1691        let values = crate::make_cell(
1692            vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
1693            1,
1694            3,
1695        )
1696        .unwrap();
1697        let map = containers_map_builtin(vec![keys, values]).expect("map");
1698        let request = crate::make_cell(vec![Value::from("a"), Value::from("c")], 1, 2).unwrap();
1699        let payload = crate::make_cell(vec![request], 1, 1).unwrap();
1700        let result =
1701            containers_map_subsref(map.clone(), "()".to_string(), payload).expect("lookup");
1702        match result {
1703            Value::Cell(cell) => {
1704                assert_eq!(cell.rows, 1);
1705                assert_eq!(cell.cols, 2);
1706                assert_eq!(cell.get(0, 0).expect("cell 0,0"), Value::Num(1.0));
1707                assert_eq!(cell.get(0, 1).expect("cell 0,1"), Value::Num(3.0));
1708            }
1709            other => panic!("expected cell array, got {other:?}"),
1710        }
1711    }
1712
1713    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1714    #[test]
1715    fn subsref_empty_key_collection_returns_empty_cell() {
1716        let keys = crate::make_cell(vec![Value::from("z")], 1, 1).unwrap();
1717        let values = crate::make_cell(vec![Value::Num(42.0)], 1, 1).unwrap();
1718        let map = containers_map_builtin(vec![keys, values]).expect("map");
1719        let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
1720        let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
1721        let result = containers_map_subsref(map, "()".to_string(), payload).expect("lookup empty");
1722        match result {
1723            Value::Cell(cell) => {
1724                assert_eq!(cell.rows, 1);
1725                assert_eq!(cell.cols, 0);
1726                assert!(cell.data.is_empty());
1727            }
1728            other => panic!("expected empty cell, got {other:?}"),
1729        }
1730    }
1731
1732    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1733    #[test]
1734    fn subsasgn_with_cell_keys_updates_all_targets() {
1735        let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
1736        let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
1737        let map = containers_map_builtin(vec![keys, values]).expect("map");
1738        let key_spec = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
1739        let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
1740        let new_values = crate::make_cell(vec![Value::Num(10.0), Value::Num(20.0)], 1, 2).unwrap();
1741        let updated = containers_map_subsasgn(map.clone(), "()".to_string(), payload, new_values)
1742            .expect("assign");
1743        let a_payload = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
1744        let b_payload = crate::make_cell(vec![Value::from("b")], 1, 1).unwrap();
1745        let a_value =
1746            containers_map_subsref(updated.clone(), "()".to_string(), a_payload).expect("a lookup");
1747        let b_value =
1748            containers_map_subsref(updated, "()".to_string(), b_payload).expect("b lookup");
1749        assert_eq!(a_value, Value::Num(10.0));
1750        assert_eq!(b_value, Value::Num(20.0));
1751    }
1752
1753    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1754    #[test]
1755    fn assignment_value_count_mismatch_errors() {
1756        let keys = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
1757        let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
1758        let map = containers_map_builtin(vec![keys, values]).expect("map");
1759        let key_spec = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
1760        let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
1761        let rhs = crate::make_cell(vec![Value::Num(99.0)], 1, 1).unwrap();
1762        let err =
1763            containers_map_subsasgn(map, "()".to_string(), payload, rhs).expect_err("value count");
1764        let message = error_message(err);
1765        assert!(message.contains("requires 2 values"));
1766    }
1767
1768    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1769    #[test]
1770    fn subsasgn_empty_key_collection_is_noop() {
1771        let keys = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
1772        let values = crate::make_cell(vec![Value::Num(7.0)], 1, 1).unwrap();
1773        let map = containers_map_builtin(vec![keys, values]).expect("map");
1774        let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
1775        let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
1776        let rhs = crate::make_cell(Vec::new(), 1, 0).unwrap();
1777        let updated =
1778            containers_map_subsasgn(map.clone(), "()".to_string(), payload, rhs).expect("assign");
1779        let lookup_payload = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
1780        let value =
1781            containers_map_subsref(updated, "()".to_string(), lookup_payload).expect("lookup");
1782        assert_eq!(value, Value::Num(7.0));
1783    }
1784
1785    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1786    #[test]
1787    fn keys_values_iskey_remove() {
1788        let keys = crate::make_cell(
1789            vec![Value::from("a"), Value::from("b"), Value::from("c")],
1790            1,
1791            3,
1792        )
1793        .unwrap();
1794        let values = crate::make_cell(
1795            vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
1796            1,
1797            3,
1798        )
1799        .unwrap();
1800        let map = containers_map_builtin(vec![keys, values]).expect("map");
1801        let key_list = containers_map_keys(map.clone()).expect("keys");
1802        match key_list {
1803            Value::Cell(cell) => assert_eq!(cell.data.len(), 3),
1804            other => panic!("expected cell array, got {other:?}"),
1805        }
1806        let mask = containers_map_is_key(
1807            map.clone(),
1808            crate::make_cell(vec![Value::from("a"), Value::from("z")], 1, 2).unwrap(),
1809        )
1810        .expect("mask");
1811        match mask {
1812            Value::LogicalArray(arr) => {
1813                assert_eq!(arr.data, vec![1, 0]);
1814            }
1815            other => panic!("expected logical array, got {:?}", other),
1816        }
1817        let removed = containers_map_remove(
1818            map.clone(),
1819            crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
1820        )
1821        .expect("remove");
1822        let mask = containers_map_is_key(
1823            removed,
1824            crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
1825        )
1826        .expect("mask");
1827        assert_eq!(mask, Value::Bool(false));
1828    }
1829
1830    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1831    #[test]
1832    fn remove_missing_key_returns_error() {
1833        let keys = crate::make_cell(vec![Value::from("key")], 1, 1).unwrap();
1834        let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
1835        let map = containers_map_builtin(vec![keys, values]).expect("map");
1836        let err = containers_map_remove(
1837            map,
1838            crate::make_cell(vec![Value::from("missing")], 1, 1).unwrap(),
1839        )
1840        .expect_err("remove missing");
1841        let message = error_message(err);
1842        assert_eq!(message, MISSING_KEY_ERR);
1843    }
1844
1845    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1846    #[test]
1847    fn length_delegates_to_map_count() {
1848        let keys = crate::make_cell(
1849            vec![Value::from("a"), Value::from("b"), Value::from("c")],
1850            1,
1851            3,
1852        )
1853        .unwrap();
1854        let values = crate::make_cell(
1855            vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
1856            1,
1857            3,
1858        )
1859        .unwrap();
1860        let map = containers_map_builtin(vec![keys, values]).expect("map");
1861        assert_eq!(map_length(&map), Some(3));
1862    }
1863
1864    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1865    #[test]
1866    fn map_constructor_gathers_gpu_values() {
1867        test_support::with_test_provider(|provider| {
1868            let keys = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
1869            let data = vec![1.0, 2.0, 3.0];
1870            let shape = vec![3, 1];
1871            let view = runmat_accelerate_api::HostTensorView {
1872                data: &data,
1873                shape: &shape,
1874            };
1875            let handle = provider.upload(&view).expect("upload");
1876            let values = crate::make_cell(vec![Value::GpuTensor(handle)], 1, 1).unwrap();
1877            let map = containers_map_builtin(vec![keys, values]).expect("map");
1878            let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
1879            let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
1880            match value {
1881                Value::Tensor(t) => {
1882                    assert_eq!(t.shape, shape);
1883                    assert_eq!(t.data, data);
1884                }
1885                other => panic!("expected tensor, got {:?}", other),
1886            }
1887        });
1888    }
1889}