1use runmat_types::MemberAccess;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::{
8 atomic::{AtomicBool, Ordering as AtomicOrdering},
9 Arc,
10};
11
12use runmat_builtins::{
13 BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinExtensionDescriptor,
14 BuiltinExtensionMode, BuiltinIntegerBackendRule, BuiltinIntegerCapabilityDescriptor,
15 BuiltinIntegerClass, BuiltinIntegerComputationDomain, BuiltinIntegerInputAvailability,
16 BuiltinIntegerInputCapability, BuiltinIntegerOutputClassRule, BuiltinIntegerOverflowRule,
17 BuiltinIntegerOverloadKind, BuiltinIntegerScalarDoubleRule, BuiltinOutputMode,
18 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
19};
20use runmat_gc::{GcHandle, GcRoot, RootId, Trace, Tracer};
21use runmat_macros::runtime_builtin;
22use runmat_value::{
23 CharArray, HandleRef, IntValue, IntegerStorage, LogicalArray, NumericDType, ObjectInstance,
24 StructValue, Tensor, Value,
25};
26
27use crate::builtins::common::random_args::keyword_of;
28use crate::builtins::common::spec::{
29 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
30 ReductionNaN, ResidencyPolicy, ShapeRequirements,
31};
32use crate::builtins::common::tensor;
33use crate::builtins::containers::type_resolvers::{
34 map_cell_type, map_handle_type, map_is_key_type, map_unknown_type,
35};
36use crate::{
37 build_runtime_error, gather_if_needed_async, BuiltinResult, RuntimeError, OBJECT_INDEX_BRACE,
38 OBJECT_INDEX_MEMBER, OBJECT_INDEX_PAREN, OBJECT_SUBSASGN_METHOD, OBJECT_SUBSREF_METHOD,
39};
40
41const CLASS_NAME: &str = "containers.Map";
42const BUILTIN_CONSTRUCTOR: &str = "containers.Map";
43const BUILTIN_KEYS: &str = "containers.Map.keys";
44const BUILTIN_VALUES: &str = "containers.Map.values";
45const BUILTIN_IS_KEY: &str = "containers.Map.isKey";
46const BUILTIN_REMOVE: &str = "containers.Map.remove";
47const BUILTIN_SUBSREF: &str = "containers.Map.subsref";
48const BUILTIN_SUBSASGN: &str = "containers.Map.subsasgn";
49
50fn contains_resident_value(value: &Value) -> bool {
51 match value {
52 Value::GpuTensor(_) => true,
53 Value::Cell(cell) => cell.data.iter().any(contains_resident_value),
54 Value::OutputList(values) => values.iter().any(contains_resident_value),
55 _ => false,
56 }
57}
58
59fn ensure_resident_extension(
60 value: &Value,
61 extension: &BuiltinExtensionDescriptor,
62 builtin: &'static str,
63) -> BuiltinResult<()> {
64 if contains_resident_value(value) {
65 crate::compatibility::ensure_builtin_extension_enabled(extension, builtin)?;
66 }
67 Ok(())
68}
69
70const CONTAINERS_MAP_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
71 name: "M",
72 ty: BuiltinParamType::Any,
73 arity: BuiltinParamArity::Required,
74 default: None,
75 description: "containers.Map handle object.",
76}];
77
78const CONTAINERS_MAP_INPUTS_KEYS_VALUES: [BuiltinParamDescriptor; 2] = [
79 BuiltinParamDescriptor {
80 name: "keys",
81 ty: BuiltinParamType::Any,
82 arity: BuiltinParamArity::Required,
83 default: None,
84 description: "Key container (cell, string/char, or numeric vector).",
85 },
86 BuiltinParamDescriptor {
87 name: "values",
88 ty: BuiltinParamType::Any,
89 arity: BuiltinParamArity::Required,
90 default: None,
91 description: "Value container aligned with keys.",
92 },
93];
94
95const CONTAINERS_MAP_INPUTS_KEYS_VALUES_OPTS: [BuiltinParamDescriptor; 4] = [
96 BuiltinParamDescriptor {
97 name: "keys",
98 ty: BuiltinParamType::Any,
99 arity: BuiltinParamArity::Required,
100 default: None,
101 description: "Key container (cell, string/char, or numeric vector).",
102 },
103 BuiltinParamDescriptor {
104 name: "values",
105 ty: BuiltinParamType::Any,
106 arity: BuiltinParamArity::Required,
107 default: None,
108 description: "Value container aligned with keys.",
109 },
110 BuiltinParamDescriptor {
111 name: "UniformValues",
112 ty: BuiltinParamType::StringScalar,
113 arity: BuiltinParamArity::Required,
114 default: None,
115 description: "Literal UniformValues option name.",
116 },
117 BuiltinParamDescriptor {
118 name: "isUniform",
119 ty: BuiltinParamType::LogicalArray,
120 arity: BuiltinParamArity::Required,
121 default: None,
122 description: "Logical scalar selecting uniform-value validation.",
123 },
124];
125
126const CONTAINERS_MAP_INPUTS_OPTIONS_ONLY: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
127 name: "options",
128 ty: BuiltinParamType::Any,
129 arity: BuiltinParamArity::Variadic,
130 default: None,
131 description: "The required KeyType and ValueType name/value pairs, in either order.",
132}];
133
134const CONTAINERS_MAP_SIGNATURES: [BuiltinSignatureDescriptor; 4] = [
135 BuiltinSignatureDescriptor {
136 label: "M = containers.Map()",
137 inputs: &[],
138 outputs: &CONTAINERS_MAP_OUTPUT,
139 },
140 BuiltinSignatureDescriptor {
141 label: "M = containers.Map(keys, values)",
142 inputs: &CONTAINERS_MAP_INPUTS_KEYS_VALUES,
143 outputs: &CONTAINERS_MAP_OUTPUT,
144 },
145 BuiltinSignatureDescriptor {
146 label: "M = containers.Map(keys, values, 'UniformValues', isUniform)",
147 inputs: &CONTAINERS_MAP_INPUTS_KEYS_VALUES_OPTS,
148 outputs: &CONTAINERS_MAP_OUTPUT,
149 },
150 BuiltinSignatureDescriptor {
151 label: "M = containers.Map('KeyType', kType, 'ValueType', vType)",
152 inputs: &CONTAINERS_MAP_INPUTS_OPTIONS_ONLY,
153 outputs: &CONTAINERS_MAP_OUTPUT,
154 },
155];
156
157const CONTAINERS_MAP_METHOD_INPUT_MAP: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
158 name: "M",
159 ty: BuiltinParamType::Any,
160 arity: BuiltinParamArity::Required,
161 default: None,
162 description: "containers.Map handle object.",
163}];
164
165const CONTAINERS_MAP_KEYS_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
166 name: "K",
167 ty: BuiltinParamType::Any,
168 arity: BuiltinParamArity::Required,
169 default: None,
170 description: "Row cell array containing map keys.",
171}];
172
173const CONTAINERS_MAP_VALUES_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
174 name: "V",
175 ty: BuiltinParamType::Any,
176 arity: BuiltinParamArity::Required,
177 default: None,
178 description: "Row cell array containing map values.",
179}];
180const CONTAINERS_MAP_OUTPUTS_NONE: [BuiltinParamDescriptor; 0] = [];
181
182const CONTAINERS_MAP_INPUTS_KEY_SPEC: [BuiltinParamDescriptor; 2] = [
183 BuiltinParamDescriptor {
184 name: "M",
185 ty: BuiltinParamType::Any,
186 arity: BuiltinParamArity::Required,
187 default: None,
188 description: "containers.Map handle object.",
189 },
190 BuiltinParamDescriptor {
191 name: "keySet",
192 ty: BuiltinParamType::Any,
193 arity: BuiltinParamArity::Required,
194 default: None,
195 description: "Key scalar or key collection to query/mutate.",
196 },
197];
198
199const CONTAINERS_MAP_ISKEY_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
200 name: "tf",
201 ty: BuiltinParamType::LogicalArray,
202 arity: BuiltinParamArity::Required,
203 default: None,
204 description: "Logical membership result for each key.",
205}];
206
207const CONTAINERS_MAP_INPUTS_SUBSREF: [BuiltinParamDescriptor; 3] = [
208 BuiltinParamDescriptor {
209 name: "M",
210 ty: BuiltinParamType::Any,
211 arity: BuiltinParamArity::Required,
212 default: None,
213 description: "containers.Map handle object.",
214 },
215 BuiltinParamDescriptor {
216 name: "kind",
217 ty: BuiltinParamType::StringScalar,
218 arity: BuiltinParamArity::Required,
219 default: None,
220 description: "Indexing kind: (), ., or {}.",
221 },
222 BuiltinParamDescriptor {
223 name: "payload",
224 ty: BuiltinParamType::Any,
225 arity: BuiltinParamArity::Required,
226 default: None,
227 description: "Indexing payload cell/property argument.",
228 },
229];
230
231const CONTAINERS_MAP_INPUTS_SUBSASGN: [BuiltinParamDescriptor; 4] = [
232 BuiltinParamDescriptor {
233 name: "M",
234 ty: BuiltinParamType::Any,
235 arity: BuiltinParamArity::Required,
236 default: None,
237 description: "containers.Map handle object.",
238 },
239 BuiltinParamDescriptor {
240 name: "kind",
241 ty: BuiltinParamType::StringScalar,
242 arity: BuiltinParamArity::Required,
243 default: None,
244 description: "Assignment kind: (), ., or {}.",
245 },
246 BuiltinParamDescriptor {
247 name: "payload",
248 ty: BuiltinParamType::Any,
249 arity: BuiltinParamArity::Required,
250 default: None,
251 description: "Assignment payload cell/property argument.",
252 },
253 BuiltinParamDescriptor {
254 name: "rhs",
255 ty: BuiltinParamType::Any,
256 arity: BuiltinParamArity::Required,
257 default: None,
258 description: "Assigned value (scalar or collection).",
259 },
260];
261
262const CONTAINERS_MAP_SUBSREF_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
263 name: "value",
264 ty: BuiltinParamType::Any,
265 arity: BuiltinParamArity::Required,
266 default: None,
267 description: "Lookup/property value result.",
268}];
269
270const CONTAINERS_MAP_KEYS_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
271 [BuiltinSignatureDescriptor {
272 label: "K = containers.Map.keys(M)",
273 inputs: &CONTAINERS_MAP_METHOD_INPUT_MAP,
274 outputs: &CONTAINERS_MAP_KEYS_OUTPUT,
275 }];
276
277const CONTAINERS_MAP_VALUES_SIGNATURES: [BuiltinSignatureDescriptor; 2] = [
278 BuiltinSignatureDescriptor {
279 label: "V = containers.Map.values(M)",
280 inputs: &CONTAINERS_MAP_METHOD_INPUT_MAP,
281 outputs: &CONTAINERS_MAP_VALUES_OUTPUT,
282 },
283 BuiltinSignatureDescriptor {
284 label: "V = containers.Map.values(M, keySet)",
285 inputs: &CONTAINERS_MAP_INPUTS_KEY_SPEC,
286 outputs: &CONTAINERS_MAP_VALUES_OUTPUT,
287 },
288];
289
290const CONTAINERS_MAP_ISKEY_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
291 [BuiltinSignatureDescriptor {
292 label: "tf = containers.Map.isKey(M, keySet)",
293 inputs: &CONTAINERS_MAP_INPUTS_KEY_SPEC,
294 outputs: &CONTAINERS_MAP_ISKEY_OUTPUT,
295 }];
296
297const CONTAINERS_MAP_REMOVE_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
298 [BuiltinSignatureDescriptor {
299 label: "containers.Map.remove(M, keySet)",
300 inputs: &CONTAINERS_MAP_INPUTS_KEY_SPEC,
301 outputs: &CONTAINERS_MAP_OUTPUTS_NONE,
302 }];
303
304const CONTAINERS_MAP_SUBSREF_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
305 [BuiltinSignatureDescriptor {
306 label: "value = containers.Map.subsref(M, kind, payload)",
307 inputs: &CONTAINERS_MAP_INPUTS_SUBSREF,
308 outputs: &CONTAINERS_MAP_SUBSREF_OUTPUT,
309 }];
310
311const CONTAINERS_MAP_SUBSASGN_SIGNATURES: [BuiltinSignatureDescriptor; 1] =
312 [BuiltinSignatureDescriptor {
313 label: "M = containers.Map.subsasgn(M, kind, payload, rhs)",
314 inputs: &CONTAINERS_MAP_INPUTS_SUBSASGN,
315 outputs: &CONTAINERS_MAP_OUTPUT,
316 }];
317
318const CONTAINERS_MAP_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
319 code: "RM.CONTAINERS_MAP.INVALID_ARGUMENT",
320 identifier: Some("RunMat:containers.Map:InvalidArgument"),
321 when: "Map constructor/method inputs, option grammar, or key/value payloads are invalid.",
322 message: "containers.Map: invalid argument",
323};
324
325const CONTAINERS_MAP_ERROR_MISSING_KEY: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
326 code: "RM.CONTAINERS_MAP.MISSING_KEY",
327 identifier: Some("RunMat:containers.Map:MissingKey"),
328 when: "Lookup/removal targets a key that is not present in the map.",
329 message: "containers.Map: The specified key is not present in this container.",
330};
331
332const CONTAINERS_MAP_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
333 code: "RM.CONTAINERS_MAP.INTERNAL",
334 identifier: Some("RunMat:containers.Map:Internal"),
335 when: "Map registry/storage operations fail unexpectedly.",
336 message: "containers.Map: internal operation failed",
337};
338
339const CONTAINERS_MAP_ERRORS: [BuiltinErrorDescriptor; 3] = [
340 CONTAINERS_MAP_ERROR_INVALID_ARGUMENT,
341 CONTAINERS_MAP_ERROR_MISSING_KEY,
342 CONTAINERS_MAP_ERROR_INTERNAL,
343];
344
345pub const CONTAINERS_MAP_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
346 signatures: &CONTAINERS_MAP_SIGNATURES,
347 output_mode: BuiltinOutputMode::Fixed,
348 completion_policy: BuiltinCompletionPolicy::Public,
349 errors: &CONTAINERS_MAP_ERRORS,
350};
351
352pub const CONTAINERS_MAP_KEYS_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
353 signatures: &CONTAINERS_MAP_KEYS_SIGNATURES,
354 output_mode: BuiltinOutputMode::Fixed,
355 completion_policy: BuiltinCompletionPolicy::Public,
356 errors: &CONTAINERS_MAP_ERRORS,
357};
358
359pub const CONTAINERS_MAP_VALUES_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
360 signatures: &CONTAINERS_MAP_VALUES_SIGNATURES,
361 output_mode: BuiltinOutputMode::Fixed,
362 completion_policy: BuiltinCompletionPolicy::Public,
363 errors: &CONTAINERS_MAP_ERRORS,
364};
365
366pub const CONTAINERS_MAP_ISKEY_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
367 signatures: &CONTAINERS_MAP_ISKEY_SIGNATURES,
368 output_mode: BuiltinOutputMode::Fixed,
369 completion_policy: BuiltinCompletionPolicy::Public,
370 errors: &CONTAINERS_MAP_ERRORS,
371};
372
373pub const CONTAINERS_MAP_REMOVE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
374 signatures: &CONTAINERS_MAP_REMOVE_SIGNATURES,
375 output_mode: BuiltinOutputMode::Fixed,
376 completion_policy: BuiltinCompletionPolicy::Public,
377 errors: &CONTAINERS_MAP_ERRORS,
378};
379
380pub const CONTAINERS_MAP_SUBSREF_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
381 signatures: &CONTAINERS_MAP_SUBSREF_SIGNATURES,
382 output_mode: BuiltinOutputMode::Fixed,
383 completion_policy: BuiltinCompletionPolicy::Public,
384 errors: &CONTAINERS_MAP_ERRORS,
385};
386
387pub const CONTAINERS_MAP_SUBSASGN_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
388 signatures: &CONTAINERS_MAP_SUBSASGN_SIGNATURES,
389 output_mode: BuiltinOutputMode::Fixed,
390 completion_policy: BuiltinCompletionPolicy::Public,
391 errors: &CONTAINERS_MAP_ERRORS,
392};
393
394const MAP_KEY_INTEGER_CLASSES: [BuiltinIntegerClass; 4] = [
395 BuiltinIntegerClass::Int32,
396 BuiltinIntegerClass::Uint32,
397 BuiltinIntegerClass::Int64,
398 BuiltinIntegerClass::Uint64,
399];
400const MAP_KEY_INPUT: [BuiltinIntegerInputCapability; 1] = [BuiltinIntegerInputCapability {
401 name: "keySet",
402 classes: &MAP_KEY_INTEGER_CLASSES,
403 availability: BuiltinIntegerInputAvailability::Documented,
404 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
405 notes: "Public numeric Map keys are scalar single/double/int32/uint32/int64/uint64; these four integer key classes retain exact native identity.",
406}];
407const MAP_VALUE_INPUT: [BuiltinIntegerInputCapability; 1] = [BuiltinIntegerInputCapability {
408 name: "valueSet",
409 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
410 availability: BuiltinIntegerInputAvailability::Documented,
411 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
412 notes: "Every integer class is a documented ValueType and retains its exact class and payload.",
413}];
414const MAP_STORED_KEY_INPUT: [BuiltinIntegerInputCapability; 1] = [BuiltinIntegerInputCapability {
415 name: "stored keys",
416 classes: &MAP_KEY_INTEGER_CLASSES,
417 availability: BuiltinIntegerInputAvailability::Documented,
418 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
419 notes: "Integer keys are returned in their declared key class inside the output cell array.",
420}];
421const MAP_STORED_VALUE_INPUT: [BuiltinIntegerInputCapability; 1] =
422 [BuiltinIntegerInputCapability {
423 name: "stored values",
424 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
425 availability: BuiltinIntegerInputAvailability::Documented,
426 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
427 notes: "Integer values pass through the host Map store and output cells with exact class and value.",
428 }];
429const MAP_ASSIGN_INPUTS: [BuiltinIntegerInputCapability; 2] = [
430 BuiltinIntegerInputCapability {
431 name: "keySet",
432 classes: &MAP_KEY_INTEGER_CLASSES,
433 availability: BuiltinIntegerInputAvailability::Documented,
434 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
435 notes: "Public integer Map keys are int32/uint32/int64/uint64 and retain exact native identity.",
436 },
437 BuiltinIntegerInputCapability {
438 name: "rhs",
439 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
440 availability: BuiltinIntegerInputAvailability::Documented,
441 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
442 notes: "All integer rhs classes are exact for ValueType='any' and their matching declared integer ValueType.",
443 },
444];
445const MAP_RESIDENT_KEY_INPUT: [BuiltinIntegerInputCapability; 1] =
446 [BuiltinIntegerInputCapability {
447 name: "resident keySet",
448 classes: &MAP_KEY_INTEGER_CLASSES,
449 availability: BuiltinIntegerInputAvailability::RunMatOnly,
450 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
451 notes: "Resident int32/uint32/int64/uint64 keys are a gated RunMat extension and gather before host Map lookup.",
452 }];
453const MAP_RESIDENT_VALUE_INPUT: [BuiltinIntegerInputCapability; 1] =
454 [BuiltinIntegerInputCapability {
455 name: "resident valueSet or rhs",
456 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
457 availability: BuiltinIntegerInputAvailability::RunMatOnly,
458 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
459 notes: "Resident integer values are a gated RunMat extension and gather before exact host storage or declared ValueType conversion.",
460 }];
461const MAP_RESIDENT_ASSIGN_INPUTS: [BuiltinIntegerInputCapability; 2] = [
462 BuiltinIntegerInputCapability {
463 name: "resident keySet",
464 classes: &MAP_KEY_INTEGER_CLASSES,
465 availability: BuiltinIntegerInputAvailability::RunMatOnly,
466 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
467 notes: "Resident integer keys are gated and gathered before host key normalization.",
468 },
469 BuiltinIntegerInputCapability {
470 name: "resident rhs",
471 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
472 availability: BuiltinIntegerInputAvailability::RunMatOnly,
473 scalar_double: BuiltinIntegerScalarDoubleRule::NotApplicable,
474 notes:
475 "Resident integer rhs values are gated and gathered before host ValueType conversion.",
476 },
477];
478
479pub const CONTAINERS_MAP_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 4] = [
480 BuiltinIntegerCapabilityDescriptor {
481 form: "M = containers.Map(integer_keySet, valueSet)",
482 inputs: &MAP_KEY_INPUT,
483 computation_domain: BuiltinIntegerComputationDomain::Structural,
484 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
485 overflow: BuiltinIntegerOverflowRule::Error,
486 backend: BuiltinIntegerBackendRule::HostOnly,
487 overload: BuiltinIntegerOverloadKind::StructuralParameter,
488 notes: "KeyType is inferred exactly for int32/uint32/int64/uint64; unsupported int8/uint8/int16/uint16 key arrays reject rather than alias through double.",
489 },
490 BuiltinIntegerCapabilityDescriptor {
491 form: "M = containers.Map(keySet, integer_valueSet)",
492 inputs: &MAP_VALUE_INPUT,
493 computation_domain: BuiltinIntegerComputationDomain::Structural,
494 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
495 overflow: BuiltinIntegerOverflowRule::Saturate,
496 backend: BuiltinIntegerBackendRule::HostOnly,
497 overload: BuiltinIntegerOverloadKind::StructuralParameter,
498 notes: "Uniform constructor values infer their exact integer ValueType; ValueType='any' and explicit integer ValueType preserve or deliberately cast native storage.",
499 },
500 BuiltinIntegerCapabilityDescriptor {
501 form: "M = containers.Map(resident_integer_keySet, valueSet)",
502 inputs: &MAP_RESIDENT_KEY_INPUT,
503 computation_domain: BuiltinIntegerComputationDomain::Structural,
504 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
505 overflow: BuiltinIntegerOverflowRule::Error,
506 backend: BuiltinIntegerBackendRule::GatherFallback,
507 overload: BuiltinIntegerOverloadKind::StructuralParameter,
508 notes: "RunMat-only resident keys gather after compatibility admission and before host key-type inference and exact identity storage.",
509 },
510 BuiltinIntegerCapabilityDescriptor {
511 form: "M = containers.Map(keySet, resident_integer_valueSet)",
512 inputs: &MAP_RESIDENT_VALUE_INPUT,
513 computation_domain: BuiltinIntegerComputationDomain::Structural,
514 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
515 overflow: BuiltinIntegerOverflowRule::Saturate,
516 backend: BuiltinIntegerBackendRule::GatherFallback,
517 overload: BuiltinIntegerOverloadKind::StructuralParameter,
518 notes: "RunMat-only resident values gather after compatibility admission; the resulting Map and all outputs remain host-resident.",
519 },
520];
521pub const CONTAINERS_MAP_KEYS_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 1] =
522 [BuiltinIntegerCapabilityDescriptor {
523 form: "K = containers.Map.keys(M_with_integer_keys)",
524 inputs: &MAP_STORED_KEY_INPUT,
525 computation_domain: BuiltinIntegerComputationDomain::Structural,
526 output_class: BuiltinIntegerOutputClassRule::PreserveInput,
527 overflow: BuiltinIntegerOverflowRule::NotApplicable,
528 backend: BuiltinIntegerBackendRule::HostOnly,
529 overload: BuiltinIntegerOverloadKind::StructuralParameter,
530 notes: "The row cell contains exact typed scalar keys; the Map itself is host-resident.",
531 }];
532pub const CONTAINERS_MAP_VALUES_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 2] = [
533 BuiltinIntegerCapabilityDescriptor {
534 form: "V = containers.Map.values(M_with_integer_values, keySet?)",
535 inputs: &MAP_STORED_VALUE_INPUT,
536 computation_domain: BuiltinIntegerComputationDomain::Structural,
537 output_class: BuiltinIntegerOutputClassRule::PreserveInput,
538 overflow: BuiltinIntegerOverflowRule::NotApplicable,
539 backend: BuiltinIntegerBackendRule::HostOnly,
540 overload: BuiltinIntegerOverloadKind::StructuralParameter,
541 notes: "All-values output is a row cell; selected-values output matches keySet cell shape and preserves each stored integer class.",
542 },
543 BuiltinIntegerCapabilityDescriptor {
544 form: "V = containers.Map.values(M, resident_integer_keySet)",
545 inputs: &MAP_RESIDENT_KEY_INPUT,
546 computation_domain: BuiltinIntegerComputationDomain::Structural,
547 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
548 overflow: BuiltinIntegerOverflowRule::Error,
549 backend: BuiltinIntegerBackendRule::GatherFallback,
550 overload: BuiltinIntegerOverloadKind::ElementwiseShapePreserving,
551 notes: "RunMat-only resident selected keys gather after compatibility admission; the host cell output preserves keySet shape.",
552 },
553];
554pub const CONTAINERS_MAP_ISKEY_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 2] = [
555 BuiltinIntegerCapabilityDescriptor {
556 form: "tf = containers.Map.isKey(M, integer_keySet)",
557 inputs: &MAP_KEY_INPUT,
558 computation_domain: BuiltinIntegerComputationDomain::Structural,
559 output_class: BuiltinIntegerOutputClassRule::Logical,
560 overflow: BuiltinIntegerOverflowRule::Error,
561 backend: BuiltinIntegerBackendRule::HostOnly,
562 overload: BuiltinIntegerOverloadKind::ElementwiseShapePreserving,
563 notes: "Scalar input returns scalar logical; a cell keySet returns a same-shape logical array with exact wide-key identity.",
564 },
565 BuiltinIntegerCapabilityDescriptor {
566 form: "tf = containers.Map.isKey(M, resident_integer_keySet)",
567 inputs: &MAP_RESIDENT_KEY_INPUT,
568 computation_domain: BuiltinIntegerComputationDomain::Structural,
569 output_class: BuiltinIntegerOutputClassRule::Logical,
570 overflow: BuiltinIntegerOverflowRule::Error,
571 backend: BuiltinIntegerBackendRule::GatherFallback,
572 overload: BuiltinIntegerOverloadKind::ElementwiseShapePreserving,
573 notes: "RunMat-only resident keys gather after compatibility admission; the logical result is host-resident and shape preserving.",
574 },
575];
576pub const CONTAINERS_MAP_REMOVE_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 2] = [
577 BuiltinIntegerCapabilityDescriptor {
578 form: "containers.Map.remove(M, integer_keySet)",
579 inputs: &MAP_KEY_INPUT,
580 computation_domain: BuiltinIntegerComputationDomain::Structural,
581 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
582 overflow: BuiltinIntegerOverflowRule::Error,
583 backend: BuiltinIntegerBackendRule::HostOnly,
584 overload: BuiltinIntegerOverloadKind::StructuralParameter,
585 notes: "Removal mutates the host handle object and performs exact native key lookup.",
586 },
587 BuiltinIntegerCapabilityDescriptor {
588 form: "containers.Map.remove(M, resident_integer_keySet)",
589 inputs: &MAP_RESIDENT_KEY_INPUT,
590 computation_domain: BuiltinIntegerComputationDomain::Structural,
591 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
592 overflow: BuiltinIntegerOverflowRule::Error,
593 backend: BuiltinIntegerBackendRule::GatherFallback,
594 overload: BuiltinIntegerOverloadKind::StructuralParameter,
595 notes: "RunMat-only resident keys gather after compatibility admission before the host handle is mutated.",
596 },
597];
598pub const CONTAINERS_MAP_SUBSREF_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 4] = [
599 BuiltinIntegerCapabilityDescriptor {
600 form: "count = containers.Map.subsref(M, '.', 'Count')",
601 inputs: &[],
602 computation_domain: BuiltinIntegerComputationDomain::Structural,
603 output_class: BuiltinIntegerOutputClassRule::FunctionSpecific,
604 overflow: BuiltinIntegerOverflowRule::NotApplicable,
605 backend: BuiltinIntegerBackendRule::HostOnly,
606 overload: BuiltinIntegerOverloadKind::ScalarOnly,
607 notes: "The read-only Count property is a scalar uint64 and is derived from the host Map entry count without floating conversion.",
608 },
609 BuiltinIntegerCapabilityDescriptor {
610 form: "value = containers.Map.subsref(M, '()', integer_key)",
611 inputs: &MAP_KEY_INPUT,
612 computation_domain: BuiltinIntegerComputationDomain::Structural,
613 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
614 overflow: BuiltinIntegerOverflowRule::Error,
615 backend: BuiltinIntegerBackendRule::HostOnly,
616 overload: BuiltinIntegerOverloadKind::StructuralParameter,
617 notes: "The integer controls lookup only; output class is the stored value class.",
618 },
619 BuiltinIntegerCapabilityDescriptor {
620 form: "integer_value = containers.Map.subsref(M_with_integer_value, '()', key)",
621 inputs: &MAP_STORED_VALUE_INPUT,
622 computation_domain: BuiltinIntegerComputationDomain::Structural,
623 output_class: BuiltinIntegerOutputClassRule::PreserveInput,
624 overflow: BuiltinIntegerOverflowRule::NotApplicable,
625 backend: BuiltinIntegerBackendRule::HostOnly,
626 overload: BuiltinIntegerOverloadKind::StructuralParameter,
627 notes:
628 "Lookup returns the exact stored integer scalar or array without floating conversion.",
629 },
630 BuiltinIntegerCapabilityDescriptor {
631 form: "value = containers.Map.subsref(M, '()', resident_integer_key)",
632 inputs: &MAP_RESIDENT_KEY_INPUT,
633 computation_domain: BuiltinIntegerComputationDomain::Structural,
634 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
635 overflow: BuiltinIntegerOverflowRule::Error,
636 backend: BuiltinIntegerBackendRule::GatherFallback,
637 overload: BuiltinIntegerOverloadKind::StructuralParameter,
638 notes: "RunMat-only resident keys gather after compatibility admission; lookup returns the host-stored value.",
639 },
640];
641pub const CONTAINERS_MAP_SUBSASGN_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 2] = [
642 BuiltinIntegerCapabilityDescriptor {
643 form: "containers.Map.subsasgn(M, '()', integer_key, integer_rhs)",
644 inputs: &MAP_ASSIGN_INPUTS,
645 computation_domain: BuiltinIntegerComputationDomain::Structural,
646 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
647 overflow: BuiltinIntegerOverflowRule::Saturate,
648 backend: BuiltinIntegerBackendRule::HostOnly,
649 overload: BuiltinIntegerOverloadKind::StructuralParameter,
650 notes: "Exact key identity is independent of exact rhs storage; a declared integer ValueType applies MATLAB integer conversion before storage.",
651 },
652 BuiltinIntegerCapabilityDescriptor {
653 form: "containers.Map.subsasgn(M, '()', resident_integer_key, resident_integer_rhs)",
654 inputs: &MAP_RESIDENT_ASSIGN_INPUTS,
655 computation_domain: BuiltinIntegerComputationDomain::Structural,
656 output_class: BuiltinIntegerOutputClassRule::NotApplicable,
657 overflow: BuiltinIntegerOverflowRule::Saturate,
658 backend: BuiltinIntegerBackendRule::GatherFallback,
659 overload: BuiltinIntegerOverloadKind::StructuralParameter,
660 notes: "RunMat-only resident assignment data gathers after compatibility admission and before host key normalization and rhs conversion.",
661 },
662];
663
664const MAP_RESIDENT_CONSTRUCTOR_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
665 id: "containers-map-resident-constructor-input",
666 mode: BuiltinExtensionMode::RunMatOnly,
667 description:
668 "containers.Map gathering resident constructor keys or values is a RunMat extension",
669 error_identifier: Some("RunMat:compatibility:ContainersMapResidentConstructorExtension"),
670};
671const MAP_RESIDENT_ISKEY_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
672 id: "containers-map-resident-iskey-input",
673 mode: BuiltinExtensionMode::RunMatOnly,
674 description: "containers.Map.isKey gathering resident keys is a RunMat extension",
675 error_identifier: Some("RunMat:compatibility:ContainersMapResidentIsKeyExtension"),
676};
677const MAP_RESIDENT_VALUES_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
678 id: "containers-map-resident-values-input",
679 mode: BuiltinExtensionMode::RunMatOnly,
680 description: "containers.Map.values gathering resident selected keys is a RunMat extension",
681 error_identifier: Some("RunMat:compatibility:ContainersMapResidentValuesExtension"),
682};
683const MAP_RESIDENT_REMOVE_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
684 id: "containers-map-resident-remove-input",
685 mode: BuiltinExtensionMode::RunMatOnly,
686 description: "containers.Map.remove gathering resident keys is a RunMat extension",
687 error_identifier: Some("RunMat:compatibility:ContainersMapResidentRemoveExtension"),
688};
689const MAP_RESIDENT_SUBSREF_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
690 id: "containers-map-resident-subsref-input",
691 mode: BuiltinExtensionMode::RunMatOnly,
692 description: "containers.Map.subsref gathering resident keys is a RunMat extension",
693 error_identifier: Some("RunMat:compatibility:ContainersMapResidentSubsrefExtension"),
694};
695const MAP_RESIDENT_SUBSASGN_EXTENSION: BuiltinExtensionDescriptor = BuiltinExtensionDescriptor {
696 id: "containers-map-resident-subsasgn-input",
697 mode: BuiltinExtensionMode::RunMatOnly,
698 description:
699 "containers.Map.subsasgn gathering resident keys or rhs values is a RunMat extension",
700 error_identifier: Some("RunMat:compatibility:ContainersMapResidentSubsasgnExtension"),
701};
702pub const CONTAINERS_MAP_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
703 [MAP_RESIDENT_CONSTRUCTOR_EXTENSION];
704pub const CONTAINERS_MAP_ISKEY_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
705 [MAP_RESIDENT_ISKEY_EXTENSION];
706pub const CONTAINERS_MAP_VALUES_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
707 [MAP_RESIDENT_VALUES_EXTENSION];
708pub const CONTAINERS_MAP_REMOVE_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
709 [MAP_RESIDENT_REMOVE_EXTENSION];
710pub const CONTAINERS_MAP_SUBSREF_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
711 [MAP_RESIDENT_SUBSREF_EXTENSION];
712pub const CONTAINERS_MAP_SUBSASGN_EXTENSIONS: [BuiltinExtensionDescriptor; 1] =
713 [MAP_RESIDENT_SUBSASGN_EXTENSION];
714
715#[runmat_macros::register_gpu_spec(
716 builtin_path = "crate::builtins::containers::map::containers_map"
717)]
718pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
719 name: "containers.Map",
720 op_kind: GpuOpKind::Custom("map"),
721 supported_precisions: &[],
722 broadcast: BroadcastSemantics::None,
723 provider_hooks: &[],
724 constant_strategy: ConstantStrategy::InlineLiteral,
725 residency: ResidencyPolicy::GatherImmediately,
726 nan_mode: ReductionNaN::Include,
727 two_pass_threshold: None,
728 workgroup_size: None,
729 accepts_nan_mode: false,
730 notes: "Map storage and outputs are host-resident; resident inputs gather only through explicitly gated RunMat extensions.",
731};
732
733fn map_error_with_detail(
734 error: &'static BuiltinErrorDescriptor,
735 detail: impl AsRef<str>,
736 builtin: &'static str,
737) -> RuntimeError {
738 let raw = detail.as_ref().trim();
739 let normalized = raw
740 .strip_prefix("containers.Map:")
741 .map(str::trim)
742 .unwrap_or(raw);
743 let message = if normalized.is_empty() {
744 error.message.to_string()
745 } else {
746 format!("{}: {}", error.message, normalized)
747 };
748 let mut builder = build_runtime_error(message).with_builtin(builtin);
749 if let Some(identifier) = error.identifier {
750 builder = builder.with_identifier(identifier);
751 }
752 builder.build()
753}
754
755fn map_descriptor_error(
756 error: &'static BuiltinErrorDescriptor,
757 builtin: &'static str,
758) -> RuntimeError {
759 map_error_with_detail(error, "", builtin)
760}
761
762fn map_invalid(detail: impl AsRef<str>, builtin: &'static str) -> RuntimeError {
763 map_error_with_detail(&CONTAINERS_MAP_ERROR_INVALID_ARGUMENT, detail, builtin)
764}
765
766fn map_internal(detail: impl AsRef<str>, builtin: &'static str) -> RuntimeError {
767 map_error_with_detail(&CONTAINERS_MAP_ERROR_INTERNAL, detail, builtin)
768}
769
770fn map_error(message: impl Into<String>, builtin: &'static str) -> RuntimeError {
771 map_invalid(message.into(), builtin)
772}
773
774fn attach_builtin_context(mut error: RuntimeError, builtin: &'static str) -> RuntimeError {
775 if error.context.builtin.is_none() {
776 error.context = error.context.with_builtin(builtin);
777 }
778 error
779}
780
781#[runmat_macros::register_fusion_spec(
782 builtin_path = "crate::builtins::containers::map::containers_map"
783)]
784pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
785 name: "containers.Map",
786 shape: ShapeRequirements::Any,
787 constant_strategy: ConstantStrategy::InlineLiteral,
788 elementwise: None,
789 reduction: None,
790 emits_nan: false,
791 notes: "Handles act as fusion sinks; map construction terminates GPU fusion plans.",
792};
793
794static NEXT_ID: AtomicU64 = AtomicU64::new(1);
795
796thread_local! {
797 static MAP_REGISTRY: RefCell<HashMap<u64, MapStore>> = RefCell::new(HashMap::new());
798 static MAP_ROOT_STATE: RefCell<Option<MapRootState>> = const { RefCell::new(None) };
799}
800
801static CONTAINERS_MAP_CLASS_REGISTERED: crate::class_registry::ClassRegistration =
802 crate::class_registry::ClassRegistration::new(CLASS_NAME);
803
804struct MapRootState {
805 root_id: RootId,
806 active: Arc<AtomicBool>,
807}
808
809struct MapRegistryRoot {
810 active: Arc<AtomicBool>,
811}
812
813impl GcRoot for MapRegistryRoot {
814 fn scan(&self) -> Vec<GcHandle> {
815 struct RootCollector {
816 roots: Vec<GcHandle>,
817 }
818
819 impl Tracer for RootCollector {
820 fn mark(&mut self, handle: GcHandle) {
821 self.roots.push(handle);
822 }
823 }
824
825 MAP_REGISTRY.with(|registry| {
826 let registry = registry.borrow();
827 let mut collector = RootCollector { roots: Vec::new() };
828 for store in registry.values() {
829 if let Some(storage) = store.storage {
830 collector.mark(storage);
831 }
832 for entry in &store.entries {
833 entry.key_value.trace(&mut collector);
834 entry.value.trace(&mut collector);
835 }
836 }
837 collector.roots
838 })
839 }
840
841 fn description(&self) -> String {
842 "containers.Map registry values".to_string()
843 }
844
845 fn is_active(&self) -> bool {
846 self.active.load(AtomicOrdering::Acquire)
847 && MAP_REGISTRY.with(|registry| !registry.borrow().is_empty())
848 }
849}
850
851fn ensure_map_registry_root_registered(builtin: &'static str) -> BuiltinResult<()> {
852 MAP_ROOT_STATE.with(|state| {
853 if state
854 .borrow()
855 .as_ref()
856 .is_some_and(|state| state.active.load(AtomicOrdering::Acquire))
857 {
858 return Ok(());
859 }
860
861 let active = Arc::new(AtomicBool::new(true));
862 let root_id = runmat_gc::gc_register_root(Box::new(MapRegistryRoot {
863 active: Arc::clone(&active),
864 }))
865 .map_err(|e| {
866 map_internal(
867 format!("containers.Map: failed to register GC root: {e}"),
868 builtin,
869 )
870 })?;
871 *state.borrow_mut() = Some(MapRootState { root_id, active });
872 Ok(())
873 })
874}
875
876fn deactivate_map_registry_root_if_empty() {
877 let empty = MAP_REGISTRY.with(|registry| {
878 registry
879 .try_borrow()
880 .map(|registry| registry.is_empty())
881 .unwrap_or(false)
882 });
883 if empty {
884 MAP_ROOT_STATE.with(|state| {
885 if let Some(state) = state.borrow_mut().take() {
886 state.active.store(false, AtomicOrdering::Release);
887 if let Err(err) = runmat_gc::gc_unregister_root(state.root_id) {
888 log::warn!("containers.Map: failed to unregister empty registry root: {err}");
889 }
890 }
891 });
892 }
893}
894
895fn ensure_containers_map_class_registered() {
896 CONTAINERS_MAP_CLASS_REGISTERED.ensure(|| {
897 let mut properties = HashMap::new();
898 for name in ["Count", "KeyType", "ValueType"] {
899 properties.insert(
900 name.to_string(),
901 crate::class_registry::RuntimeProperty {
902 name: name.to_string(),
903 is_static: false,
904 is_constant: false,
905 is_dependent: true,
906 get_access: MemberAccess::Public,
907 set_access: MemberAccess::Private,
908 default_value: None,
909 },
910 );
911 }
912
913 let mut methods = HashMap::new();
914 for (name, function_name) in [
915 ("keys", BUILTIN_KEYS),
916 ("values", BUILTIN_VALUES),
917 ("isKey", BUILTIN_IS_KEY),
918 ("remove", BUILTIN_REMOVE),
919 (OBJECT_SUBSREF_METHOD, BUILTIN_SUBSREF),
920 (OBJECT_SUBSASGN_METHOD, BUILTIN_SUBSASGN),
921 ] {
922 methods.insert(
923 name.to_string(),
924 crate::class_registry::RuntimeMethod {
925 name: name.to_string(),
926 is_static: false,
927 is_abstract: false,
928 is_sealed: false,
929 access: MemberAccess::Public,
930 function_name: function_name.to_string(),
931 implicit_class_argument: None,
932 },
933 );
934 }
935
936 crate::class_registry::register_class(crate::class_registry::RuntimeClass {
937 name: CLASS_NAME.to_string(),
938 parent: None,
939 properties,
940 methods,
941 });
942 });
943}
944
945#[derive(Clone, Copy, Debug, PartialEq, Eq)]
946enum KeyType {
947 Char,
948 Double,
949 Single,
950 Int32,
951 UInt32,
952 Int64,
953 UInt64,
954}
955
956impl KeyType {
957 fn matlab_name(self) -> &'static str {
958 match self {
959 KeyType::Char => "char",
960 KeyType::Double => "double",
961 KeyType::Single => "single",
962 KeyType::Int32 => "int32",
963 KeyType::UInt32 => "uint32",
964 KeyType::Int64 => "int64",
965 KeyType::UInt64 => "uint64",
966 }
967 }
968
969 fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
970 let text = string_from_value(value, "containers.Map: expected a KeyType string", builtin)?;
971 match text.to_ascii_lowercase().as_str() {
972 "char" | "character" => Ok(KeyType::Char),
973 "double" => Ok(KeyType::Double),
974 "single" => Ok(KeyType::Single),
975 "int32" => Ok(KeyType::Int32),
976 "uint32" => Ok(KeyType::UInt32),
977 "int64" => Ok(KeyType::Int64),
978 "uint64" => Ok(KeyType::UInt64),
979 other => Err(map_error(
980 format!(
981 "containers.Map: unsupported KeyType '{other}'. Valid types: char, double, single, int32, uint32, int64, uint64."
982 ),
983 builtin,
984 )),
985 }
986 }
987}
988
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990enum ValueType {
991 Any,
992 Char,
993 Double,
994 Single,
995 Logical,
996 Int8,
997 UInt8,
998 Int16,
999 UInt16,
1000 Int32,
1001 UInt32,
1002 Int64,
1003 UInt64,
1004}
1005
1006impl ValueType {
1007 fn matlab_name(self) -> &'static str {
1008 match self {
1009 ValueType::Any => "any",
1010 ValueType::Char => "char",
1011 ValueType::Double => "double",
1012 ValueType::Single => "single",
1013 ValueType::Logical => "logical",
1014 ValueType::Int8 => "int8",
1015 ValueType::UInt8 => "uint8",
1016 ValueType::Int16 => "int16",
1017 ValueType::UInt16 => "uint16",
1018 ValueType::Int32 => "int32",
1019 ValueType::UInt32 => "uint32",
1020 ValueType::Int64 => "int64",
1021 ValueType::UInt64 => "uint64",
1022 }
1023 }
1024
1025 fn parse(value: &Value, builtin: &'static str) -> BuiltinResult<Self> {
1026 let text = string_from_value(
1027 value,
1028 "containers.Map: expected a ValueType string",
1029 builtin,
1030 )?;
1031 match text.to_ascii_lowercase().as_str() {
1032 "any" => Ok(ValueType::Any),
1033 "char" | "character" => Ok(ValueType::Char),
1034 "double" => Ok(ValueType::Double),
1035 "single" => Ok(ValueType::Single),
1036 "logical" => Ok(ValueType::Logical),
1037 "int8" => Ok(ValueType::Int8),
1038 "uint8" => Ok(ValueType::UInt8),
1039 "int16" => Ok(ValueType::Int16),
1040 "uint16" => Ok(ValueType::UInt16),
1041 "int32" => Ok(ValueType::Int32),
1042 "uint32" => Ok(ValueType::UInt32),
1043 "int64" => Ok(ValueType::Int64),
1044 "uint64" => Ok(ValueType::UInt64),
1045 other => Err(map_error(
1046 format!(
1047 "containers.Map: unsupported ValueType '{other}'. Valid types: any, char, logical, double, single, int8, uint8, int16, uint16, int32, uint32, int64, uint64."
1048 ),
1049 builtin,
1050 )),
1051 }
1052 }
1053
1054 fn normalize(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1055 match self {
1056 ValueType::Any => Ok(value),
1057 ValueType::Char => {
1058 let chars = char_array_from_value(&value, builtin)?;
1059 Ok(Value::CharArray(chars))
1060 }
1061 ValueType::Double => normalize_numeric_value(value, NumericDType::F64, builtin),
1062 ValueType::Single => normalize_numeric_value(value, NumericDType::F32, builtin),
1063 ValueType::Logical => normalize_logical_value(value, builtin),
1064 integer_type => normalize_integer_value(value, *integer_type, builtin),
1065 }
1066 }
1067}
1068
1069#[derive(Clone, PartialEq, Eq, Hash)]
1070enum NormalizedKey {
1071 String(String),
1072 Float(u64),
1073 Int(i64),
1074 UInt(u64),
1075}
1076
1077#[derive(Clone)]
1078struct MapEntry {
1079 normalized: NormalizedKey,
1080 key_value: Value,
1081 value: Value,
1082}
1083
1084struct MapStore {
1085 storage: Option<GcHandle>,
1086 key_type: KeyType,
1087 value_type: ValueType,
1088 uniform_values: bool,
1089 uniform_class: Option<ValueClass>,
1090 entries: Vec<MapEntry>,
1091 index: HashMap<NormalizedKey, usize>,
1092}
1093
1094impl MapStore {
1095 fn new(key_type: KeyType, value_type: ValueType, uniform_values: bool) -> Self {
1096 Self {
1097 storage: None,
1098 key_type,
1099 value_type,
1100 uniform_values,
1101 uniform_class: None,
1102 entries: Vec::new(),
1103 index: HashMap::new(),
1104 }
1105 }
1106
1107 fn len(&self) -> usize {
1108 self.entries.len()
1109 }
1110
1111 fn contains(&self, key: &NormalizedKey) -> bool {
1112 self.index.contains_key(key)
1113 }
1114
1115 fn get(&self, key: &NormalizedKey) -> Option<Value> {
1116 self.index
1117 .get(key)
1118 .map(|&idx| self.entries[idx].value.clone())
1119 }
1120
1121 fn insert_new(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
1122 if self.index.contains_key(&entry.normalized) {
1123 return Err(map_error(
1124 "containers.Map: Duplicate key name was provided.",
1125 builtin,
1126 ));
1127 }
1128 entry.value = self.normalize_value(entry.value, builtin)?;
1129 self.track_uniform_class(&entry.value, builtin)?;
1130 let idx = self.entries.len();
1131 self.entries.push(entry.clone());
1132 self.index.insert(entry.normalized, idx);
1133 Ok(())
1134 }
1135
1136 fn set(&mut self, mut entry: MapEntry, builtin: &'static str) -> BuiltinResult<()> {
1137 entry.value = self.normalize_value(entry.value, builtin)?;
1138 self.track_uniform_class(&entry.value, builtin)?;
1139 if let Some(&idx) = self.index.get(&entry.normalized) {
1140 self.entries[idx].value = entry.value.clone();
1141 self.entries[idx].key_value = entry.key_value;
1142 } else {
1143 let idx = self.entries.len();
1144 self.entries.push(entry.clone());
1145 self.index.insert(entry.normalized, idx);
1146 }
1147 Ok(())
1148 }
1149
1150 fn remove(&mut self, key: &NormalizedKey, builtin: &'static str) -> BuiltinResult<()> {
1151 let idx = match self.index.get(key) {
1152 Some(&idx) => idx,
1153 None => {
1154 return Err(map_descriptor_error(
1155 &CONTAINERS_MAP_ERROR_MISSING_KEY,
1156 builtin,
1157 ));
1158 }
1159 };
1160 self.entries.remove(idx);
1161 self.index.clear();
1162 for (pos, entry) in self.entries.iter().enumerate() {
1163 self.index.insert(entry.normalized.clone(), pos);
1164 }
1165 if self.entries.is_empty() {
1166 self.uniform_class = None;
1167 }
1168 Ok(())
1169 }
1170
1171 fn keys(&self) -> Vec<Value> {
1172 self.sorted_entries()
1173 .into_iter()
1174 .map(|entry| entry.key_value.clone())
1175 .collect()
1176 }
1177
1178 fn values(&self) -> Vec<Value> {
1179 self.sorted_entries()
1180 .into_iter()
1181 .map(|entry| entry.value.clone())
1182 .collect()
1183 }
1184
1185 fn sorted_entries(&self) -> Vec<&MapEntry> {
1186 let mut entries = self.entries.iter().collect::<Vec<_>>();
1187 entries.sort_by(|left, right| match (&left.normalized, &right.normalized) {
1188 (NormalizedKey::String(left), NormalizedKey::String(right)) => left.cmp(right),
1189 (NormalizedKey::Int(left), NormalizedKey::Int(right)) => left.cmp(right),
1190 (NormalizedKey::UInt(left), NormalizedKey::UInt(right)) => left.cmp(right),
1191 (NormalizedKey::Float(left), NormalizedKey::Float(right)) => f64::from_bits(*left)
1192 .partial_cmp(&f64::from_bits(*right))
1193 .unwrap_or(std::cmp::Ordering::Equal),
1194 _ => std::cmp::Ordering::Equal,
1195 });
1196 entries
1197 }
1198
1199 fn normalize_value(&self, value: Value, builtin: &'static str) -> BuiltinResult<Value> {
1200 self.value_type.normalize(value, builtin)
1201 }
1202
1203 fn track_uniform_class(&mut self, value: &Value, builtin: &'static str) -> BuiltinResult<()> {
1204 if !self.uniform_values {
1205 return Ok(());
1206 }
1207 let class = ValueClass::from_value(value);
1208 if let Some(existing) = &self.uniform_class {
1209 if existing != &class {
1210 return Err(map_error(
1211 "containers.Map: UniformValues=true requires all values to share the same MATLAB class.",
1212 builtin,
1213 ));
1214 }
1215 } else {
1216 self.uniform_class = Some(class);
1217 }
1218 Ok(())
1219 }
1220}
1221
1222#[derive(Clone, Debug, PartialEq, Eq)]
1223enum ValueClass {
1224 Char,
1225 String,
1226 Numeric(NumericDType),
1227 Logical,
1228 Cell,
1229 Struct,
1230 Object,
1231 Other(&'static str),
1232}
1233
1234impl ValueClass {
1235 fn from_value(value: &Value) -> Self {
1236 match value {
1237 Value::CharArray(_) => ValueClass::Char,
1238 Value::String(_) | Value::StringArray(_) => ValueClass::String,
1239 Value::Num(_) => ValueClass::Numeric(NumericDType::F64),
1240 Value::Tensor(tensor) => ValueClass::Numeric(tensor.numeric_dtype()),
1241 Value::ComplexTensor(tensor) => ValueClass::Numeric(tensor.numeric_dtype()),
1242 Value::Bool(_) | Value::LogicalArray(_) => ValueClass::Logical,
1243 Value::Int(value) => ValueClass::Numeric(int_value_dtype(value)),
1244 Value::Cell(_) => ValueClass::Cell,
1245 Value::Struct(_) => ValueClass::Struct,
1246 Value::ObjectArray(_)
1247 | Value::Object(_)
1248 | Value::HandleObject(_)
1249 | Value::Listener(_) => ValueClass::Object,
1250 _ => ValueClass::Other("other"),
1251 }
1252 }
1253}
1254
1255struct ConstructorArgs {
1256 key_type: KeyType,
1257 value_type: ValueType,
1258 uniform_values: bool,
1259 keys: Vec<KeyCandidate>,
1260 values: Vec<Value>,
1261}
1262
1263struct KeyCandidate {
1264 normalized: NormalizedKey,
1265 canonical: Value,
1266}
1267
1268#[runtime_builtin(
1269 name = "containers.Map",
1270 category = "containers/map",
1271 summary = "Create key-value dictionary objects.",
1272 keywords = "map,containers.Map,dictionary,hash map,lookup",
1273 accel = "metadata",
1274 sink = true,
1275 type_resolver(map_handle_type),
1276 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_DESCRIPTOR),
1277 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_EXTENSIONS),
1278 integer_capabilities(
1279 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_INTEGER_CAPABILITIES
1280 ),
1281 builtin_path = "crate::builtins::containers::map::containers_map"
1282)]
1283async fn containers_map_builtin(args: Vec<Value>) -> crate::BuiltinResult<Value> {
1284 if args.iter().any(contains_resident_value) {
1285 crate::compatibility::ensure_builtin_extension_enabled(
1286 &MAP_RESIDENT_CONSTRUCTOR_EXTENSION,
1287 BUILTIN_CONSTRUCTOR,
1288 )?;
1289 }
1290 let mut host_args = Vec::with_capacity(args.len());
1291 for value in args {
1292 host_args.push(
1293 gather_if_needed_async(&value)
1294 .await
1295 .map_err(|err| attach_builtin_context(err, BUILTIN_CONSTRUCTOR))?,
1296 );
1297 }
1298 let parsed = parse_constructor_args(host_args, BUILTIN_CONSTRUCTOR).await?;
1299 let store = build_store(parsed, BUILTIN_CONSTRUCTOR)?;
1300 allocate_handle(store, BUILTIN_CONSTRUCTOR)
1301}
1302
1303#[runtime_builtin(
1304 name = "containers.Map.keys",
1305 type_resolver(map_cell_type),
1306 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_KEYS_DESCRIPTOR),
1307 integer_capabilities(
1308 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_KEYS_INTEGER_CAPABILITIES
1309 ),
1310 builtin_path = "crate::builtins::containers::map::containers_map"
1311)]
1312async fn containers_map_keys(map: Value) -> crate::BuiltinResult<Value> {
1313 with_store(&map, BUILTIN_KEYS, |store| {
1314 let values = store.keys();
1315 make_row_cell(values, BUILTIN_KEYS)
1316 })
1317}
1318
1319#[runtime_builtin(
1320 name = "containers.Map.values",
1321 type_resolver(map_cell_type),
1322 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_VALUES_DESCRIPTOR),
1323 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_VALUES_EXTENSIONS),
1324 integer_capabilities(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_VALUES_INTEGER_CAPABILITIES),
1325 builtin_path = "crate::builtins::containers::map::containers_map"
1326)]
1327async fn containers_map_values(map: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1328 if rest.iter().any(contains_resident_value) {
1329 crate::compatibility::ensure_builtin_extension_enabled(
1330 &MAP_RESIDENT_VALUES_EXTENSION,
1331 BUILTIN_VALUES,
1332 )?;
1333 }
1334 if rest.is_empty() {
1335 return with_store(&map, BUILTIN_VALUES, |store| {
1336 make_row_cell(store.values(), BUILTIN_VALUES)
1337 });
1338 }
1339 if rest.len() != 1 {
1340 return Err(map_error(
1341 "containers.Map: values expects M or M,keySet",
1342 BUILTIN_VALUES,
1343 ));
1344 }
1345 let Value::Cell(key_set) = &rest[0] else {
1346 return Err(map_error(
1347 "containers.Map: values keySet must be a cell array",
1348 BUILTIN_VALUES,
1349 ));
1350 };
1351 let mut keys = Vec::with_capacity(key_set.data.len());
1352 for key in &key_set.data {
1353 keys.push(
1354 gather_if_needed_async(key)
1355 .await
1356 .map_err(|err| attach_builtin_context(err, BUILTIN_VALUES))?,
1357 );
1358 }
1359 with_store(&map, BUILTIN_VALUES, |store| {
1360 let mut values = Vec::with_capacity(keys.len());
1361 for key in &keys {
1362 let normalized = normalize_key(key, store.key_type, BUILTIN_VALUES)?;
1363 values.push(store.get(&normalized).ok_or_else(|| {
1364 map_descriptor_error(&CONTAINERS_MAP_ERROR_MISSING_KEY, BUILTIN_VALUES)
1365 })?);
1366 }
1367 crate::make_cell_with_shape(values, vec![key_set.rows, key_set.cols])
1368 .map_err(|err| map_error(format!("containers.Map: {err}"), BUILTIN_VALUES))
1369 })
1370}
1371
1372#[runtime_builtin(
1373 name = "containers.Map.isKey",
1374 type_resolver(map_is_key_type),
1375 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_ISKEY_DESCRIPTOR),
1376 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_ISKEY_EXTENSIONS),
1377 integer_capabilities(
1378 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_ISKEY_INTEGER_CAPABILITIES
1379 ),
1380 builtin_path = "crate::builtins::containers::map::containers_map"
1381)]
1382async fn containers_map_is_key(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
1383 ensure_resident_extension(&key_spec, &MAP_RESIDENT_ISKEY_EXTENSION, BUILTIN_IS_KEY)?;
1384 let key_type = with_store(&map, BUILTIN_IS_KEY, |store| Ok(store.key_type))?;
1385 let collection = collect_key_spec(&key_spec, key_type, BUILTIN_IS_KEY).await?;
1386 with_store(&map, BUILTIN_IS_KEY, |store| {
1387 let mut flags = Vec::with_capacity(collection.values.len());
1388 for value in &collection.values {
1389 let normalized = normalize_key(value, store.key_type, BUILTIN_IS_KEY)?;
1390 flags.push(store.contains(&normalized));
1391 }
1392 if collection.values.len() == 1 {
1393 Ok(Value::Bool(flags[0]))
1394 } else {
1395 let data: Vec<u8> = flags.into_iter().map(|b| if b { 1 } else { 0 }).collect();
1396 let logical = LogicalArray::new(data, collection.shape)
1397 .map_err(|e| map_error(format!("containers.Map: {e}"), BUILTIN_IS_KEY))?;
1398 Ok(Value::LogicalArray(logical))
1399 }
1400 })
1401}
1402
1403#[runtime_builtin(
1404 name = "containers.Map.remove",
1405 type_resolver(map_handle_type),
1406 descriptor(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_REMOVE_DESCRIPTOR),
1407 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_REMOVE_EXTENSIONS),
1408 integer_capabilities(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_REMOVE_INTEGER_CAPABILITIES),
1409 builtin_path = "crate::builtins::containers::map::containers_map"
1410)]
1411async fn containers_map_remove(map: Value, key_spec: Value) -> crate::BuiltinResult<Value> {
1412 ensure_resident_extension(&key_spec, &MAP_RESIDENT_REMOVE_EXTENSION, BUILTIN_REMOVE)?;
1413 let key_type = with_store(&map, BUILTIN_REMOVE, |store| Ok(store.key_type))?;
1414 let collection = collect_key_spec(&key_spec, key_type, BUILTIN_REMOVE).await?;
1415 with_store_mut(&map, BUILTIN_REMOVE, |store| {
1416 for value in &collection.values {
1417 let normalized = normalize_key(value, store.key_type, BUILTIN_REMOVE)?;
1418 store.remove(&normalized, BUILTIN_REMOVE)?;
1419 }
1420 Ok(())
1421 })?;
1422 Ok(map)
1423}
1424
1425#[runtime_builtin(
1426 name = "containers.Map.subsref",
1427 type_resolver(map_unknown_type),
1428 descriptor(
1429 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSREF_DESCRIPTOR
1430 ),
1431 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSREF_EXTENSIONS),
1432 integer_capabilities(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSREF_INTEGER_CAPABILITIES),
1433 builtin_path = "crate::builtins::containers::map::containers_map"
1434)]
1435async fn containers_map_subsref(
1436 map: Value,
1437 kind: String,
1438 payload: Value,
1439) -> crate::BuiltinResult<Value> {
1440 ensure_resident_extension(&payload, &MAP_RESIDENT_SUBSREF_EXTENSION, BUILTIN_SUBSREF)?;
1441 if !matches!(map, Value::HandleObject(_)) {
1442 return Err(map_error(
1443 format!("containers.Map: subsref expects a containers.Map handle, got {map:?}"),
1444 BUILTIN_SUBSREF,
1445 ));
1446 }
1447 match kind.as_str() {
1448 OBJECT_INDEX_PAREN => {
1449 let mut args = extract_key_arguments(&payload, BUILTIN_SUBSREF)?;
1450 if args.is_empty() {
1451 return Err(map_error(
1452 "containers.Map: indexing requires at least one key",
1453 BUILTIN_SUBSREF,
1454 ));
1455 }
1456 if args.len() != 1 {
1457 return Err(map_error(
1458 "containers.Map: indexing expects a single key argument",
1459 BUILTIN_SUBSREF,
1460 ));
1461 }
1462 let key_arg = args.remove(0);
1463 let key_type = with_store(&map, BUILTIN_SUBSREF, |store| Ok(store.key_type))?;
1464 let collection = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSREF).await?;
1465 if collection.values.len() != 1 {
1466 return Err(map_error(
1467 "containers.Map: indexing requires exactly one scalar key",
1468 BUILTIN_SUBSREF,
1469 ));
1470 }
1471 with_store(&map, BUILTIN_SUBSREF, |store| {
1472 let normalized =
1473 normalize_key(&collection.values[0], store.key_type, BUILTIN_SUBSREF)?;
1474 store.get(&normalized).ok_or_else(|| {
1475 map_descriptor_error(&CONTAINERS_MAP_ERROR_MISSING_KEY, BUILTIN_SUBSREF)
1476 })
1477 })
1478 }
1479 OBJECT_INDEX_MEMBER => {
1480 let field = string_from_value(
1481 &payload,
1482 "containers.Map: property name must be text",
1483 BUILTIN_SUBSREF,
1484 )?;
1485 with_store(&map, BUILTIN_SUBSREF, |store| {
1486 match field.to_ascii_lowercase().as_str() {
1487 "count" => Ok(Value::Int(IntValue::U64(store.len() as u64))),
1488 "keytype" => char_array_value(store.key_type.matlab_name(), BUILTIN_SUBSREF),
1489 "valuetype" => {
1490 char_array_value(store.value_type.matlab_name(), BUILTIN_SUBSREF)
1491 }
1492 other => Err(map_error(
1493 format!("containers.Map: no such property '{other}'"),
1494 BUILTIN_SUBSREF,
1495 )),
1496 }
1497 })
1498 }
1499 OBJECT_INDEX_BRACE => Err(map_error(
1500 "containers.Map: curly-brace indexing is not supported.",
1501 BUILTIN_SUBSREF,
1502 )),
1503 other => Err(map_error(
1504 format!("containers.Map: unsupported indexing kind '{other}'"),
1505 BUILTIN_SUBSREF,
1506 )),
1507 }
1508}
1509
1510#[runtime_builtin(
1511 name = "containers.Map.subsasgn",
1512 type_resolver(map_handle_type),
1513 descriptor(
1514 crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSASGN_DESCRIPTOR
1515 ),
1516 extensions(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSASGN_EXTENSIONS),
1517 integer_capabilities(crate::builtins::containers::map::containers_map::CONTAINERS_MAP_SUBSASGN_INTEGER_CAPABILITIES),
1518 builtin_path = "crate::builtins::containers::map::containers_map"
1519)]
1520async fn containers_map_subsasgn(
1521 map: Value,
1522 kind: String,
1523 payload: Value,
1524 rhs: Value,
1525) -> crate::BuiltinResult<Value> {
1526 if contains_resident_value(&payload) || contains_resident_value(&rhs) {
1527 crate::compatibility::ensure_builtin_extension_enabled(
1528 &MAP_RESIDENT_SUBSASGN_EXTENSION,
1529 BUILTIN_SUBSASGN,
1530 )?;
1531 }
1532 if !matches!(map, Value::HandleObject(_)) {
1533 return Err(map_error(
1534 format!("containers.Map: subsasgn expects a containers.Map handle, got {map:?}"),
1535 BUILTIN_SUBSASGN,
1536 ));
1537 }
1538 match kind.as_str() {
1539 OBJECT_INDEX_PAREN => {
1540 let mut args = extract_key_arguments(&payload, BUILTIN_SUBSASGN)?;
1541 if args.is_empty() {
1542 return Err(map_error(
1543 "containers.Map: assignment requires at least one key",
1544 BUILTIN_SUBSASGN,
1545 ));
1546 }
1547 if args.len() != 1 {
1548 return Err(map_error(
1549 "containers.Map: assignment expects a single key argument",
1550 BUILTIN_SUBSASGN,
1551 ));
1552 }
1553 let key_arg = args.remove(0);
1554 let key_type = with_store(&map, BUILTIN_SUBSASGN, |store| Ok(store.key_type))?;
1555 let KeyCollection {
1556 values: key_values, ..
1557 } = collect_key_spec(&key_arg, key_type, BUILTIN_SUBSASGN).await?;
1558 if key_values.len() != 1 {
1559 return Err(map_error(
1560 "containers.Map: assignment requires exactly one scalar key",
1561 BUILTIN_SUBSASGN,
1562 ));
1563 }
1564 let values =
1565 expand_assignment_values(rhs.clone(), key_values.len(), BUILTIN_SUBSASGN).await?;
1566 with_store_mut(&map, BUILTIN_SUBSASGN, move |store| {
1567 for (key_raw, value) in key_values.into_iter().zip(values.into_iter()) {
1568 let (normalized, canonical) =
1569 canonicalize_key(key_raw, store.key_type, BUILTIN_SUBSASGN)?;
1570 let entry = MapEntry {
1571 normalized,
1572 key_value: canonical,
1573 value,
1574 };
1575 store.set(entry, BUILTIN_SUBSASGN)?;
1576 }
1577 Ok(())
1578 })?;
1579 Ok(map)
1580 }
1581 OBJECT_INDEX_MEMBER => Err(map_error(
1582 "containers.Map: property assignments are not supported.",
1583 BUILTIN_SUBSASGN,
1584 )),
1585 OBJECT_INDEX_BRACE => Err(map_error(
1586 "containers.Map: curly-brace assignment is not supported.",
1587 BUILTIN_SUBSASGN,
1588 )),
1589 other => Err(map_error(
1590 format!("containers.Map: unsupported assignment kind '{other}'"),
1591 BUILTIN_SUBSASGN,
1592 )),
1593 }
1594}
1595
1596async fn parse_constructor_args(
1597 args: Vec<Value>,
1598 builtin: &'static str,
1599) -> BuiltinResult<ConstructorArgs> {
1600 let mut index = 0usize;
1601 let mut keys_input: Option<Value> = None;
1602 let mut values_input: Option<Value> = None;
1603
1604 if index < args.len() && keyword_of(&args[index]).is_none() {
1605 if args.len() < 2 {
1606 return Err(map_error(
1607 "containers.Map: constructor requires both keys and values when either is provided.",
1608 builtin,
1609 ));
1610 }
1611 keys_input = Some(args[index].clone());
1612 values_input = Some(args[index + 1].clone());
1613 index += 2;
1614 }
1615
1616 let has_data = keys_input.is_some();
1617 let mut key_type = KeyType::Char;
1618 let mut value_type = ValueType::Any;
1619 let mut uniform_values = has_data;
1620 let mut key_type_explicit = false;
1621 let mut value_type_explicit = false;
1622 while index < args.len() {
1623 let keyword = keyword_of(&args[index]).ok_or_else(|| {
1624 map_error(
1625 "containers.Map: expected option name (e.g. 'KeyType')",
1626 builtin,
1627 )
1628 })?;
1629 index += 1;
1630 let Some(value) = args.get(index) else {
1631 return Err(map_error(
1632 format!("containers.Map: missing value for option '{keyword}'"),
1633 builtin,
1634 ));
1635 };
1636 index += 1;
1637 match keyword.as_str() {
1638 "keytype" => {
1639 if has_data {
1640 return Err(map_error(
1641 "containers.Map: KeyType is only valid for an empty Map constructor",
1642 builtin,
1643 ));
1644 }
1645 key_type = KeyType::parse(value, builtin)?;
1646 key_type_explicit = true;
1647 }
1648 "valuetype" => {
1649 if has_data {
1650 return Err(map_error(
1651 "containers.Map: ValueType is only valid for an empty Map constructor",
1652 builtin,
1653 ));
1654 }
1655 value_type = ValueType::parse(value, builtin)?;
1656 value_type_explicit = true;
1657 }
1658 "uniformvalues" => {
1659 if !has_data {
1660 return Err(map_error(
1661 "containers.Map: UniformValues requires keySet and valueSet",
1662 builtin,
1663 ));
1664 }
1665 uniform_values = bool_from_value(
1666 value,
1667 "containers.Map: UniformValues must be logical",
1668 builtin,
1669 )?
1670 }
1671 other => {
1672 return Err(map_error(
1673 format!("containers.Map: unrecognised option '{other}'"),
1674 builtin,
1675 ));
1676 }
1677 }
1678 }
1679
1680 if !has_data && (key_type_explicit != value_type_explicit) {
1681 return Err(map_error(
1682 "containers.Map: KeyType and ValueType must both be specified for an empty typed Map",
1683 builtin,
1684 ));
1685 }
1686 if let Some(keys) = &keys_input {
1687 key_type = infer_key_type(keys, builtin)?;
1688 }
1689 if let Some(values) = &values_input {
1690 value_type = if uniform_values {
1691 infer_value_type(values)
1692 } else {
1693 ValueType::Any
1694 };
1695 }
1696
1697 let keys = match keys_input {
1698 Some(value) => prepare_keys(value, key_type, builtin).await?,
1699 None => Vec::new(),
1700 };
1701
1702 let values = match values_input {
1703 Some(value) => prepare_values(value, builtin).await?,
1704 None => Vec::new(),
1705 };
1706
1707 if keys.len() != values.len() {
1708 return Err(map_error(
1709 format!(
1710 "containers.Map: number of keys ({}) must match number of values ({})",
1711 keys.len(),
1712 values.len()
1713 ),
1714 builtin,
1715 ));
1716 }
1717
1718 Ok(ConstructorArgs {
1719 key_type,
1720 value_type,
1721 uniform_values,
1722 keys,
1723 values,
1724 })
1725}
1726
1727fn infer_key_type(value: &Value, builtin: &'static str) -> BuiltinResult<KeyType> {
1728 match value {
1729 Value::CharArray(_) | Value::StringArray(_) | Value::String(_) => Ok(KeyType::Char),
1730 Value::Cell(cell)
1731 if cell.data.iter().all(|value| {
1732 matches!(value, Value::CharArray(_) | Value::String(_) | Value::StringArray(_))
1733 }) =>
1734 {
1735 Ok(KeyType::Char)
1736 }
1737 Value::Num(_) => Ok(KeyType::Double),
1738 Value::Tensor(tensor) => key_type_from_dtype(tensor.numeric_dtype()).ok_or_else(|| {
1739 map_error(
1740 "containers.Map: numeric key arrays must be double, single, int32, uint32, int64, or uint64",
1741 builtin,
1742 )
1743 }),
1744 Value::Int(value) => key_type_from_dtype(int_value_dtype(value)).ok_or_else(|| {
1745 map_error(
1746 "containers.Map: integer keys must be int32, uint32, int64, or uint64",
1747 builtin,
1748 )
1749 }),
1750 Value::GpuTensor(handle) => {
1751 if let Some(integer_type) = runmat_accelerate_api::handle_integer_type(handle) {
1752 match integer_type {
1753 runmat_accelerate_api::IntegerElementType::I32 => Ok(KeyType::Int32),
1754 runmat_accelerate_api::IntegerElementType::U32 => Ok(KeyType::UInt32),
1755 runmat_accelerate_api::IntegerElementType::I64 => Ok(KeyType::Int64),
1756 runmat_accelerate_api::IntegerElementType::U64 => Ok(KeyType::UInt64),
1757 _ => Err(map_error(
1758 "containers.Map: resident integer keys must be int32, uint32, int64, or uint64",
1759 builtin,
1760 )),
1761 }
1762 } else if runmat_accelerate_api::handle_precision(handle)
1763 == Some(runmat_accelerate_api::ProviderPrecision::F32)
1764 {
1765 Ok(KeyType::Single)
1766 } else {
1767 Ok(KeyType::Double)
1768 }
1769 }
1770 _ => Err(map_error(
1771 "containers.Map: keys must be a numeric array, cell array of character vectors, or string array",
1772 builtin,
1773 )),
1774 }
1775}
1776
1777fn key_type_from_dtype(dtype: NumericDType) -> Option<KeyType> {
1778 match dtype {
1779 NumericDType::F64 => Some(KeyType::Double),
1780 NumericDType::F32 => Some(KeyType::Single),
1781 NumericDType::I32 => Some(KeyType::Int32),
1782 NumericDType::U32 => Some(KeyType::UInt32),
1783 NumericDType::I64 => Some(KeyType::Int64),
1784 NumericDType::U64 => Some(KeyType::UInt64),
1785 _ => None,
1786 }
1787}
1788
1789fn int_value_dtype(value: &IntValue) -> NumericDType {
1790 IntegerStorage::from_scalar(value.clone()).numeric_dtype()
1791}
1792
1793fn infer_value_type(value: &Value) -> ValueType {
1794 match value {
1795 Value::Num(_) => ValueType::Double,
1796 Value::Tensor(tensor) => value_type_from_dtype(tensor.numeric_dtype()),
1797 Value::Int(value) => value_type_from_dtype(int_value_dtype(value)),
1798 Value::Bool(_) | Value::LogicalArray(_) => ValueType::Logical,
1799 Value::CharArray(_) => ValueType::Char,
1800 Value::GpuTensor(handle) => {
1801 if runmat_accelerate_api::handle_is_logical(handle) {
1802 ValueType::Logical
1803 } else if let Some(integer_type) = runmat_accelerate_api::handle_integer_type(handle) {
1804 match integer_type {
1805 runmat_accelerate_api::IntegerElementType::I8 => ValueType::Int8,
1806 runmat_accelerate_api::IntegerElementType::U8 => ValueType::UInt8,
1807 runmat_accelerate_api::IntegerElementType::I16 => ValueType::Int16,
1808 runmat_accelerate_api::IntegerElementType::U16 => ValueType::UInt16,
1809 runmat_accelerate_api::IntegerElementType::I32 => ValueType::Int32,
1810 runmat_accelerate_api::IntegerElementType::U32 => ValueType::UInt32,
1811 runmat_accelerate_api::IntegerElementType::I64 => ValueType::Int64,
1812 runmat_accelerate_api::IntegerElementType::U64 => ValueType::UInt64,
1813 }
1814 } else if runmat_accelerate_api::handle_precision(handle)
1815 == Some(runmat_accelerate_api::ProviderPrecision::F32)
1816 {
1817 ValueType::Single
1818 } else {
1819 ValueType::Double
1820 }
1821 }
1822 Value::Cell(cell) => {
1823 let mut iter = cell.data.iter();
1824 let Some(first) = iter.next() else {
1825 return ValueType::Any;
1826 };
1827 let inferred = infer_value_type(first);
1828 if inferred != ValueType::Any && iter.all(|value| infer_value_type(value) == inferred) {
1829 inferred
1830 } else {
1831 ValueType::Any
1832 }
1833 }
1834 _ => ValueType::Any,
1835 }
1836}
1837
1838fn value_type_from_dtype(dtype: NumericDType) -> ValueType {
1839 match dtype {
1840 NumericDType::F64 => ValueType::Double,
1841 NumericDType::F32 => ValueType::Single,
1842 NumericDType::I8 => ValueType::Int8,
1843 NumericDType::U8 => ValueType::UInt8,
1844 NumericDType::I16 => ValueType::Int16,
1845 NumericDType::U16 => ValueType::UInt16,
1846 NumericDType::I32 => ValueType::Int32,
1847 NumericDType::U32 => ValueType::UInt32,
1848 NumericDType::I64 => ValueType::Int64,
1849 NumericDType::U64 => ValueType::UInt64,
1850 }
1851}
1852
1853fn build_store(args: ConstructorArgs, builtin: &'static str) -> BuiltinResult<MapStore> {
1854 let mut store = MapStore::new(args.key_type, args.value_type, args.uniform_values);
1855 for (candidate, value) in args.keys.into_iter().zip(args.values.into_iter()) {
1856 store.insert_new(
1857 MapEntry {
1858 normalized: candidate.normalized,
1859 key_value: candidate.canonical,
1860 value,
1861 },
1862 builtin,
1863 )?;
1864 }
1865 Ok(store)
1866}
1867
1868fn allocate_handle(store: MapStore, builtin: &'static str) -> BuiltinResult<Value> {
1869 ensure_containers_map_class_registered();
1870
1871 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
1872 ensure_map_registry_root_registered(builtin)?;
1873 MAP_REGISTRY.with(|registry| {
1874 registry
1875 .try_borrow_mut()
1876 .map_err(|_| map_internal("containers.Map: registry is already borrowed", builtin))?
1877 .insert(id, store);
1878 Ok::<(), RuntimeError>(())
1879 })?;
1880 let mut storage = ObjectInstance::new(CLASS_NAME.to_string());
1881 storage
1882 .properties
1883 .insert("id".to_string(), Value::Int(IntValue::U64(id)));
1884 let gc = match runmat_gc::gc_allocate(Value::Object(storage)) {
1885 Ok(gc) => gc,
1886 Err(e) => {
1887 MAP_REGISTRY.with(|registry| {
1888 if let Ok(mut registry) = registry.try_borrow_mut() {
1889 registry.remove(&id);
1890 }
1891 });
1892 deactivate_map_registry_root_if_empty();
1893 return Err(map_error(format!("containers.Map: {e}"), builtin));
1894 }
1895 };
1896 MAP_REGISTRY.with(|registry| {
1897 let mut registry = registry
1898 .try_borrow_mut()
1899 .map_err(|_| map_internal("containers.Map: registry is already borrowed", builtin))?;
1900 let store = registry
1901 .get_mut(&id)
1902 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1903 store.storage = Some(gc);
1904 Ok::<(), RuntimeError>(())
1905 })?;
1906 Ok(Value::HandleObject(HandleRef {
1907 class_name: CLASS_NAME.to_string(),
1908 target: gc,
1909 valid: true,
1910 }))
1911}
1912
1913fn with_store<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
1914where
1915 F: FnOnce(&MapStore) -> BuiltinResult<R>,
1916{
1917 let handle = extract_handle(map, builtin)?;
1918 ensure_handle(handle, builtin)?;
1919 let id = map_id(handle, builtin)?;
1920 MAP_REGISTRY.with(|registry| {
1921 let registry = registry
1922 .try_borrow()
1923 .map_err(|_| map_internal("containers.Map: registry already borrowed", builtin))?;
1924 let store = registry
1925 .get(&id)
1926 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1927 f(store)
1928 })
1929}
1930
1931fn with_store_mut<F, R>(map: &Value, builtin: &'static str, f: F) -> BuiltinResult<R>
1932where
1933 F: FnOnce(&mut MapStore) -> BuiltinResult<R>,
1934{
1935 let handle = extract_handle(map, builtin)?;
1936 ensure_handle(handle, builtin)?;
1937 let id = map_id(handle, builtin)?;
1938 MAP_REGISTRY.with(|registry| {
1939 let mut registry = registry
1940 .try_borrow_mut()
1941 .map_err(|_| map_internal("containers.Map: registry already borrowed", builtin))?;
1942 let store = registry
1943 .get_mut(&id)
1944 .ok_or_else(|| map_internal("containers.Map: internal storage not found", builtin))?;
1945 f(store)
1946 })
1947}
1948
1949fn extract_handle<'a>(value: &'a Value, builtin: &'static str) -> BuiltinResult<&'a HandleRef> {
1950 match value {
1951 Value::HandleObject(handle) => Ok(handle),
1952 _ => Err(map_error(
1953 "containers.Map: expected a containers.Map handle",
1954 builtin,
1955 )),
1956 }
1957}
1958
1959fn ensure_handle(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<()> {
1960 if !crate::is_handle_valid(handle) {
1961 return Err(map_error("containers.Map: handle is invalid", builtin));
1962 }
1963 if handle.class_name != CLASS_NAME {
1964 return Err(map_error(
1965 format!(
1966 "containers.Map: expected handle of class '{}', got '{}'",
1967 CLASS_NAME, handle.class_name
1968 ),
1969 builtin,
1970 ));
1971 }
1972 Ok(())
1973}
1974
1975fn map_id(handle: &HandleRef, builtin: &'static str) -> BuiltinResult<u64> {
1976 let storage = runmat_gc::gc_clone_value(&handle.target).map_err(|e| {
1977 map_internal(
1978 format!("containers.Map: invalid handle storage: {e}"),
1979 builtin,
1980 )
1981 })?;
1982 let id_value = match &storage {
1983 Value::Object(object) if object.class_name == CLASS_NAME => object.properties.get("id"),
1984 Value::Struct(StructValue { fields }) => fields.get("id"),
1985 other => {
1986 return Err(map_internal(
1987 format!("containers.Map: internal storage has unexpected shape {other:?}"),
1988 builtin,
1989 ));
1990 }
1991 };
1992 match id_value {
1993 Some(Value::Int(IntValue::U64(id))) => Ok(*id),
1994 Some(Value::Int(other)) => {
1995 let id = other.to_i64();
1996 if id < 0 {
1997 Err(map_internal(
1998 "containers.Map: negative map identifier",
1999 builtin,
2000 ))
2001 } else {
2002 Ok(id as u64)
2003 }
2004 }
2005 Some(Value::Num(n)) if n.is_finite() && *n >= 0.0 && n.fract() == 0.0 => {
2006 if *n >= u64::MAX as f64 {
2007 Err(map_internal(
2008 "containers.Map: map identifier out of range",
2009 builtin,
2010 ))
2011 } else {
2012 Ok(*n as u64)
2013 }
2014 }
2015 _ => Err(map_internal(
2016 "containers.Map: corrupted storage identifier",
2017 builtin,
2018 )),
2019 }
2020}
2021
2022async fn prepare_keys(
2023 value: Value,
2024 key_type: KeyType,
2025 builtin: &'static str,
2026) -> BuiltinResult<Vec<KeyCandidate>> {
2027 let host = gather_if_needed_async(&value)
2028 .await
2029 .map_err(|err| attach_builtin_context(err, builtin))?;
2030 let flattened = flatten_keys(&host, key_type, builtin).await?;
2031 let mut out = Vec::with_capacity(flattened.len());
2032 for raw_key in flattened {
2033 let (normalized, canonical) = canonicalize_key(raw_key, key_type, builtin)?;
2034 out.push(KeyCandidate {
2035 normalized,
2036 canonical,
2037 });
2038 }
2039 Ok(out)
2040}
2041
2042async fn prepare_values(value: Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
2043 let host = gather_if_needed_async(&value)
2044 .await
2045 .map_err(|err| attach_builtin_context(err, builtin))?;
2046 flatten_values(&host, builtin).await
2047}
2048
2049async fn flatten_keys(
2050 value: &Value,
2051 _key_type: KeyType,
2052 builtin: &'static str,
2053) -> BuiltinResult<Vec<Value>> {
2054 match value {
2055 Value::Cell(cell) => {
2056 let mut out = Vec::with_capacity(cell.data.len());
2057 for ptr in &cell.data {
2058 let element = ptr;
2059 if matches!(element, Value::Cell(_)) {
2060 return Err(map_error(
2061 "containers.Map: nested cell arrays are not supported for keys",
2062 builtin,
2063 ));
2064 }
2065 out.push(
2066 gather_if_needed_async(element)
2067 .await
2068 .map_err(|err| attach_builtin_context(err, builtin))?,
2069 );
2070 }
2071 Ok(out)
2072 }
2073 Value::StringArray(sa) => Ok(sa
2074 .data
2075 .iter()
2076 .map(|text| Value::String(text.clone()))
2077 .collect()),
2078 Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
2079 Value::LogicalArray(_) => Err(map_error(
2080 "containers.Map: logical arrays are not supported as Map keys",
2081 builtin,
2082 )),
2083 Value::Tensor(t) => {
2084 if !t.shape.is_empty()
2085 && tensor::tensor_element_len(t) != 1
2086 && !is_vector_shape(&t.shape)
2087 {
2088 return Err(map_error(
2089 "containers.Map: numeric keys must be scalar or vector shaped",
2090 builtin,
2091 ));
2092 }
2093 Ok(tensor_elements_to_values(t))
2094 }
2095 Value::Num(_) | Value::Int(_) | Value::Bool(_) | Value::String(_) => {
2096 Ok(vec![value.clone()])
2097 }
2098 Value::GpuTensor(_) => Err(map_error(
2099 "containers.Map: GPU keys must be gathered to the host before construction",
2100 builtin,
2101 )),
2102 other => Err(map_error(
2103 format!("containers.Map: unsupported key container {other:?}"),
2104 builtin,
2105 )),
2106 }
2107}
2108
2109async fn flatten_values(value: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
2110 match value {
2111 Value::Cell(cell) => {
2112 let mut out = Vec::with_capacity(cell.data.len());
2113 for ptr in &cell.data {
2114 out.push(
2115 gather_if_needed_async(ptr)
2116 .await
2117 .map_err(|err| attach_builtin_context(err, builtin))?,
2118 );
2119 }
2120 Ok(out)
2121 }
2122 Value::StringArray(sa) => Ok(sa
2123 .data
2124 .iter()
2125 .map(|text| Value::String(text.clone()))
2126 .collect()),
2127 Value::CharArray(ca) => Ok(char_array_rows(ca, builtin)?),
2128 Value::LogicalArray(arr) => Ok(arr.data.iter().map(|&b| Value::Bool(b != 0)).collect()),
2129 Value::Tensor(t) => {
2130 if !t.shape.is_empty()
2131 && !is_vector_shape(&t.shape)
2132 && tensor::tensor_element_len(t) != 1
2133 {
2134 return Err(map_error(
2135 "containers.Map: numeric values must be scalar or vector shaped",
2136 builtin,
2137 ));
2138 }
2139 Ok(tensor_elements_to_values(t))
2140 }
2141 _ => Ok(vec![value.clone()]),
2142 }
2143}
2144
2145fn char_array_rows(ca: &CharArray, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
2146 if ca.rows == 0 {
2147 return Ok(Vec::new());
2148 }
2149 let mut out = Vec::with_capacity(ca.rows);
2150 for row in 0..ca.rows {
2151 let mut text = String::with_capacity(ca.cols);
2152 for col in 0..ca.cols {
2153 text.push(ca.data[row * ca.cols + col]);
2154 }
2155 let chars: Vec<char> = text.chars().collect();
2156 let array = CharArray::new(chars.clone(), 1, chars.len())
2157 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
2158 out.push(Value::CharArray(array));
2159 }
2160 Ok(out)
2161}
2162
2163fn is_vector_shape(shape: &[usize]) -> bool {
2164 match shape.len() {
2165 0 => true,
2166 1 => true,
2167 2 => shape[0] == 1 || shape[1] == 1,
2168 _ => false,
2169 }
2170}
2171
2172fn tensor_elements_to_values(tensor: &Tensor) -> Vec<Value> {
2173 if let Some(storage) = tensor.integer_storage() {
2174 return storage.exact_values().into_iter().map(Value::Int).collect();
2175 }
2176 match tensor.numeric_dtype() {
2177 NumericDType::F64 => tensor
2178 .as_f64_slice()
2179 .expect("double tensor has native double storage")
2180 .iter()
2181 .copied()
2182 .map(Value::Num)
2183 .collect(),
2184 NumericDType::F32 => (0..tensor.len())
2185 .map(|index| {
2186 let value = match tensor.numeric_value_at(index) {
2187 Some(runmat_value::NumericScalar::F32(value)) => value,
2188 _ => unreachable!("single tensor has native single storage"),
2189 };
2190 Value::Tensor(
2191 Tensor::from_f32(vec![value], vec![1, 1])
2192 .expect("single scalar tensor construction"),
2193 )
2194 })
2195 .collect(),
2196 _ => unreachable!("integer tensor returned through exact key/value path"),
2197 }
2198}
2199
2200fn canonicalize_key(
2201 value: Value,
2202 key_type: KeyType,
2203 builtin: &'static str,
2204) -> BuiltinResult<(NormalizedKey, Value)> {
2205 let normalized = normalize_key(&value, key_type, builtin)?;
2206 let canonical = match key_type {
2207 KeyType::Char => Value::CharArray(char_array_from_value(&value, builtin)?),
2208 KeyType::Double => Value::Num(numeric_from_value(
2209 &value,
2210 "containers.Map: keys must be numeric scalars",
2211 builtin,
2212 )?),
2213 KeyType::Single => Value::Tensor(
2214 Tensor::from_f32(
2215 vec![numeric_from_value(
2216 &value,
2217 "containers.Map: keys must be numeric scalars",
2218 builtin,
2219 )? as f32],
2220 vec![1, 1],
2221 )
2222 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin))?,
2223 ),
2224 KeyType::Int32 => Value::Int(IntValue::I32(integer_from_value(
2225 &value,
2226 i32::MIN as i64,
2227 i32::MAX as i64,
2228 "containers.Map: int32 keys must be integers",
2229 builtin,
2230 )? as i32)),
2231 KeyType::UInt32 => Value::Int(IntValue::U32(unsigned_from_value(
2232 &value,
2233 u32::MAX as u64,
2234 "containers.Map: uint32 keys must be unsigned integers",
2235 builtin,
2236 )? as u32)),
2237 KeyType::Int64 => Value::Int(IntValue::I64(integer_from_value(
2238 &value,
2239 i64::MIN,
2240 i64::MAX,
2241 "containers.Map: int64 keys must be integers",
2242 builtin,
2243 )?)),
2244 KeyType::UInt64 => Value::Int(IntValue::U64(unsigned_from_value(
2245 &value,
2246 u64::MAX,
2247 "containers.Map: uint64 keys must be unsigned integers",
2248 builtin,
2249 )?)),
2250 };
2251 Ok((normalized, canonical))
2252}
2253
2254fn normalize_key(
2255 value: &Value,
2256 key_type: KeyType,
2257 builtin: &'static str,
2258) -> BuiltinResult<NormalizedKey> {
2259 if !value_matches_key_type(value, key_type) {
2260 return Err(map_error(
2261 format!(
2262 "containers.Map: key class must match the map KeyType '{}'",
2263 key_type.matlab_name()
2264 ),
2265 builtin,
2266 ));
2267 }
2268 match key_type {
2269 KeyType::Char => {
2270 let text =
2271 string_from_value(value, "containers.Map: keys must be text scalars", builtin)?;
2272 Ok(NormalizedKey::String(text))
2273 }
2274 KeyType::Double | KeyType::Single => {
2275 let numeric = numeric_from_value(
2276 value,
2277 "containers.Map: keys must be numeric scalars",
2278 builtin,
2279 )?;
2280 if !numeric.is_finite() {
2281 return Err(map_error(
2282 "containers.Map: keys must be finite numeric scalars",
2283 builtin,
2284 ));
2285 }
2286 let numeric = if key_type == KeyType::Single {
2287 f64::from(numeric as f32)
2288 } else {
2289 numeric
2290 };
2291 let canonical = if numeric == 0.0 { 0.0 } else { numeric };
2292 Ok(NormalizedKey::Float(canonical.to_bits()))
2293 }
2294 KeyType::Int32 | KeyType::Int64 => {
2295 let bounds = if key_type == KeyType::Int32 {
2296 (i32::MIN as i64, i32::MAX as i64)
2297 } else {
2298 (i64::MIN, i64::MAX)
2299 };
2300 let value = integer_from_value(
2301 value,
2302 bounds.0,
2303 bounds.1,
2304 "containers.Map: integer keys must be whole numbers",
2305 builtin,
2306 )?;
2307 Ok(NormalizedKey::Int(value))
2308 }
2309 KeyType::UInt32 | KeyType::UInt64 => {
2310 let limit = if key_type == KeyType::UInt32 {
2311 u32::MAX as u64
2312 } else {
2313 u64::MAX
2314 };
2315 let value = unsigned_from_value(
2316 value,
2317 limit,
2318 "containers.Map: unsigned keys must be non-negative integers",
2319 builtin,
2320 )?;
2321 Ok(NormalizedKey::UInt(value))
2322 }
2323 }
2324}
2325
2326fn value_matches_key_type(value: &Value, key_type: KeyType) -> bool {
2327 match key_type {
2328 KeyType::Char => {
2329 matches!(
2330 value,
2331 Value::CharArray(chars) if chars.rows == 1
2332 ) || matches!(value, Value::String(_))
2333 || matches!(value, Value::StringArray(strings) if strings.data.len() == 1)
2334 }
2335 KeyType::Double => {
2336 matches!(value, Value::Num(_))
2337 || matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::F64)
2338 }
2339 KeyType::Single => {
2340 matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::F32)
2341 }
2342 KeyType::Int32 => {
2343 matches!(value, Value::Int(IntValue::I32(_)))
2344 || matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::I32)
2345 }
2346 KeyType::UInt32 => {
2347 matches!(value, Value::Int(IntValue::U32(_)))
2348 || matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::U32)
2349 }
2350 KeyType::Int64 => {
2351 matches!(value, Value::Int(IntValue::I64(_)))
2352 || matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::I64)
2353 }
2354 KeyType::UInt64 => {
2355 matches!(value, Value::Int(IntValue::U64(_)))
2356 || matches!(value, Value::Tensor(tensor) if tensor.len() == 1 && tensor.numeric_dtype() == NumericDType::U64)
2357 }
2358 }
2359}
2360
2361fn string_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<String> {
2362 match value {
2363 Value::String(s) => Ok(s.clone()),
2364 Value::StringArray(sa) if sa.data.len() == 1 => Ok(sa.data[0].clone()),
2365 Value::CharArray(ca) if ca.rows == 1 => Ok(ca.data.iter().collect()),
2366 _ => Err(map_error(context, builtin)),
2367 }
2368}
2369
2370fn char_array_from_value(value: &Value, builtin: &'static str) -> BuiltinResult<CharArray> {
2371 match value {
2372 Value::CharArray(ca) if ca.rows == 1 => Ok(ca.clone()),
2373 Value::String(s) => {
2374 let chars: Vec<char> = s.chars().collect();
2375 CharArray::new(chars.clone(), 1, chars.len())
2376 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
2377 }
2378 Value::StringArray(sa) if sa.data.len() == 1 => {
2379 let chars: Vec<char> = sa.data[0].chars().collect();
2380 CharArray::new(chars.clone(), 1, chars.len())
2381 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
2382 }
2383 _ => Err(map_error(
2384 "containers.Map: keys must be character vectors",
2385 builtin,
2386 )),
2387 }
2388}
2389
2390fn char_array_value(text: &str, builtin: &'static str) -> BuiltinResult<Value> {
2391 let chars: Vec<char> = text.chars().collect();
2392 CharArray::new(chars.clone(), 1, chars.len())
2393 .map(Value::CharArray)
2394 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
2395}
2396
2397fn normalize_numeric_value(
2398 value: Value,
2399 dtype: NumericDType,
2400 builtin: &'static str,
2401) -> BuiltinResult<Value> {
2402 match value {
2403 Value::Num(value) if dtype == NumericDType::F64 => Ok(Value::Num(value)),
2404 Value::Num(value) => Tensor::from_f32(vec![value as f32], vec![1, 1])
2405 .map(Value::Tensor)
2406 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin)),
2407 Value::Tensor(tensor) if tensor.len() == 1 => {
2408 Ok(Value::Tensor(tensor::coerce_tensor_dtype(tensor, dtype)))
2409 }
2410 Value::Int(value) => normalize_numeric_value(Value::Num(value.to_f64()), dtype, builtin),
2411 Value::Bool(value) => {
2412 normalize_numeric_value(Value::Num(if value { 1.0 } else { 0.0 }), dtype, builtin)
2413 }
2414 Value::LogicalArray(arr) if arr.data.len() == 1 => {
2415 let data: Vec<f64> = arr
2416 .data
2417 .iter()
2418 .map(|&b| if b != 0 { 1.0 } else { 0.0 })
2419 .collect();
2420 let tensor = Tensor::new(data, arr.shape.clone())
2421 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
2422 Ok(Value::Tensor(tensor::coerce_tensor_dtype(tensor, dtype)))
2423 }
2424 Value::Cell(_)
2425 | Value::SparseTensor(_)
2426 | Value::Struct(_)
2427 | Value::ObjectArray(_)
2428 | Value::Object(_)
2429 | Value::HandleObject(_)
2430 | Value::Listener(_)
2431 | Value::String(_)
2432 | Value::StringArray(_)
2433 | Value::CharArray(_)
2434 | Value::Complex(_, _)
2435 | Value::ComplexTensor(_)
2436 | Value::Symbolic(_)
2437 | Value::SymbolicArray(_)
2438 | Value::FunctionHandle(_)
2439 | Value::ExternalFunctionHandle(_)
2440 | Value::MethodFunctionHandle(_)
2441 | Value::BoundFunctionHandle { .. }
2442 | Value::Closure(_)
2443 | Value::ClassRef(_)
2444 | Value::MException(_)
2445 | Value::Future(_)
2446 | Value::Task(_)
2447 | Value::Pool(_)
2448 | Value::Job(_)
2449 | Value::Foreign(_)
2450 | Value::GpuTensor(_)
2451 | Value::OutputList(_)
2452 | Value::Tensor(_)
2453 | Value::LogicalArray(_) => Err(map_error(
2454 "containers.Map: values must be numeric scalars when ValueType is 'double' or 'single'",
2455 builtin,
2456 )),
2457 }
2458}
2459
2460fn normalize_logical_value(value: Value, builtin: &'static str) -> BuiltinResult<Value> {
2461 match value {
2462 Value::Bool(_) => Ok(value),
2463 Value::LogicalArray(ref array) if array.data.len() == 1 => Ok(value),
2464 Value::Int(i) => Ok(Value::Bool(!i.is_zero())),
2465 Value::Num(n) => Ok(Value::Bool(n != 0.0)),
2466 Value::Tensor(t) if t.len() == 1 => {
2467 let flags: Vec<u8> = if let Some(storage) = t.integer_storage() {
2468 storage
2469 .exact_values()
2470 .into_iter()
2471 .map(|value| if value.is_zero() { 0 } else { 1 })
2472 .collect()
2473 } else {
2474 (0..t.len())
2475 .map(|index| {
2476 if tensor::tensor_value_f64(&t, index) != 0.0 {
2477 1
2478 } else {
2479 0
2480 }
2481 })
2482 .collect()
2483 };
2484 let logical = LogicalArray::new(flags, t.shape.clone())
2485 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))?;
2486 Ok(Value::LogicalArray(logical))
2487 }
2488 Value::CharArray(_)
2489 | Value::SparseTensor(_)
2490 | Value::String(_)
2491 | Value::StringArray(_)
2492 | Value::Struct(_)
2493 | Value::Cell(_)
2494 | Value::ObjectArray(_)
2495 | Value::Object(_)
2496 | Value::HandleObject(_)
2497 | Value::Listener(_)
2498 | Value::Complex(_, _)
2499 | Value::ComplexTensor(_)
2500 | Value::Symbolic(_)
2501 | Value::SymbolicArray(_)
2502 | Value::FunctionHandle(_)
2503 | Value::ExternalFunctionHandle(_)
2504 | Value::MethodFunctionHandle(_)
2505 | Value::BoundFunctionHandle { .. }
2506 | Value::Closure(_)
2507 | Value::ClassRef(_)
2508 | Value::MException(_)
2509 | Value::Future(_)
2510 | Value::Task(_)
2511 | Value::Pool(_)
2512 | Value::Job(_)
2513 | Value::Foreign(_)
2514 | Value::GpuTensor(_)
2515 | Value::OutputList(_)
2516 | Value::Tensor(_)
2517 | Value::LogicalArray(_) => Err(map_error(
2518 "containers.Map: values must be logical scalars when ValueType is 'logical'",
2519 builtin,
2520 )),
2521 }
2522}
2523
2524fn integer_dtype_for_value_type(value_type: ValueType) -> Option<NumericDType> {
2525 match value_type {
2526 ValueType::Int8 => Some(NumericDType::I8),
2527 ValueType::UInt8 => Some(NumericDType::U8),
2528 ValueType::Int16 => Some(NumericDType::I16),
2529 ValueType::UInt16 => Some(NumericDType::U16),
2530 ValueType::Int32 => Some(NumericDType::I32),
2531 ValueType::UInt32 => Some(NumericDType::U32),
2532 ValueType::Int64 => Some(NumericDType::I64),
2533 ValueType::UInt64 => Some(NumericDType::U64),
2534 _ => None,
2535 }
2536}
2537
2538fn normalize_integer_value(
2539 value: Value,
2540 value_type: ValueType,
2541 builtin: &'static str,
2542) -> BuiltinResult<Value> {
2543 let dtype = integer_dtype_for_value_type(value_type)
2544 .ok_or_else(|| map_internal("containers.Map: invalid integer ValueType", builtin))?;
2545 let (tensor, scalar) = match value {
2546 Value::Tensor(tensor) if tensor.len() == 1 => (tensor, true),
2547 Value::Int(value) => (
2548 Tensor::new_integer(integer_storage_from_scalar(value), vec![1, 1])
2549 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin))?,
2550 true,
2551 ),
2552 Value::Num(value) => (
2553 Tensor::new(vec![value], vec![1, 1])
2554 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin))?,
2555 true,
2556 ),
2557 Value::Bool(value) => (
2558 Tensor::new(vec![if value { 1.0 } else { 0.0 }], vec![1, 1])
2559 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin))?,
2560 true,
2561 ),
2562 Value::LogicalArray(array) if array.data.len() == 1 => (
2563 Tensor::new(
2564 array
2565 .data
2566 .iter()
2567 .map(|value| f64::from(*value != 0))
2568 .collect(),
2569 array.shape,
2570 )
2571 .map_err(|err| map_error(format!("containers.Map: {err}"), builtin))?,
2572 true,
2573 ),
2574 _ => {
2575 return Err(map_error(
2576 "containers.Map: value cannot be converted to the declared integer ValueType",
2577 builtin,
2578 ))
2579 }
2580 };
2581 let converted = tensor::coerce_tensor_dtype(tensor, dtype);
2582 if scalar {
2583 let value = converted
2584 .integer_storage()
2585 .and_then(|storage| storage.value_at(0))
2586 .ok_or_else(|| {
2587 map_internal("containers.Map: integer scalar conversion failed", builtin)
2588 })?;
2589 Ok(Value::Int(value))
2590 } else {
2591 Ok(Value::Tensor(converted))
2592 }
2593}
2594
2595fn integer_storage_from_scalar(value: IntValue) -> IntegerStorage {
2596 match value {
2597 IntValue::I8(value) => IntegerStorage::I8(vec![value]),
2598 IntValue::I16(value) => IntegerStorage::I16(vec![value]),
2599 IntValue::I32(value) => IntegerStorage::I32(vec![value]),
2600 IntValue::I64(value) => IntegerStorage::I64(vec![value]),
2601 IntValue::U8(value) => IntegerStorage::U8(vec![value]),
2602 IntValue::U16(value) => IntegerStorage::U16(vec![value]),
2603 IntValue::U32(value) => IntegerStorage::U32(vec![value]),
2604 IntValue::U64(value) => IntegerStorage::U64(vec![value]),
2605 }
2606}
2607
2608fn numeric_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<f64> {
2609 match value {
2610 Value::Num(n) => Ok(*n),
2611 Value::Int(i) => Ok(i.to_f64()),
2612 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
2613 Value::Tensor(t) if tensor::is_scalar_tensor(t) => Ok(tensor::tensor_value_f64(t, 0)),
2614 Value::LogicalArray(arr) if arr.data.len() == 1 => {
2615 Ok(if arr.data[0] != 0 { 1.0 } else { 0.0 })
2616 }
2617 _ => Err(map_error(context, builtin)),
2618 }
2619}
2620
2621fn integer_from_value(
2622 value: &Value,
2623 min: i64,
2624 max: i64,
2625 context: &str,
2626 builtin: &'static str,
2627) -> BuiltinResult<i64> {
2628 match value {
2629 Value::Int(i) => {
2630 let Some(v) = i.try_to_i64() else {
2631 return Err(map_error(context, builtin));
2632 };
2633 if v < min || v > max {
2634 return Err(map_error(context, builtin));
2635 }
2636 Ok(v)
2637 }
2638 Value::Tensor(t) if tensor::is_scalar_tensor(t) => {
2639 if let Some(storage) = t.integer_storage() {
2640 let Some(v) = storage.value_at(0).and_then(|value| value.try_to_i64()) else {
2641 return Err(map_error(context, builtin));
2642 };
2643 if v < min || v > max {
2644 return Err(map_error(context, builtin));
2645 }
2646 Ok(v)
2647 } else {
2648 integer_from_value(
2649 &Value::Num(tensor::tensor_value_f64(t, 0)),
2650 min,
2651 max,
2652 context,
2653 builtin,
2654 )
2655 }
2656 }
2657 Value::Num(n) => {
2658 if !n.is_finite() {
2659 return Err(map_error(context, builtin));
2660 }
2661 if (*n < min as f64) || (*n > max as f64) {
2662 return Err(map_error(context, builtin));
2663 }
2664 if (n.round() - n).abs() > f64::EPSILON {
2665 return Err(map_error(context, builtin));
2666 }
2667 Ok(n.round() as i64)
2668 }
2669 Value::Bool(b) => {
2670 let v = if *b { 1 } else { 0 };
2671 if v < min || v > max {
2672 return Err(map_error(context, builtin));
2673 }
2674 Ok(v)
2675 }
2676 _ => Err(map_error(context, builtin)),
2677 }
2678}
2679
2680fn unsigned_from_value(
2681 value: &Value,
2682 max: u64,
2683 context: &str,
2684 builtin: &'static str,
2685) -> BuiltinResult<u64> {
2686 match value {
2687 Value::Int(i) => {
2688 let Some(v) = i.try_to_u64() else {
2689 return Err(map_error(context, builtin));
2690 };
2691 if v > max {
2692 return Err(map_error(context, builtin));
2693 }
2694 Ok(v)
2695 }
2696 Value::Tensor(t) if tensor::is_scalar_tensor(t) => {
2697 if let Some(storage) = t.integer_storage() {
2698 let Some(v) = storage.value_at(0).and_then(|value| value.try_to_u64()) else {
2699 return Err(map_error(context, builtin));
2700 };
2701 if v > max {
2702 return Err(map_error(context, builtin));
2703 }
2704 Ok(v)
2705 } else {
2706 unsigned_from_value(
2707 &Value::Num(tensor::tensor_value_f64(t, 0)),
2708 max,
2709 context,
2710 builtin,
2711 )
2712 }
2713 }
2714 Value::Num(n) => {
2715 if !n.is_finite() || *n < 0.0 || *n > max as f64 {
2716 return Err(map_error(context, builtin));
2717 }
2718 if (n.round() - n).abs() > f64::EPSILON {
2719 return Err(map_error(context, builtin));
2720 }
2721 Ok(n.round() as u64)
2722 }
2723 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
2724 _ => Err(map_error(context, builtin)),
2725 }
2726}
2727
2728fn bool_from_value(value: &Value, context: &str, builtin: &'static str) -> BuiltinResult<bool> {
2729 match value {
2730 Value::Bool(b) => Ok(*b),
2731 Value::LogicalArray(arr) if arr.data.len() == 1 => Ok(arr.data[0] != 0),
2732 Value::Int(i) => Ok(!i.is_zero()),
2733 Value::Num(n) => Ok(*n != 0.0),
2734 Value::Tensor(t) if tensor::is_scalar_tensor(t) => {
2735 if let Some(storage) = t.integer_storage() {
2736 Ok(!storage
2737 .value_at(0)
2738 .ok_or_else(|| map_error(context, builtin))?
2739 .is_zero())
2740 } else {
2741 Ok(tensor::tensor_value_f64(t, 0) != 0.0)
2742 }
2743 }
2744 _ => Err(map_error(context, builtin)),
2745 }
2746}
2747
2748fn make_row_cell(values: Vec<Value>, builtin: &'static str) -> BuiltinResult<Value> {
2749 let cols = values.len();
2750 crate::make_cell_with_shape(values, vec![1, cols])
2751 .map_err(|e| map_error(format!("containers.Map: {e}"), builtin))
2752}
2753
2754fn extract_key_arguments(payload: &Value, builtin: &'static str) -> BuiltinResult<Vec<Value>> {
2755 match payload {
2756 Value::Cell(cell) => {
2757 let mut out = Vec::with_capacity(cell.data.len());
2758 for ptr in &cell.data {
2759 out.push(ptr.clone());
2760 }
2761 Ok(out)
2762 }
2763 other => Err(map_error(
2764 format!("containers.Map: expected key arguments in a cell array, got {other:?}"),
2765 builtin,
2766 )),
2767 }
2768}
2769
2770async fn expand_assignment_values(
2771 value: Value,
2772 expected: usize,
2773 builtin: &'static str,
2774) -> BuiltinResult<Vec<Value>> {
2775 let host = gather_if_needed_async(&value)
2776 .await
2777 .map_err(|err| attach_builtin_context(err, builtin))?;
2778 if expected == 1 {
2779 Ok(vec![host])
2780 } else {
2781 let values = flatten_values(&host, builtin).await?;
2782 if values.len() != expected {
2783 return Err(map_error(
2784 format!(
2785 "containers.Map: assignment with {} keys requires {} values (got {})",
2786 expected,
2787 expected,
2788 values.len()
2789 ),
2790 builtin,
2791 ));
2792 }
2793 Ok(values)
2794 }
2795}
2796
2797struct KeyCollection {
2798 values: Vec<Value>,
2799 shape: Vec<usize>,
2800}
2801
2802async fn collect_key_spec(
2803 value: &Value,
2804 key_type: KeyType,
2805 builtin: &'static str,
2806) -> BuiltinResult<KeyCollection> {
2807 let host = gather_if_needed_async(value)
2808 .await
2809 .map_err(|err| attach_builtin_context(err, builtin))?;
2810 match &host {
2811 Value::Cell(cell) => {
2812 let mut values = Vec::with_capacity(cell.data.len());
2813 for ptr in &cell.data {
2814 values.push(
2815 gather_if_needed_async(ptr)
2816 .await
2817 .map_err(|err| attach_builtin_context(err, builtin))?,
2818 );
2819 }
2820 Ok(KeyCollection {
2821 values,
2822 shape: vec![cell.rows, cell.cols],
2823 })
2824 }
2825 Value::StringArray(sa) if sa.data.len() == 1 => Ok(KeyCollection {
2826 values: vec![Value::String(sa.data[0].clone())],
2827 shape: vec![1, 1],
2828 }),
2829 Value::CharArray(ca) if ca.rows == 1 => Ok(KeyCollection {
2830 values: vec![Value::CharArray(ca.clone())],
2831 shape: vec![1, 1],
2832 }),
2833 Value::Tensor(t) if key_type != KeyType::Char && t.len() == 1 => Ok(KeyCollection {
2834 values: tensor_elements_to_values(t),
2835 shape: vec![1, 1],
2836 }),
2837 Value::StringArray(_) | Value::CharArray(_) | Value::Tensor(_) => Err(map_error(
2838 "containers.Map: multiple keys must be supplied in a cell array",
2839 builtin,
2840 )),
2841 _ => Ok(KeyCollection {
2842 values: vec![host.clone()],
2843 shape: vec![1, 1],
2844 }),
2845 }
2846}
2847
2848pub fn map_length(value: &Value) -> Option<usize> {
2849 if let Value::HandleObject(handle) = value {
2850 if crate::is_handle_valid(handle) && handle.class_name == CLASS_NAME {
2851 if let Ok(id) = map_id(handle, BUILTIN_CONSTRUCTOR) {
2852 return MAP_REGISTRY.with(|registry| {
2853 registry
2854 .try_borrow()
2855 .ok()
2856 .and_then(|registry| registry.get(&id).map(|store| store.len()))
2857 });
2858 }
2859 }
2860 }
2861 None
2862}
2863
2864#[cfg(test)]
2865pub(crate) mod tests {
2866 use super::*;
2867 use crate::builtins::common::{gpu_helpers, test_support};
2868 use futures::executor::block_on;
2869 use runmat_builtins::{ResolveContext, Type};
2870 use runmat_value::IntegerStorage;
2871
2872 fn error_message(err: crate::RuntimeError) -> String {
2873 err.message.clone()
2874 }
2875
2876 fn containers_map_builtin(args: Vec<Value>) -> BuiltinResult<Value> {
2877 block_on(super::containers_map_builtin(args))
2878 }
2879
2880 fn containers_map_keys(map: Value) -> BuiltinResult<Value> {
2881 block_on(super::containers_map_keys(map))
2882 }
2883
2884 fn containers_map_values(map: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
2885 block_on(super::containers_map_values(map, rest))
2886 }
2887
2888 fn containers_map_is_key(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2889 block_on(super::containers_map_is_key(map, key_spec))
2890 }
2891
2892 fn containers_map_remove(map: Value, key_spec: Value) -> BuiltinResult<Value> {
2893 block_on(super::containers_map_remove(map, key_spec))
2894 }
2895
2896 fn containers_map_subsref(map: Value, kind: String, payload: Value) -> BuiltinResult<Value> {
2897 block_on(super::containers_map_subsref(map, kind, payload))
2898 }
2899
2900 fn containers_map_subsasgn(
2901 map: Value,
2902 kind: String,
2903 payload: Value,
2904 rhs: Value,
2905 ) -> BuiltinResult<Value> {
2906 block_on(super::containers_map_subsasgn(map, kind, payload, rhs))
2907 }
2908
2909 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2910 #[test]
2911 fn construct_empty_map_defaults() {
2912 let map = containers_map_builtin(Vec::new()).expect("map");
2913 let count = containers_map_subsref(
2914 map.clone(),
2915 ".".to_string(),
2916 Value::from("Count".to_string()),
2917 )
2918 .expect("Count");
2919 assert_eq!(count, Value::Int(IntValue::U64(0)));
2920
2921 let key_type = containers_map_subsref(
2922 map.clone(),
2923 ".".to_string(),
2924 Value::from("KeyType".to_string()),
2925 )
2926 .expect("KeyType");
2927 assert_eq!(
2928 key_type,
2929 Value::CharArray(CharArray::new("char".chars().collect(), 1, 4).unwrap())
2930 );
2931
2932 let value_type = containers_map_subsref(
2933 map.clone(),
2934 ".".to_string(),
2935 Value::from("ValueType".to_string()),
2936 )
2937 .expect("ValueType");
2938 assert_eq!(
2939 value_type,
2940 Value::CharArray(CharArray::new("any".chars().collect(), 1, 3).unwrap())
2941 );
2942 }
2943
2944 #[test]
2945 fn map_type_resolvers_basics() {
2946 let ctx = ResolveContext::new(Vec::new());
2947 assert_eq!(map_handle_type(&[Type::Unknown], &ctx), Type::Unknown);
2948 assert_eq!(map_cell_type(&[], &ctx), Type::cell());
2949 assert_eq!(map_is_key_type(&[Type::String], &ctx), Type::logical());
2950 assert_eq!(map_unknown_type(&[], &ctx), Type::Unknown);
2951 }
2952
2953 #[test]
2954 fn containers_map_descriptor_includes_constructor_and_method_signatures() {
2955 let constructor_labels: Vec<&str> = CONTAINERS_MAP_DESCRIPTOR
2956 .signatures
2957 .iter()
2958 .map(|sig| sig.label)
2959 .collect();
2960 assert!(constructor_labels.contains(&"M = containers.Map()"));
2961 assert!(constructor_labels.contains(&"M = containers.Map(keys, values)"));
2962
2963 let method_labels: Vec<&str> = CONTAINERS_MAP_SUBSREF_DESCRIPTOR
2964 .signatures
2965 .iter()
2966 .map(|sig| sig.label)
2967 .collect();
2968 assert!(method_labels.contains(&"value = containers.Map.subsref(M, kind, payload)"));
2969 }
2970
2971 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2972 #[test]
2973 fn constructor_with_cells_lookup() {
2974 let keys = crate::make_cell(vec![Value::from("apple"), Value::from("pear")], 1, 2).unwrap();
2975 let values = crate::make_cell(vec![Value::Num(5.0), Value::Num(7.0)], 1, 2).unwrap();
2976 let map = containers_map_builtin(vec![keys, values]).expect("map");
2977 let apple = containers_map_subsref(
2978 map.clone(),
2979 "()".to_string(),
2980 crate::make_cell(vec![Value::from("apple")], 1, 1).unwrap(),
2981 )
2982 .expect("lookup");
2983 assert_eq!(apple, Value::Num(5.0));
2984 }
2985
2986 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2987 #[test]
2988 fn constructor_rejects_duplicate_keys() {
2989 let keys = crate::make_cell(vec![Value::from("dup"), Value::from("dup")], 1, 2).unwrap();
2990 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
2991 let err = containers_map_builtin(vec![keys, values]).expect_err("duplicate check");
2992 let message = error_message(err);
2993 assert!(message.contains("Duplicate key name"));
2994 }
2995
2996 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
2997 #[test]
2998 fn constructor_errors_when_value_count_mismatch() {
2999 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
3000 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
3001 let err = containers_map_builtin(vec![keys, values]).expect_err("count mismatch");
3002 let message = error_message(err);
3003 assert!(message.contains("number of keys"));
3004 }
3005
3006 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3007 #[test]
3008 fn comparison_method_rejects_unknown_values() {
3009 let keys = crate::make_cell(vec![Value::from("a")], 1, 1).unwrap();
3010 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
3011 let err = containers_map_builtin(vec![
3012 keys,
3013 values,
3014 Value::from("ComparisonMethod"),
3015 Value::from("caseinsensitive"),
3016 ])
3017 .expect_err("comparison method");
3018 let message = error_message(err);
3019 assert!(message.contains("unrecognised option"));
3020 }
3021
3022 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3023 #[test]
3024 fn key_type_single_roundtrip() {
3025 let map = containers_map_builtin(vec![
3026 Value::from("KeyType"),
3027 Value::from("single"),
3028 Value::from("ValueType"),
3029 Value::from("any"),
3030 ])
3031 .expect("map");
3032 let key_type = containers_map_subsref(map.clone(), ".".to_string(), Value::from("KeyType"))
3033 .expect("keytype");
3034 assert_eq!(
3035 key_type,
3036 Value::CharArray(CharArray::new("single".chars().collect(), 1, 6).unwrap())
3037 );
3038
3039 let single_key = Value::Tensor(Tensor::from_f32(vec![1.0], vec![1, 1]).unwrap());
3040 let payload = crate::make_cell(vec![single_key], 1, 1).unwrap();
3041 let map = containers_map_subsasgn(map, "()".to_string(), payload.clone(), Value::Num(7.0))
3042 .expect("assign");
3043 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
3044 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
3045 }
3046
3047 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3048 #[test]
3049 fn value_type_double_converts_integers() {
3050 let map = containers_map_builtin(vec![
3051 Value::from("KeyType"),
3052 Value::from("double"),
3053 Value::from("ValueType"),
3054 Value::from("double"),
3055 ])
3056 .expect("map");
3057 let payload = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
3058 let map = containers_map_subsasgn(
3059 map,
3060 "()".to_string(),
3061 payload.clone(),
3062 Value::Int(IntValue::I32(7)),
3063 )
3064 .expect("assign");
3065 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
3066 assert!(matches!(value, Value::Num(n) if (n - 7.0).abs() < 1e-12));
3067 }
3068
3069 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3070 #[test]
3071 fn value_type_logical_rejects_nonscalar_numeric_arrays() {
3072 let tensor = Tensor::new(vec![0.0, 2.0, -3.0], vec![3, 1]).unwrap();
3073 let map = containers_map_builtin(vec![
3074 Value::from("KeyType"),
3075 Value::from("char"),
3076 Value::from("ValueType"),
3077 Value::from("logical"),
3078 ])
3079 .expect("map");
3080 let payload = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
3081 let error = containers_map_subsasgn(map, "()".to_string(), payload, Value::Tensor(tensor))
3082 .expect_err("declared logical values must be scalar");
3083 assert!(error.to_string().contains("logical scalars"));
3084 }
3085
3086 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3087 #[test]
3088 fn uniform_values_enforced_on_assignment() {
3089 let map = containers_map_builtin(vec![
3090 crate::make_cell(vec![Value::from("x")], 1, 1).unwrap(),
3091 crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap(),
3092 ])
3093 .expect("map");
3094 let payload = crate::make_cell(vec![Value::from("x")], 1, 1).unwrap();
3095 let err = containers_map_subsasgn(map, "()".to_string(), payload, Value::from("text"))
3096 .expect_err("uniform enforcement");
3097 let message = error_message(err);
3098 assert!(message.contains("numeric scalar"));
3099 }
3100
3101 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3102 #[test]
3103 fn assignment_updates_and_inserts() {
3104 let map = containers_map_builtin(Vec::new()).expect("map");
3105 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
3106 let updated = containers_map_subsasgn(
3107 map.clone(),
3108 "()".to_string(),
3109 payload.clone(),
3110 Value::Num(1.0),
3111 )
3112 .expect("assign");
3113 let updated = containers_map_subsasgn(
3114 updated.clone(),
3115 "()".to_string(),
3116 payload.clone(),
3117 Value::Num(5.0),
3118 )
3119 .expect("update");
3120 let beta_payload = crate::make_cell(vec![Value::from("beta")], 1, 1).unwrap();
3121 let updated = containers_map_subsasgn(
3122 updated.clone(),
3123 "()".to_string(),
3124 beta_payload,
3125 Value::Num(9.0),
3126 )
3127 .expect("insert");
3128 let value = containers_map_subsref(updated, "()".to_string(), payload).expect("lookup");
3129 assert_eq!(value, Value::Num(5.0));
3130 }
3131
3132 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3133 #[test]
3134 fn subsref_rejects_multiple_keys() {
3135 let keys = crate::make_cell(
3136 vec![Value::from("a"), Value::from("b"), Value::from("c")],
3137 1,
3138 3,
3139 )
3140 .unwrap();
3141 let values = crate::make_cell(
3142 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
3143 1,
3144 3,
3145 )
3146 .unwrap();
3147 let map = containers_map_builtin(vec![keys, values]).expect("map");
3148 let request = crate::make_cell(vec![Value::from("a"), Value::from("c")], 1, 2).unwrap();
3149 let payload = crate::make_cell(vec![request], 1, 1).unwrap();
3150 let error = containers_map_subsref(map, "()".to_string(), payload).unwrap_err();
3151 assert!(error.to_string().contains("exactly one scalar key"));
3152 }
3153
3154 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3155 #[test]
3156 fn subsref_rejects_empty_key_collection() {
3157 let keys = crate::make_cell(vec![Value::from("z")], 1, 1).unwrap();
3158 let values = crate::make_cell(vec![Value::Num(42.0)], 1, 1).unwrap();
3159 let map = containers_map_builtin(vec![keys, values]).expect("map");
3160 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
3161 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
3162 let error = containers_map_subsref(map, "()".to_string(), payload).unwrap_err();
3163 assert!(error.to_string().contains("exactly one scalar key"));
3164 }
3165
3166 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3167 #[test]
3168 fn subsasgn_rejects_multiple_keys() {
3169 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
3170 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
3171 let map = containers_map_builtin(vec![keys, values]).expect("map");
3172 let key_spec = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
3173 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
3174 let new_values = crate::make_cell(vec![Value::Num(10.0), Value::Num(20.0)], 1, 2).unwrap();
3175 let error =
3176 containers_map_subsasgn(map, "()".to_string(), payload, new_values).unwrap_err();
3177 assert!(error.to_string().contains("exactly one scalar key"));
3178 }
3179
3180 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3181 #[test]
3182 fn assignment_value_count_mismatch_errors() {
3183 let keys = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
3184 let values = crate::make_cell(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
3185 let map = containers_map_builtin(vec![keys, values]).expect("map");
3186 let key_spec = crate::make_cell(vec![Value::from("x"), Value::from("y")], 1, 2).unwrap();
3187 let payload = crate::make_cell(vec![key_spec], 1, 1).unwrap();
3188 let rhs = crate::make_cell(vec![Value::Num(99.0)], 1, 1).unwrap();
3189 let err =
3190 containers_map_subsasgn(map, "()".to_string(), payload, rhs).expect_err("value count");
3191 let message = error_message(err);
3192 assert!(message.contains("exactly one scalar key"));
3193 }
3194
3195 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3196 #[test]
3197 fn subsasgn_rejects_empty_key_collection() {
3198 let keys = crate::make_cell(vec![Value::from("root")], 1, 1).unwrap();
3199 let values = crate::make_cell(vec![Value::Num(7.0)], 1, 1).unwrap();
3200 let map = containers_map_builtin(vec![keys, values]).expect("map");
3201 let empty_keys = crate::make_cell(Vec::new(), 1, 0).unwrap();
3202 let payload = crate::make_cell(vec![empty_keys], 1, 1).unwrap();
3203 let rhs = crate::make_cell(Vec::new(), 1, 0).unwrap();
3204 let error = containers_map_subsasgn(map, "()".to_string(), payload, rhs).unwrap_err();
3205 assert!(error.to_string().contains("exactly one scalar key"));
3206 }
3207
3208 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3209 #[test]
3210 fn keys_values_iskey_remove() {
3211 let keys = crate::make_cell(
3212 vec![Value::from("a"), Value::from("b"), Value::from("c")],
3213 1,
3214 3,
3215 )
3216 .unwrap();
3217 let values = crate::make_cell(
3218 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
3219 1,
3220 3,
3221 )
3222 .unwrap();
3223 let map = containers_map_builtin(vec![keys, values]).expect("map");
3224 let key_list = containers_map_keys(map.clone()).expect("keys");
3225 match key_list {
3226 Value::Cell(cell) => assert_eq!(cell.data.len(), 3),
3227 other => panic!("expected cell array, got {other:?}"),
3228 }
3229 let mask = containers_map_is_key(
3230 map.clone(),
3231 crate::make_cell(vec![Value::from("a"), Value::from("z")], 1, 2).unwrap(),
3232 )
3233 .expect("mask");
3234 match mask {
3235 Value::LogicalArray(arr) => {
3236 assert_eq!(arr.data, vec![1, 0]);
3237 }
3238 other => panic!("expected logical array, got {:?}", other),
3239 }
3240 let removed = containers_map_remove(
3241 map.clone(),
3242 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
3243 )
3244 .expect("remove");
3245 let mask = containers_map_is_key(
3246 removed,
3247 crate::make_cell(vec![Value::from("b")], 1, 1).unwrap(),
3248 )
3249 .expect("mask");
3250 assert_eq!(mask, Value::Bool(false));
3251 }
3252
3253 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3254 #[test]
3255 fn remove_missing_key_returns_error() {
3256 let keys = crate::make_cell(vec![Value::from("key")], 1, 1).unwrap();
3257 let values = crate::make_cell(vec![Value::Num(1.0)], 1, 1).unwrap();
3258 let map = containers_map_builtin(vec![keys, values]).expect("map");
3259 let err = containers_map_remove(
3260 map,
3261 crate::make_cell(vec![Value::from("missing")], 1, 1).unwrap(),
3262 )
3263 .expect_err("remove missing");
3264 assert_eq!(
3265 err.identifier(),
3266 CONTAINERS_MAP_ERROR_MISSING_KEY.identifier
3267 );
3268 let message = error_message(err);
3269 assert_eq!(message, CONTAINERS_MAP_ERROR_MISSING_KEY.message);
3270 }
3271
3272 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3273 #[test]
3274 fn length_delegates_to_map_count() {
3275 let keys = crate::make_cell(
3276 vec![Value::from("a"), Value::from("b"), Value::from("c")],
3277 1,
3278 3,
3279 )
3280 .unwrap();
3281 let values = crate::make_cell(
3282 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)],
3283 1,
3284 3,
3285 )
3286 .unwrap();
3287 let map = containers_map_builtin(vec![keys, values]).expect("map");
3288 assert_eq!(map_length(&map), Some(3));
3289 }
3290
3291 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3292 #[test]
3293 fn map_id_rejects_corrupted_numeric_identifiers() {
3294 for id_value in [
3295 Value::Num(1.9),
3296 Value::Num(f64::INFINITY),
3297 Value::Num(u64::MAX as f64),
3298 ] {
3299 let mut storage = ObjectInstance::new(CLASS_NAME.to_string());
3300 storage.properties.insert("id".to_string(), id_value);
3301 let target = runmat_gc::gc_allocate(Value::Object(storage)).expect("storage");
3302 let handle = HandleRef {
3303 class_name: CLASS_NAME.to_string(),
3304 target,
3305 valid: true,
3306 };
3307
3308 let err =
3309 map_id(&handle, BUILTIN_CONSTRUCTOR).expect_err("corrupted map id should reject");
3310 assert_eq!(err.identifier(), CONTAINERS_MAP_ERROR_INTERNAL.identifier);
3311 }
3312 }
3313
3314 #[test]
3315 fn typed_map_keys_preserve_full_uint64_range() {
3316 assert_eq!(
3317 unsigned_from_value(
3318 &Value::Int(IntValue::U64(u64::MAX)),
3319 u64::MAX,
3320 "key",
3321 BUILTIN_CONSTRUCTOR,
3322 )
3323 .expect("uint64 key"),
3324 u64::MAX
3325 );
3326 assert!(integer_from_value(
3327 &Value::Int(IntValue::U64(u64::MAX)),
3328 i64::MIN,
3329 i64::MAX,
3330 "key",
3331 BUILTIN_CONSTRUCTOR,
3332 )
3333 .is_err());
3334 }
3335
3336 #[test]
3337 fn scalar_map_helpers_read_typed_integer_tensor_storage_exactly() {
3338 let u64_tensor = Tensor::new_integer(IntegerStorage::U64(vec![u64::MAX]), vec![1, 1])
3339 .expect("uint64 key");
3340 assert_eq!(
3341 unsigned_from_value(
3342 &Value::Tensor(u64_tensor),
3343 u64::MAX,
3344 "key",
3345 BUILTIN_CONSTRUCTOR,
3346 )
3347 .expect("uint64 key"),
3348 u64::MAX
3349 );
3350
3351 let i32_tensor =
3352 Tensor::new_integer(IntegerStorage::I32(vec![-7]), vec![1, 1]).expect("int32 key");
3353 assert_eq!(
3354 integer_from_value(
3355 &Value::Tensor(i32_tensor),
3356 i32::MIN as i64,
3357 i32::MAX as i64,
3358 "key",
3359 BUILTIN_CONSTRUCTOR,
3360 )
3361 .expect("int32 key"),
3362 -7
3363 );
3364
3365 let logical_tensor =
3366 Tensor::new_integer(IntegerStorage::U8(vec![1]), vec![1, 1]).expect("logical key");
3367 assert!(
3368 bool_from_value(&Value::Tensor(logical_tensor), "key", BUILTIN_CONSTRUCTOR,)
3369 .expect("logical key")
3370 );
3371 }
3372
3373 #[test]
3374 fn vector_map_helpers_preserve_native_single_and_logicalize_typed_values() {
3375 let single = Tensor::from_f32(vec![1.25, 2.5], vec![1, 2]).expect("single values");
3376 let values = tensor_elements_to_values(&single);
3377 assert_eq!(values.len(), 2);
3378 for (value, expected) in values.into_iter().zip([1.25_f32, 2.5]) {
3379 let Value::Tensor(tensor) = value else {
3380 panic!("expected native-single scalar tensor");
3381 };
3382 assert_eq!(tensor.numeric_dtype(), NumericDType::F32);
3383 assert_eq!(
3384 tensor.numeric_value_at(0),
3385 Some(runmat_value::NumericScalar::F32(expected))
3386 );
3387 }
3388
3389 let logical = normalize_logical_value(
3390 Value::Tensor(
3391 Tensor::from_f32(vec![0.0, -0.0, 2.0, f32::NAN], vec![2, 2])
3392 .expect("single logical source"),
3393 ),
3394 BUILTIN_CONSTRUCTOR,
3395 );
3396 assert!(logical.is_err());
3397 }
3398
3399 #[test]
3400 fn vector_map_keys_and_values_read_typed_integer_storage_exactly() {
3401 let keys = Tensor::new_integer(
3402 IntegerStorage::U64(vec![u64::MAX - 1, u64::MAX]),
3403 vec![1, 2],
3404 )
3405 .expect("uint64 keys");
3406 let values =
3407 Tensor::new_integer(IntegerStorage::I32(vec![11, 22]), vec![1, 2]).expect("values");
3408
3409 let map =
3410 containers_map_builtin(vec![Value::Tensor(keys), Value::Tensor(values)]).expect("map");
3411
3412 let key_payload = crate::make_cell(
3413 vec![
3414 Value::Int(IntValue::U64(u64::MAX - 1)),
3415 Value::Int(IntValue::U64(u64::MAX)),
3416 ],
3417 1,
3418 2,
3419 )
3420 .unwrap();
3421 let result = containers_map_values(map, vec![key_payload]).expect("selected values");
3422 match result {
3423 Value::Cell(cell) => {
3424 assert_eq!((cell.rows, cell.cols), (1, 2));
3425 assert_eq!(cell.data[0], Value::Int(IntValue::I32(11)));
3426 assert_eq!(cell.data[1], Value::Int(IntValue::I32(22)));
3427 }
3428 other => panic!("expected cell lookup result, got {:?}", other),
3429 }
3430 }
3431
3432 #[test]
3433 fn value_type_logical_reads_typed_integer_tensor_storage_exactly() {
3434 let tensor = Tensor::new_integer(IntegerStorage::U64(vec![0, u64::MAX]), vec![1, 2])
3435 .expect("integer logical source");
3436 let map = containers_map_builtin(vec![
3437 Value::from("KeyType"),
3438 Value::from("char"),
3439 Value::from("ValueType"),
3440 Value::from("logical"),
3441 ])
3442 .expect("map");
3443 let payload = crate::make_cell(vec![Value::from("mask")], 1, 1).unwrap();
3444 let error = containers_map_subsasgn(map, "()".to_string(), payload, Value::Tensor(tensor))
3445 .expect_err("declared logical ValueType accepts scalar values only");
3446 assert!(error.to_string().contains("logical scalars"));
3447 }
3448
3449 fn all_integer_value_storages() -> Vec<(IntegerStorage, &'static str)> {
3450 vec![
3451 (IntegerStorage::I8(vec![i8::MIN, i8::MAX]), "int8"),
3452 (IntegerStorage::U8(vec![0, u8::MAX]), "uint8"),
3453 (IntegerStorage::I16(vec![i16::MIN, i16::MAX]), "int16"),
3454 (IntegerStorage::U16(vec![0, u16::MAX]), "uint16"),
3455 (IntegerStorage::I32(vec![i32::MIN, i32::MAX]), "int32"),
3456 (IntegerStorage::U32(vec![0, u32::MAX]), "uint32"),
3457 (IntegerStorage::I64(vec![i64::MIN, i64::MAX]), "int64"),
3458 (IntegerStorage::U64(vec![u64::MAX - 1, u64::MAX]), "uint64"),
3459 ]
3460 }
3461
3462 fn public_integer_key_storages() -> Vec<(IntegerStorage, &'static str)> {
3463 vec![
3464 (IntegerStorage::I32(vec![i32::MIN, i32::MAX]), "int32"),
3465 (IntegerStorage::U32(vec![0, u32::MAX]), "uint32"),
3466 (IntegerStorage::I64(vec![i64::MIN, i64::MAX]), "int64"),
3467 (IntegerStorage::U64(vec![u64::MAX - 1, u64::MAX]), "uint64"),
3468 ]
3469 }
3470
3471 fn property_text(map: Value, name: &str) -> String {
3472 let value = containers_map_subsref(map, ".".to_string(), Value::from(name)).unwrap();
3473 let Value::CharArray(chars) = value else {
3474 panic!("expected char property");
3475 };
3476 chars.data.iter().collect()
3477 }
3478
3479 #[test]
3480 fn constructor_subsref_and_values_preserve_all_integer_value_classes_exactly() {
3481 for (storage, class_name) in all_integer_value_storages() {
3482 let expected = storage.exact_values();
3483 let keys = crate::make_cell(vec![Value::from("a"), Value::from("b")], 1, 2).unwrap();
3484 let values = Value::Tensor(Tensor::new_integer(storage, vec![1, 2]).unwrap());
3485 let map = containers_map_builtin(vec![keys, values]).unwrap();
3486 assert_eq!(property_text(map.clone(), "ValueType"), class_name);
3487
3488 let payload = crate::make_cell(vec![Value::from("b")], 1, 1).unwrap();
3489 assert_eq!(
3490 containers_map_subsref(map.clone(), "()".to_string(), payload).unwrap(),
3491 Value::Int(expected[1].clone())
3492 );
3493
3494 let Value::Cell(all_values) = containers_map_values(map.clone(), Vec::new()).unwrap()
3495 else {
3496 panic!("expected values cell");
3497 };
3498 assert_eq!(
3499 all_values.data,
3500 vec![
3501 Value::Int(expected[0].clone()),
3502 Value::Int(expected[1].clone())
3503 ]
3504 );
3505
3506 let selected_keys =
3507 crate::make_cell(vec![Value::from("b"), Value::from("a")], 2, 1).unwrap();
3508 let Value::Cell(selected) =
3509 containers_map_values(map, vec![selected_keys]).expect("selected values")
3510 else {
3511 panic!("expected selected values cell");
3512 };
3513 assert_eq!((selected.rows, selected.cols), (2, 1));
3514 assert_eq!(
3515 selected.data,
3516 vec![
3517 Value::Int(expected[1].clone()),
3518 Value::Int(expected[0].clone())
3519 ]
3520 );
3521 }
3522 }
3523
3524 #[test]
3525 fn keys_iskey_and_remove_preserve_all_public_integer_key_classes() {
3526 for (storage, class_name) in public_integer_key_storages() {
3527 let expected = storage.exact_values();
3528 let key_tensor =
3529 Value::Tensor(Tensor::new_integer(storage.clone(), vec![1, 2]).unwrap());
3530 let value_tensor = Value::Tensor(Tensor::new(vec![10.0, 20.0], vec![1, 2]).unwrap());
3531 let map = containers_map_builtin(vec![key_tensor.clone(), value_tensor]).unwrap();
3532 assert_eq!(property_text(map.clone(), "KeyType"), class_name);
3533
3534 let Value::Cell(keys) = containers_map_keys(map.clone()).unwrap() else {
3535 panic!("expected keys cell");
3536 };
3537 assert_eq!(
3538 keys.data,
3539 vec![
3540 Value::Int(expected[0].clone()),
3541 Value::Int(expected[1].clone())
3542 ]
3543 );
3544
3545 let key_cell =
3546 crate::make_cell(expected.iter().cloned().map(Value::Int).collect(), 1, 2).unwrap();
3547 let Value::LogicalArray(found) =
3548 containers_map_is_key(map.clone(), key_cell).expect("isKey")
3549 else {
3550 panic!("expected logical array");
3551 };
3552 assert_eq!(found.shape, vec![1, 2]);
3553 assert_eq!(found.data, vec![1, 1]);
3554
3555 let removed = containers_map_remove(map, Value::Int(expected[1].clone())).unwrap();
3556 assert_eq!(
3557 containers_map_is_key(removed, Value::Int(expected[1].clone())).unwrap(),
3558 Value::Bool(false)
3559 );
3560 }
3561 }
3562
3563 #[test]
3564 fn integer_key_methods_reject_class_mismatch_and_binary64_aliases() {
3565 let wide = 9_007_199_254_740_993_u64;
3566 let keys = Value::Tensor(
3567 Tensor::new_integer(IntegerStorage::U64(vec![wide]), vec![1, 1]).unwrap(),
3568 );
3569 let map = containers_map_builtin(vec![keys, Value::Num(7.0)]).unwrap();
3570 let rounded_double = Value::Num(wide as f64);
3571 let payload = crate::make_cell(vec![rounded_double.clone()], 1, 1).unwrap();
3572 for error in [
3573 containers_map_subsref(map.clone(), "()".to_string(), payload.clone()).unwrap_err(),
3574 containers_map_is_key(map.clone(), rounded_double.clone()).unwrap_err(),
3575 containers_map_remove(map.clone(), rounded_double.clone()).unwrap_err(),
3576 containers_map_subsasgn(map.clone(), "()".to_string(), payload, Value::Num(9.0))
3577 .unwrap_err(),
3578 ] {
3579 assert!(error.to_string().contains("KeyType 'uint64'"));
3580 }
3581 let exact_payload = crate::make_cell(vec![Value::Int(IntValue::U64(wide))], 1, 1).unwrap();
3582 assert_eq!(
3583 containers_map_subsref(map, "()".to_string(), exact_payload).unwrap(),
3584 Value::Num(7.0)
3585 );
3586 }
3587
3588 #[test]
3589 fn direct_noncell_multi_key_method_inputs_reject() {
3590 let keys = Value::Tensor(
3591 Tensor::new_integer(IntegerStorage::I32(vec![1, 2]), vec![1, 2]).unwrap(),
3592 );
3593 let map = containers_map_builtin(vec![
3594 keys.clone(),
3595 Value::Tensor(Tensor::new(vec![10.0, 20.0], vec![1, 2]).unwrap()),
3596 ])
3597 .unwrap();
3598 let is_key_error = containers_map_is_key(map.clone(), keys.clone()).unwrap_err();
3599 assert!(is_key_error.to_string().contains("cell array"));
3600 let remove_error = containers_map_remove(map, keys).unwrap_err();
3601 assert!(remove_error.to_string().contains("cell array"));
3602 }
3603
3604 #[test]
3605 fn subsasgn_explicit_integer_value_types_cover_all_eight_classes() {
3606 for (storage, class_name) in all_integer_value_storages() {
3607 let expected = storage.exact_values()[1].clone();
3608 let map = containers_map_builtin(vec![
3609 Value::from("KeyType"),
3610 Value::from("uint64"),
3611 Value::from("ValueType"),
3612 Value::from(class_name),
3613 ])
3614 .unwrap();
3615 let payload =
3616 crate::make_cell(vec![Value::Int(IntValue::U64(u64::MAX))], 1, 1).unwrap();
3617 let updated = containers_map_subsasgn(
3618 map,
3619 "()".to_string(),
3620 payload.clone(),
3621 Value::Int(expected.clone()),
3622 )
3623 .unwrap();
3624 assert_eq!(
3625 containers_map_subsref(updated, "()".to_string(), payload).unwrap(),
3626 Value::Int(expected)
3627 );
3628 }
3629 }
3630
3631 #[test]
3632 fn any_preserves_nonscalar_integer_arrays_and_declared_types_reject_them() {
3633 for (storage, class_name) in all_integer_value_storages() {
3634 let expected = storage.clone();
3635 let array = Value::Tensor(Tensor::new_integer(storage, vec![1, 2]).unwrap());
3636 let any_map = containers_map_builtin(vec![
3637 Value::from("KeyType"),
3638 Value::from("char"),
3639 Value::from("ValueType"),
3640 Value::from("any"),
3641 ])
3642 .unwrap();
3643 let payload = crate::make_cell(vec![Value::from("array")], 1, 1).unwrap();
3644 let any_map =
3645 containers_map_subsasgn(any_map, "()".to_string(), payload.clone(), array.clone())
3646 .unwrap();
3647 let Value::Tensor(stored) =
3648 containers_map_subsref(any_map, "()".to_string(), payload.clone()).unwrap()
3649 else {
3650 panic!("expected exact integer tensor");
3651 };
3652 assert_eq!(stored.integer_storage(), Some(&expected));
3653
3654 let typed_map = containers_map_builtin(vec![
3655 Value::from("KeyType"),
3656 Value::from("char"),
3657 Value::from("ValueType"),
3658 Value::from(class_name),
3659 ])
3660 .unwrap();
3661 assert!(containers_map_subsasgn(typed_map, "()".to_string(), payload, array,).is_err());
3662 }
3663 }
3664
3665 #[test]
3666 fn unsupported_narrow_integer_key_classes_reject_without_double_aliasing() {
3667 for storage in [
3668 IntegerStorage::I8(vec![1]),
3669 IntegerStorage::U8(vec![1]),
3670 IntegerStorage::I16(vec![1]),
3671 IntegerStorage::U16(vec![1]),
3672 ] {
3673 let error = containers_map_builtin(vec![
3674 Value::Tensor(Tensor::new_integer(storage, vec![1, 1]).unwrap()),
3675 Value::Tensor(Tensor::new(vec![1.0], vec![1, 1]).unwrap()),
3676 ])
3677 .unwrap_err();
3678 assert!(error.to_string().contains("int32"));
3679 }
3680 }
3681
3682 #[test]
3683 fn resident_extensions_gate_before_provider_access_for_integer_surfaces() {
3684 let _compat = crate::compatibility::push_runmat_extensions_enabled(false);
3685 let resident = Value::GpuTensor(runmat_accelerate_api::GpuTensorHandle {
3686 shape: vec![1, 1],
3687 device_id: 77,
3688 buffer_id: 99,
3689 descriptor: Default::default(),
3690 });
3691 let char_map = containers_map_builtin(Vec::new()).unwrap();
3692 let checks = [
3693 block_on(super::containers_map_builtin(vec![
3694 resident.clone(),
3695 Value::Num(1.0),
3696 ]))
3697 .unwrap_err(),
3698 block_on(super::containers_map_is_key(
3699 char_map.clone(),
3700 resident.clone(),
3701 ))
3702 .unwrap_err(),
3703 block_on(super::containers_map_remove(
3704 char_map.clone(),
3705 resident.clone(),
3706 ))
3707 .unwrap_err(),
3708 block_on(super::containers_map_values(
3709 char_map.clone(),
3710 vec![crate::make_cell(vec![resident.clone()], 1, 1).unwrap()],
3711 ))
3712 .unwrap_err(),
3713 block_on(super::containers_map_subsref(
3714 char_map.clone(),
3715 "()".to_string(),
3716 crate::make_cell(vec![resident.clone()], 1, 1).unwrap(),
3717 ))
3718 .unwrap_err(),
3719 block_on(super::containers_map_subsasgn(
3720 char_map,
3721 "()".to_string(),
3722 crate::make_cell(vec![Value::from("x")], 1, 1).unwrap(),
3723 resident,
3724 ))
3725 .unwrap_err(),
3726 ];
3727 for error in checks {
3728 assert!(error
3729 .identifier()
3730 .is_some_and(|identifier| identifier
3731 .starts_with("RunMat:compatibility:ContainersMapResident")));
3732 }
3733 }
3734
3735 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
3736 #[test]
3737 fn map_constructor_gathers_gpu_values() {
3738 test_support::with_test_provider(|provider| {
3739 let _compat = crate::compatibility::push_runmat_extensions_enabled(true);
3740 let keys = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
3741 let data = vec![1.0, 2.0, 3.0];
3742 let shape = vec![3, 1];
3743 let view = runmat_accelerate_api::HostTensorView {
3744 data: &data,
3745 shape: &shape,
3746 };
3747 let handle = provider.upload(&view).expect("upload");
3748 let values = crate::make_cell(vec![Value::GpuTensor(handle)], 1, 1).unwrap();
3749 let map = containers_map_builtin(vec![
3750 keys,
3751 values,
3752 Value::from("UniformValues"),
3753 Value::Bool(false),
3754 ])
3755 .expect("map");
3756 let payload = crate::make_cell(vec![Value::from("alpha")], 1, 1).unwrap();
3757 let value = containers_map_subsref(map, "()".to_string(), payload).expect("lookup");
3758 match value {
3759 Value::Tensor(t) => {
3760 assert_eq!(t.shape, shape);
3761 assert_eq!(t.materialize_f64(), data);
3762 }
3763 other => panic!("expected tensor, got {:?}", other),
3764 }
3765 });
3766 }
3767
3768 #[test]
3769 fn map_resident_integer_keys_and_values_gather_exactly() {
3770 test_support::with_test_provider(|provider| {
3771 let _compat = crate::compatibility::push_runmat_extensions_enabled(true);
3772 let wide = 9_007_199_254_740_993_u64;
3773 let key_tensor =
3774 Tensor::new_integer(IntegerStorage::U64(vec![wide]), vec![1, 1]).unwrap();
3775 let key_handle = gpu_helpers::upload_tensor(provider, &key_tensor).expect("key upload");
3776 let map = containers_map_builtin(vec![Value::GpuTensor(key_handle), Value::Num(7.0)])
3777 .expect("resident key constructor");
3778 let payload = crate::make_cell(vec![Value::Int(IntValue::U64(wide))], 1, 1).unwrap();
3779 assert_eq!(
3780 containers_map_subsref(map, "()".to_string(), payload).unwrap(),
3781 Value::Num(7.0)
3782 );
3783
3784 let value_tensor =
3785 Tensor::new_integer(IntegerStorage::U64(vec![wide, u64::MAX]), vec![2, 1]).unwrap();
3786 let value_handle =
3787 gpu_helpers::upload_tensor(provider, &value_tensor).expect("value upload");
3788 let keys = crate::make_cell(vec![Value::from("wide")], 1, 1).unwrap();
3789 let values = crate::make_cell(vec![Value::GpuTensor(value_handle)], 1, 1).unwrap();
3790 let map = containers_map_builtin(vec![
3791 keys,
3792 values,
3793 Value::from("UniformValues"),
3794 Value::Bool(false),
3795 ])
3796 .expect("resident value constructor");
3797 let payload = crate::make_cell(vec![Value::from("wide")], 1, 1).unwrap();
3798 let Value::Tensor(gathered) =
3799 containers_map_subsref(map, "()".to_string(), payload).unwrap()
3800 else {
3801 panic!("expected exact integer tensor value");
3802 };
3803 assert_eq!(gathered.integer_storage(), value_tensor.integer_storage());
3804 });
3805 }
3806}