1use crate::builtins::common::spec::{
9 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
10 ReductionNaN, ResidencyPolicy, ShapeRequirements,
11};
12use crate::builtins::introspection::dynamicprops;
13use crate::builtins::structs::type_resolvers::setfield_type;
14use crate::{
15 build_runtime_error, call_builtin_async, gather_if_needed_async, object_property_getter_name,
16 object_property_setter_name, BuiltinResult, RuntimeError,
17};
18use runmat_builtins::{
19 Access, BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
20 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
21 CellArray, CharArray, ComplexTensor, HandleRef, LogicalArray, ObjectInstance, StructValue,
22 Tensor, Value,
23};
24use runmat_macros::runtime_builtin;
25use std::convert::TryFrom;
26
27#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::structs::core::setfield")]
28pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
29 name: "setfield",
30 op_kind: GpuOpKind::Custom("setfield"),
31 supported_precisions: &[],
32 broadcast: BroadcastSemantics::None,
33 provider_hooks: &[],
34 constant_strategy: ConstantStrategy::InlineLiteral,
35 residency: ResidencyPolicy::InheritInputs,
36 nan_mode: ReductionNaN::Include,
37 two_pass_threshold: None,
38 workgroup_size: None,
39 accepts_nan_mode: false,
40 notes: "Host-only metadata mutation; GPU tensors are gathered before assignment.",
41};
42
43#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::structs::core::setfield")]
44pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
45 name: "setfield",
46 shape: ShapeRequirements::Any,
47 constant_strategy: ConstantStrategy::InlineLiteral,
48 elementwise: None,
49 reduction: None,
50 emits_nan: false,
51 notes: "Assignments terminate fusion and gather device data back to the host.",
52};
53
54const BUILTIN_NAME: &str = "setfield";
55const SETFIELD_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
56 name: "S",
57 ty: BuiltinParamType::Any,
58 arity: BuiltinParamArity::Required,
59 default: None,
60 description: "Updated struct/object/array value.",
61}];
62
63const SETFIELD_INPUTS_SCALAR: [BuiltinParamDescriptor; 3] = [
64 BuiltinParamDescriptor {
65 name: "S",
66 ty: BuiltinParamType::Any,
67 arity: BuiltinParamArity::Required,
68 default: None,
69 description: "Input struct/object/struct-array target.",
70 },
71 BuiltinParamDescriptor {
72 name: "field",
73 ty: BuiltinParamType::PropertyName,
74 arity: BuiltinParamArity::Required,
75 default: None,
76 description: "Field/property name to assign.",
77 },
78 BuiltinParamDescriptor {
79 name: "value",
80 ty: BuiltinParamType::Any,
81 arity: BuiltinParamArity::Required,
82 default: None,
83 description: "Assigned value.",
84 },
85];
86
87const SETFIELD_INPUTS_NESTED: [BuiltinParamDescriptor; 3] = [
88 BuiltinParamDescriptor {
89 name: "S",
90 ty: BuiltinParamType::Any,
91 arity: BuiltinParamArity::Required,
92 default: None,
93 description: "Input struct/object/struct-array target.",
94 },
95 BuiltinParamDescriptor {
96 name: "path",
97 ty: BuiltinParamType::Any,
98 arity: BuiltinParamArity::Variadic,
99 default: None,
100 description:
101 "Alternating field names and optional index-selector cells `{...}` for nested assignment.",
102 },
103 BuiltinParamDescriptor {
104 name: "value",
105 ty: BuiltinParamType::Any,
106 arity: BuiltinParamArity::Required,
107 default: None,
108 description: "Assigned value.",
109 },
110];
111
112const SETFIELD_INPUTS_LEADING_INDEX: [BuiltinParamDescriptor; 4] = [
113 BuiltinParamDescriptor {
114 name: "S",
115 ty: BuiltinParamType::Any,
116 arity: BuiltinParamArity::Required,
117 default: None,
118 description: "Input struct-array target.",
119 },
120 BuiltinParamDescriptor {
121 name: "index_selector",
122 ty: BuiltinParamType::Any,
123 arity: BuiltinParamArity::Required,
124 default: None,
125 description: "Leading index selector in a cell array, e.g. `{2}` or `{end}`.",
126 },
127 BuiltinParamDescriptor {
128 name: "path",
129 ty: BuiltinParamType::Any,
130 arity: BuiltinParamArity::Variadic,
131 default: None,
132 description:
133 "Alternating field names and optional index-selector cells `{...}` for nested assignment.",
134 },
135 BuiltinParamDescriptor {
136 name: "value",
137 ty: BuiltinParamType::Any,
138 arity: BuiltinParamArity::Required,
139 default: None,
140 description: "Assigned value.",
141 },
142];
143
144const SETFIELD_SIGNATURES: [BuiltinSignatureDescriptor; 3] = [
145 BuiltinSignatureDescriptor {
146 label: "S = setfield(S, field, value)",
147 inputs: &SETFIELD_INPUTS_SCALAR,
148 outputs: &SETFIELD_OUTPUT,
149 },
150 BuiltinSignatureDescriptor {
151 label: "S = setfield(S, field_or_index, ..., value)",
152 inputs: &SETFIELD_INPUTS_NESTED,
153 outputs: &SETFIELD_OUTPUT,
154 },
155 BuiltinSignatureDescriptor {
156 label: "S = setfield(S, {idx0}, field_or_index, ..., value)",
157 inputs: &SETFIELD_INPUTS_LEADING_INDEX,
158 outputs: &SETFIELD_OUTPUT,
159 },
160];
161
162const SETFIELD_ERROR_NOT_ENOUGH_INPUTS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
163 code: "RM.SETFIELD.NOT_ENOUGH_INPUTS",
164 identifier: Some("RunMat:setfield:NotEnoughInputs"),
165 when: "Input does not provide at least one path component plus assigned value.",
166 message: "setfield: expected at least one field name and a value",
167};
168
169const SETFIELD_ERROR_FIELD_EXPECTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
170 code: "RM.SETFIELD.FIELD_EXPECTED",
171 identifier: Some("RunMat:setfield:FieldExpected"),
172 when: "Field/path arguments are missing after parsing selectors.",
173 message: "setfield: expected field name arguments",
174};
175
176const SETFIELD_ERROR_INDEX_SELECTOR_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
177 code: "RM.SETFIELD.INDEX_SELECTOR_TYPE",
178 identifier: Some("RunMat:setfield:IndexSelectorType"),
179 when: "Index selector is not provided as a cell array.",
180 message: "setfield: indices must be provided in a cell array",
181};
182
183const SETFIELD_ERROR_INDEX_INVALID: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
184 code: "RM.SETFIELD.INDEX_INVALID",
185 identifier: Some("RunMat:setfield:InvalidIndex"),
186 when: "Index component is malformed, empty, unsupported, or not a positive integer.",
187 message: "setfield: invalid index element",
188};
189
190const SETFIELD_ERROR_FIELD_NAME_TYPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
191 code: "RM.SETFIELD.FIELD_NAME_TYPE",
192 identifier: Some("RunMat:setfield:FieldNameType"),
193 when: "Field name is not a scalar string or 1-by-N char vector.",
194 message: "setfield: expected field name",
195};
196
197const SETFIELD_ERROR_INDEX_SHAPE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
198 code: "RM.SETFIELD.INDEX_SHAPE",
199 identifier: Some("RunMat:setfield:IndexShape"),
200 when: "Indexing rank/shape is unsupported for the targeted value.",
201 message: "setfield: unsupported index shape for target value",
202};
203
204const SETFIELD_ERROR_NON_STRUCT_ASSIGNMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
205 code: "RM.SETFIELD.NON_STRUCT_ASSIGNMENT",
206 identifier: Some("RunMat:setfield:NonStructAssignment"),
207 when: "Assignment target does not support struct-like field updates.",
208 message: "Struct contents assignment to a non-struct object is not supported.",
209};
210
211const SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
212 code: "RM.SETFIELD.INDEX_OUT_OF_BOUNDS",
213 identifier: Some("RunMat:setfield:IndexOutOfBounds"),
214 when: "Resolved index is outside bounds for target value.",
215 message: "Index exceeds the number of array elements.",
216};
217
218const SETFIELD_ERROR_MISSING_FIELD: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
219 code: "RM.SETFIELD.MISSING_FIELD",
220 identifier: Some("RunMat:setfield:MissingField"),
221 when: "Indexed assignment path references a missing field.",
222 message: "Reference to non-existent field",
223};
224
225const SETFIELD_ERROR_PROPERTY_PRIVATE_ACCESS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
226 code: "RM.SETFIELD.PROPERTY_PRIVATE_ACCESS",
227 identifier: Some("RunMat:PropertyPrivateAccess"),
228 when: "Property exists but get/set access is private.",
229 message: "setfield: private property access denied",
230};
231
232const SETFIELD_ERROR_PROPERTY_STATIC_ACCESS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
233 code: "RM.SETFIELD.PROPERTY_STATIC_ACCESS",
234 identifier: Some("RunMat:PropertyStaticAccess"),
235 when: "Property exists but is static and cannot be assigned through an instance.",
236 message: "setfield: static property access denied",
237};
238
239const SETFIELD_ERROR_OBJECT_PROPERTY: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
240 code: "RM.SETFIELD.OBJECT_PROPERTY",
241 identifier: Some("RunMat:setfield:ObjectProperty"),
242 when: "Object property operation is invalid (static, non-public, or malformed setter result).",
243 message: "setfield: invalid object property operation",
244};
245
246const SETFIELD_ERROR_INVALID_HANDLE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
247 code: "RM.SETFIELD.INVALID_HANDLE",
248 identifier: Some("RunMat:setfield:InvalidHandle"),
249 when: "Handle target is invalid/deleted/null.",
250 message: "setfield: invalid or deleted handle object",
251};
252
253const SETFIELD_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
254 code: "RM.SETFIELD.INTERNAL",
255 identifier: Some("RunMat:setfield:InternalError"),
256 when: "Internal conversion/allocation failed while assigning values.",
257 message: "setfield: internal error",
258};
259
260const SETFIELD_ERRORS: [BuiltinErrorDescriptor; 14] = [
261 SETFIELD_ERROR_NOT_ENOUGH_INPUTS,
262 SETFIELD_ERROR_FIELD_EXPECTED,
263 SETFIELD_ERROR_INDEX_SELECTOR_TYPE,
264 SETFIELD_ERROR_INDEX_INVALID,
265 SETFIELD_ERROR_FIELD_NAME_TYPE,
266 SETFIELD_ERROR_INDEX_SHAPE,
267 SETFIELD_ERROR_NON_STRUCT_ASSIGNMENT,
268 SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS,
269 SETFIELD_ERROR_MISSING_FIELD,
270 SETFIELD_ERROR_PROPERTY_PRIVATE_ACCESS,
271 SETFIELD_ERROR_PROPERTY_STATIC_ACCESS,
272 SETFIELD_ERROR_OBJECT_PROPERTY,
273 SETFIELD_ERROR_INVALID_HANDLE,
274 SETFIELD_ERROR_INTERNAL,
275];
276
277pub const SETFIELD_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
278 signatures: &SETFIELD_SIGNATURES,
279 output_mode: BuiltinOutputMode::Fixed,
280 completion_policy: BuiltinCompletionPolicy::Public,
281 errors: &SETFIELD_ERRORS,
282};
283
284fn setfield_flow(message: impl Into<String>) -> RuntimeError {
285 setfield_error_with_message(
286 format!("{}: {}", SETFIELD_ERROR_INTERNAL.message, message.into()),
287 &SETFIELD_ERROR_INTERNAL,
288 )
289}
290
291fn setfield_error_with_message(
292 message: impl Into<String>,
293 error: &'static BuiltinErrorDescriptor,
294) -> RuntimeError {
295 let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
296 if let Some(identifier) = error.identifier {
297 builder = builder.with_identifier(identifier);
298 }
299 builder.build()
300}
301
302fn setfield_private_access(message: impl Into<String>) -> RuntimeError {
303 setfield_error_with_message(message, &SETFIELD_ERROR_PROPERTY_PRIVATE_ACCESS)
304}
305
306fn setfield_static_access(message: impl Into<String>) -> RuntimeError {
307 setfield_error_with_message(message, &SETFIELD_ERROR_PROPERTY_STATIC_ACCESS)
308}
309
310fn remap_setfield_flow(err: RuntimeError, prefix: Option<&str>) -> RuntimeError {
311 let mut message = err.message().to_string();
312 if let Some(prefix) = prefix {
313 if !message.starts_with(prefix) {
314 message = format!("{prefix}{message}");
315 }
316 }
317 let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
318 if let Some(identifier) = err.identifier() {
319 builder = builder.with_identifier(identifier);
320 }
321 builder.with_source(err).build()
322}
323
324fn is_undefined_function(err: &RuntimeError) -> bool {
325 err.identifier() == Some(crate::IDENT_UNDEFINED_FUNCTION)
326}
327
328#[runtime_builtin(
329 name = "setfield",
330 category = "structs/core",
331 summary = "Assign values into struct fields, nested fields, or struct-array elements.",
332 keywords = "setfield,struct,assignment,object property",
333 type_resolver(setfield_type),
334 descriptor(crate::builtins::structs::core::setfield::SETFIELD_DESCRIPTOR),
335 builtin_path = "crate::builtins::structs::core::setfield"
336)]
337async fn setfield_builtin(base: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
338 let parsed = parse_arguments(rest)?;
339 let ParsedArguments {
340 leading_index,
341 steps,
342 value,
343 } = parsed;
344 assign_value(base, leading_index, steps, value).await
345}
346
347struct ParsedArguments {
348 leading_index: Option<IndexSelector>,
349 steps: Vec<FieldStep>,
350 value: Value,
351}
352
353struct FieldStep {
354 name: String,
355 index: Option<IndexSelector>,
356}
357
358#[derive(Clone)]
359struct IndexSelector {
360 components: Vec<IndexComponent>,
361}
362
363#[derive(Clone)]
364enum IndexComponent {
365 Scalar(usize),
366 End,
367}
368
369fn parse_arguments(mut rest: Vec<Value>) -> BuiltinResult<ParsedArguments> {
370 if rest.len() < 2 {
371 return Err(setfield_flow(SETFIELD_ERROR_NOT_ENOUGH_INPUTS.message));
372 }
373
374 let value = rest
375 .pop()
376 .expect("rest contains at least two elements after early return");
377
378 let mut parsed = ParsedArguments {
379 leading_index: None,
380 steps: Vec::new(),
381 value,
382 };
383
384 if let Some(first) = rest.first() {
385 if is_index_selector(first) {
386 let selector = rest.remove(0);
387 parsed.leading_index = Some(parse_index_selector(selector)?);
388 }
389 }
390
391 if rest.is_empty() {
392 return Err(setfield_flow(SETFIELD_ERROR_FIELD_EXPECTED.message));
393 }
394
395 let mut iter = rest.into_iter().peekable();
396 while let Some(arg) = iter.next() {
397 let name = parse_field_name(arg)?;
398 let mut step = FieldStep { name, index: None };
399 if let Some(next) = iter.peek() {
400 if is_index_selector(next) {
401 let selector = iter.next().unwrap();
402 step.index = Some(parse_index_selector(selector)?);
403 }
404 }
405 parsed.steps.push(step);
406 }
407
408 if parsed.steps.is_empty() {
409 return Err(setfield_flow(SETFIELD_ERROR_FIELD_EXPECTED.message));
410 }
411
412 Ok(parsed)
413}
414
415async fn assign_value(
416 base: Value,
417 leading_index: Option<IndexSelector>,
418 steps: Vec<FieldStep>,
419 rhs: Value,
420) -> BuiltinResult<Value> {
421 if steps.is_empty() {
422 return Err(setfield_flow(SETFIELD_ERROR_FIELD_EXPECTED.message));
423 }
424 if let Some(selector) = leading_index {
425 assign_with_leading_index(base, &selector, &steps, rhs).await
426 } else {
427 assign_without_leading_index(base, &steps, rhs).await
428 }
429}
430
431async fn assign_with_leading_index(
432 base: Value,
433 selector: &IndexSelector,
434 steps: &[FieldStep],
435 rhs: Value,
436) -> BuiltinResult<Value> {
437 match base {
438 Value::Cell(cell) => assign_into_struct_array(cell, selector, steps, rhs).await,
439 other => Err(setfield_flow(format!(
440 "setfield: leading indices require a struct array, got {other:?}"
441 ))),
442 }
443}
444
445async fn assign_without_leading_index(
446 base: Value,
447 steps: &[FieldStep],
448 rhs: Value,
449) -> BuiltinResult<Value> {
450 match base {
451 Value::Struct(struct_value) => assign_into_struct(struct_value, steps, rhs).await,
452 Value::Object(object) => assign_into_object(object, steps, rhs).await,
453 Value::Cell(cell) if is_struct_array(&cell) => {
454 if cell.data.is_empty() {
455 Err(setfield_flow(
456 "setfield: struct array is empty; supply indices in a cell array",
457 ))
458 } else {
459 let selector = IndexSelector {
460 components: vec![IndexComponent::Scalar(1)],
461 };
462 assign_into_struct_array(cell, &selector, steps, rhs).await
463 }
464 }
465 Value::HandleObject(handle) => assign_into_handle(handle, steps, rhs).await,
466 Value::Listener(_) => Err(setfield_flow(
467 "setfield: listeners do not support direct field assignment",
468 )),
469 other => Err(setfield_flow(format!(
470 "setfield unsupported on this value for field '{}': {other:?}",
471 steps.first().map(|s| s.name.as_str()).unwrap_or_default()
472 ))),
473 }
474}
475
476async fn assign_into_struct_array(
477 mut cell: CellArray,
478 selector: &IndexSelector,
479 steps: &[FieldStep],
480 rhs: Value,
481) -> BuiltinResult<Value> {
482 if selector.components.is_empty() {
483 return Err(setfield_flow(
484 "setfield: index cell must contain at least one element",
485 ));
486 }
487
488 let resolved = resolve_indices(&Value::Cell(cell.clone()), selector)?;
489
490 let position = match resolved.len() {
491 1 => {
492 let idx = resolved[0];
493 if idx == 0 || idx > cell.data.len() {
494 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
495 }
496 idx - 1
497 }
498 2 => {
499 let row = resolved[0];
500 let col = resolved[1];
501 if row == 0 || row > cell.rows || col == 0 || col > cell.cols {
502 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
503 }
504 (row - 1) * cell.cols + (col - 1)
505 }
506 _ => {
507 return Err(setfield_flow(
508 "setfield: indexing with more than two indices is not supported yet",
509 ));
510 }
511 };
512
513 let handle = cell
514 .data
515 .get(position)
516 .ok_or_else(|| setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message))?
517 .clone();
518
519 let current = handle.clone();
520 let updated = assign_into_value(current, steps, rhs).await?;
521 cell.data[position] = updated;
522 Ok(Value::Cell(cell))
523}
524
525#[async_recursion::async_recursion(?Send)]
526async fn assign_into_value(value: Value, steps: &[FieldStep], rhs: Value) -> BuiltinResult<Value> {
527 if steps.is_empty() {
528 return Ok(rhs);
529 }
530 match value {
531 Value::Struct(struct_value) => assign_into_struct(struct_value, steps, rhs).await,
532 Value::Object(object) => assign_into_object(object, steps, rhs).await,
533 Value::Cell(cell) => assign_into_cell(cell, steps, rhs).await,
534 Value::HandleObject(handle) => assign_into_handle(handle, steps, rhs).await,
535 Value::Listener(_) => Err(setfield_flow(
536 "setfield: listeners do not support nested field assignment",
537 )),
538 other => Err(setfield_flow(format!(
539 "Struct contents assignment to a {other:?} object is not supported."
540 ))),
541 }
542}
543
544#[async_recursion::async_recursion(?Send)]
545async fn assign_into_struct(
546 mut struct_value: StructValue,
547 steps: &[FieldStep],
548 rhs: Value,
549) -> BuiltinResult<Value> {
550 let (first, rest) = steps
551 .split_first()
552 .expect("steps is non-empty when assign_into_struct is called");
553
554 if rest.is_empty() {
555 if let Some(selector) = &first.index {
556 let current = struct_value
557 .fields
558 .get(&first.name)
559 .cloned()
560 .ok_or_else(|| format!("Reference to non-existent field '{}'.", first.name))?;
561 let updated = assign_with_selector(current, selector, &[], rhs).await?;
562 struct_value.fields.insert(first.name.clone(), updated);
563 } else {
564 struct_value.fields.insert(first.name.clone(), rhs);
565 }
566 return Ok(Value::Struct(struct_value));
567 }
568
569 if let Some(selector) = &first.index {
570 let current = struct_value
571 .fields
572 .get(&first.name)
573 .cloned()
574 .ok_or_else(|| format!("Reference to non-existent field '{}'.", first.name))?;
575 let updated = assign_with_selector(current, selector, rest, rhs).await?;
576 struct_value.fields.insert(first.name.clone(), updated);
577 return Ok(Value::Struct(struct_value));
578 }
579
580 let current = struct_value
581 .fields
582 .get(&first.name)
583 .cloned()
584 .unwrap_or_else(|| Value::Struct(StructValue::new()));
585 let updated = assign_into_value(current, rest, rhs).await?;
586 struct_value.fields.insert(first.name.clone(), updated);
587 Ok(Value::Struct(struct_value))
588}
589
590async fn assign_into_object(
591 mut object: ObjectInstance,
592 steps: &[FieldStep],
593 rhs: Value,
594) -> BuiltinResult<Value> {
595 let (first, rest) = steps
596 .split_first()
597 .expect("steps is non-empty when assign_into_object is called");
598
599 if first.index.is_some() {
600 return Err(setfield_flow(
601 "setfield: indexing into object properties is not currently supported",
602 ));
603 }
604
605 if rest.is_empty() {
606 write_object_property(&mut object, &first.name, rhs).await?;
607 return Ok(Value::Object(object));
608 }
609
610 let current = read_object_property(&object, &first.name).await?;
611 let updated = assign_into_value(current, rest, rhs).await?;
612 write_object_property(&mut object, &first.name, updated).await?;
613 Ok(Value::Object(object))
614}
615
616async fn assign_into_cell(
617 cell: CellArray,
618 steps: &[FieldStep],
619 rhs: Value,
620) -> BuiltinResult<Value> {
621 let (first, rest) = steps
622 .split_first()
623 .expect("steps is non-empty when assign_into_cell is called");
624
625 let selector = first.index.as_ref().ok_or_else(|| {
626 setfield_flow("setfield: cell array assignments require indices in a cell array")
627 })?;
628 if rest.is_empty() {
629 assign_with_selector(Value::Cell(cell), selector, &[], rhs).await
630 } else {
631 assign_with_selector(Value::Cell(cell), selector, rest, rhs).await
632 }
633}
634
635#[async_recursion::async_recursion(?Send)]
636async fn assign_with_selector(
637 value: Value,
638 selector: &IndexSelector,
639 rest: &[FieldStep],
640 rhs: Value,
641) -> BuiltinResult<Value> {
642 let host_value = gather_if_needed_async(&value)
643 .await
644 .map_err(|flow| remap_setfield_flow(flow, Some("setfield: ")))?;
645 match host_value {
646 Value::Cell(mut cell) => {
647 let resolved = resolve_indices(&Value::Cell(cell.clone()), selector)?;
648 let position = match resolved.len() {
649 1 => {
650 let idx = resolved[0];
651 if idx == 0 || idx > cell.data.len() {
652 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
653 }
654 idx - 1
655 }
656 2 => {
657 let row = resolved[0];
658 let col = resolved[1];
659 if row == 0 || row > cell.rows || col == 0 || col > cell.cols {
660 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
661 }
662 (row - 1) * cell.cols + (col - 1)
663 }
664 _ => {
665 return Err(setfield_flow(
666 "setfield: indexing with more than two indices is not supported yet",
667 ));
668 }
669 };
670
671 let handle = cell
672 .data
673 .get(position)
674 .ok_or_else(|| setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message))?
675 .clone();
676 let existing = handle.clone();
677 let new_value = if rest.is_empty() {
678 rhs
679 } else {
680 assign_into_value(existing, rest, rhs).await?
681 };
682 cell.data[position] = new_value;
683 Ok(Value::Cell(cell))
684 }
685 Value::Tensor(mut tensor) => {
686 if !rest.is_empty() {
687 return Err(setfield_flow(
688 "setfield: cannot traverse deeper fields after indexing into a numeric tensor",
689 ));
690 }
691 assign_tensor_element(&mut tensor, selector, rhs)?;
692 Ok(Value::Tensor(tensor))
693 }
694 Value::LogicalArray(mut logical) => {
695 if !rest.is_empty() {
696 return Err(setfield_flow(
697 "setfield: cannot traverse deeper fields after indexing into a logical array",
698 ));
699 }
700 assign_logical_element(&mut logical, selector, rhs)?;
701 Ok(Value::LogicalArray(logical))
702 }
703 Value::StringArray(mut sa) => {
704 if !rest.is_empty() {
705 return Err(setfield_flow(
706 "setfield: cannot traverse deeper fields after indexing into a string array",
707 ));
708 }
709 assign_string_array_element(&mut sa, selector, rhs)?;
710 Ok(Value::StringArray(sa))
711 }
712 Value::CharArray(mut ca) => {
713 if !rest.is_empty() {
714 return Err(setfield_flow(
715 "setfield: cannot traverse deeper fields after indexing into a char array",
716 ));
717 }
718 assign_char_array_element(&mut ca, selector, rhs)?;
719 Ok(Value::CharArray(ca))
720 }
721 Value::ComplexTensor(mut tensor) => {
722 if !rest.is_empty() {
723 return Err(setfield_flow(
724 "setfield: cannot traverse deeper fields after indexing into a complex tensor",
725 ));
726 }
727 assign_complex_tensor_element(&mut tensor, selector, rhs)?;
728 Ok(Value::ComplexTensor(tensor))
729 }
730 other => Err(setfield_flow(format!(
731 "Struct contents assignment to a {other:?} object is not supported."
732 ))),
733 }
734}
735
736fn assign_tensor_element(
737 tensor: &mut Tensor,
738 selector: &IndexSelector,
739 rhs: Value,
740) -> BuiltinResult<()> {
741 let resolved = resolve_indices(&Value::Tensor(tensor.clone()), selector)?;
742 let value = value_to_scalar(rhs)?;
743 match resolved.len() {
744 1 => {
745 let idx = resolved[0];
746 if idx == 0 || idx > tensor.data.len() {
747 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
748 }
749 tensor.data[idx - 1] = value;
750 Ok(())
751 }
752 2 => {
753 let row = resolved[0];
754 let col = resolved[1];
755 if row == 0 || row > tensor.rows() || col == 0 || col > tensor.cols() {
756 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
757 }
758 let pos = (row - 1) + (col - 1) * tensor.rows();
759 tensor
760 .data
761 .get_mut(pos)
762 .map(|slot| *slot = value)
763 .ok_or_else(|| setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message))
764 }
765 _ => Err(setfield_flow(
766 "setfield: indexing with more than two indices is not supported yet",
767 )),
768 }
769}
770
771fn assign_logical_element(
772 logical: &mut LogicalArray,
773 selector: &IndexSelector,
774 rhs: Value,
775) -> BuiltinResult<()> {
776 let resolved = resolve_indices(&Value::LogicalArray(logical.clone()), selector)?;
777 let value = value_to_bool(rhs)?;
778 match resolved.len() {
779 1 => {
780 let idx = resolved[0];
781 if idx == 0 || idx > logical.data.len() {
782 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
783 }
784 logical.data[idx - 1] = if value { 1 } else { 0 };
785 Ok(())
786 }
787 2 => {
788 if logical.shape.len() < 2 {
789 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
790 }
791 let row = resolved[0];
792 let col = resolved[1];
793 let rows = logical.shape[0];
794 let cols = logical.shape[1];
795 if row == 0 || row > rows || col == 0 || col > cols {
796 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
797 }
798 let pos = (row - 1) + (col - 1) * rows;
799 if pos >= logical.data.len() {
800 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
801 }
802 logical.data[pos] = if value { 1 } else { 0 };
803 Ok(())
804 }
805 _ => Err(setfield_flow(
806 "setfield: indexing with more than two indices is not supported yet",
807 )),
808 }
809}
810
811fn assign_string_array_element(
812 array: &mut runmat_builtins::StringArray,
813 selector: &IndexSelector,
814 rhs: Value,
815) -> BuiltinResult<()> {
816 let resolved = resolve_indices(&Value::StringArray(array.clone()), selector)?;
817 let text = String::try_from(&rhs).map_err(|_| {
818 setfield_flow("setfield: string assignments require text-compatible values")
819 })?;
820 match resolved.len() {
821 1 => {
822 let idx = resolved[0];
823 if idx == 0 || idx > array.data.len() {
824 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
825 }
826 array.data[idx - 1] = text;
827 Ok(())
828 }
829 2 => {
830 let row = resolved[0];
831 let col = resolved[1];
832 if row == 0 || row > array.rows || col == 0 || col > array.cols {
833 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
834 }
835 let pos = (row - 1) + (col - 1) * array.rows;
836 if pos >= array.data.len() {
837 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
838 }
839 array.data[pos] = text;
840 Ok(())
841 }
842 _ => Err(setfield_flow(
843 "setfield: indexing with more than two indices is not supported yet",
844 )),
845 }
846}
847
848fn assign_char_array_element(
849 array: &mut CharArray,
850 selector: &IndexSelector,
851 rhs: Value,
852) -> BuiltinResult<()> {
853 let resolved = resolve_indices(&Value::CharArray(array.clone()), selector)?;
854 let text = String::try_from(&rhs)
855 .map_err(|_| setfield_flow("setfield: char assignments require text-compatible values"))?;
856 if text.chars().count() != 1 {
857 return Err(setfield_flow(
858 "setfield: char array assignments require single characters",
859 ));
860 }
861 let ch = text.chars().next().unwrap();
862 match resolved.len() {
863 1 => {
864 let idx = resolved[0];
865 if idx == 0 || idx > array.data.len() {
866 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
867 }
868 array.data[idx - 1] = ch;
869 Ok(())
870 }
871 2 => {
872 let row = resolved[0];
873 let col = resolved[1];
874 if row == 0 || row > array.rows || col == 0 || col > array.cols {
875 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
876 }
877 let pos = (row - 1) * array.cols + (col - 1);
878 if pos >= array.data.len() {
879 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
880 }
881 array.data[pos] = ch;
882 Ok(())
883 }
884 _ => Err(setfield_flow(
885 "setfield: indexing with more than two indices is not supported yet",
886 )),
887 }
888}
889
890fn assign_complex_tensor_element(
891 tensor: &mut ComplexTensor,
892 selector: &IndexSelector,
893 rhs: Value,
894) -> BuiltinResult<()> {
895 let resolved = resolve_indices(&Value::ComplexTensor(tensor.clone()), selector)?;
896 let (re, im) = match rhs {
897 Value::Complex(r, i) => (r, i),
898 Value::Num(n) => (n, 0.0),
899 Value::Int(i) => (i.to_f64(), 0.0),
900 other => {
901 return Err(setfield_flow(format!(
902 "setfield: cannot assign {other:?} into a complex tensor element"
903 )));
904 }
905 };
906 match resolved.len() {
907 1 => {
908 let idx = resolved[0];
909 if idx == 0 || idx > tensor.data.len() {
910 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
911 }
912 tensor.data[idx - 1] = (re, im);
913 Ok(())
914 }
915 2 => {
916 let row = resolved[0];
917 let col = resolved[1];
918 if row == 0 || row > tensor.rows || col == 0 || col > tensor.cols {
919 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
920 }
921 let pos = (row - 1) + (col - 1) * tensor.rows;
922 if pos >= tensor.data.len() {
923 return Err(setfield_flow(SETFIELD_ERROR_INDEX_OUT_OF_BOUNDS.message));
924 }
925 tensor.data[pos] = (re, im);
926 Ok(())
927 }
928 _ => Err(setfield_flow(
929 "setfield: indexing with more than two indices is not supported yet",
930 )),
931 }
932}
933
934async fn read_object_property(obj: &ObjectInstance, name: &str) -> BuiltinResult<Value> {
935 if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
936 if prop.is_static {
937 return Err(setfield_flow(format!(
938 "You cannot access the static property '{}' through an instance of class '{}'.",
939 name, obj.class_name
940 )));
941 }
942 if prop.get_access == Access::Private {
943 return Err(setfield_private_access(format!(
944 "You cannot get the '{}' property of '{}' class.",
945 name, obj.class_name
946 )));
947 }
948 if prop.is_dependent {
949 let getter = object_property_getter_name(name);
950 match call_builtin_async(&getter, &[Value::Object(obj.clone())]).await {
951 Ok(value) => return Ok(value),
952 Err(err) => {
953 if !is_undefined_function(&err) {
954 return Err(remap_setfield_flow(err, None));
955 }
956 }
957 }
958 if let Some(value) = obj.properties.get(&format!("{name}_backing")) {
959 return Ok(value.clone());
960 }
961 }
962 }
963
964 if let Some(value) = obj.properties.get(name) {
965 return Ok(value.clone());
966 }
967
968 if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
969 if prop.get_access == Access::Private {
970 return Err(setfield_private_access(format!(
971 "You cannot get the '{}' property of '{}' class.",
972 name, obj.class_name
973 )));
974 }
975 return Err(setfield_flow(format!(
976 "No public property '{}' for class '{}'.",
977 name, obj.class_name
978 )));
979 }
980
981 Err(setfield_flow(format!(
982 "Undefined property '{}' for class {}",
983 name, obj.class_name
984 )))
985}
986
987async fn write_object_property(
988 obj: &mut ObjectInstance,
989 name: &str,
990 rhs: Value,
991) -> BuiltinResult<()> {
992 if dynamicprops::metadata_assignment(obj, name, rhs.clone())? {
993 return Ok(());
994 }
995
996 if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
997 if prop.is_static {
998 return Err(setfield_static_access(format!(
999 "Property '{}' is static; use classref('{}').{}",
1000 name, obj.class_name, name
1001 )));
1002 }
1003 if prop.set_access == Access::Private {
1004 return Err(setfield_private_access(format!(
1005 "Property '{name}' is private"
1006 )));
1007 }
1008 if prop.is_dependent {
1009 let setter = object_property_setter_name(name);
1010 match call_builtin_async(&setter, &[Value::Object(obj.clone()), rhs.clone()]).await {
1011 Ok(value) => {
1012 if let Value::Object(updated) = value {
1013 *obj = updated;
1014 return Ok(());
1015 }
1016 return Err(setfield_flow(format!(
1017 "Dependent property setter for '{}' must return the updated object",
1018 name
1019 )));
1020 }
1021 Err(err) => {
1022 if !is_undefined_function(&err) {
1023 return Err(remap_setfield_flow(err, None));
1024 }
1025 }
1026 }
1027 obj.properties.insert(format!("{name}_backing"), rhs);
1028 return Ok(());
1029 }
1030 }
1031
1032 if dynamicprops::dynamic_property_assign(obj, name, rhs.clone())? {
1033 return Ok(());
1034 }
1035
1036 obj.properties.insert(name.to_string(), rhs);
1037 Ok(())
1038}
1039
1040async fn assign_into_handle(
1041 handle: HandleRef,
1042 steps: &[FieldStep],
1043 rhs: Value,
1044) -> BuiltinResult<Value> {
1045 if steps.is_empty() {
1046 return Err(setfield_flow(
1047 "setfield: expected at least one field name when assigning into a handle",
1048 ));
1049 }
1050 if !crate::is_handle_valid(&handle) {
1051 return Err(setfield_flow(format!(
1052 "Invalid or deleted handle object '{}'.",
1053 handle.class_name
1054 )));
1055 }
1056 let current = runmat_gc::gc_clone_value(&handle.target)
1057 .map_err(|e| setfield_flow(format!("setfield: invalid handle target: {e}")))?;
1058 let updated = assign_into_value(current.clone(), steps, rhs).await?;
1059 runmat_gc::gc_with_value_mut(&handle.target, |target| -> BuiltinResult<()> {
1060 let target_valid = match target {
1061 Value::Object(obj) => !matches!(
1062 obj.properties.get(crate::HANDLE_VALID_FLAG_PROPERTY),
1063 Some(Value::Bool(false))
1064 ),
1065 _ => {
1066 return Err(setfield_flow(format!(
1067 "Invalid or deleted handle object '{}'.",
1068 handle.class_name
1069 )));
1070 }
1071 };
1072 if !target_valid {
1073 return Err(setfield_flow(format!(
1074 "Invalid or deleted handle object '{}'.",
1075 handle.class_name
1076 )));
1077 }
1078 if *target != current {
1079 return Err(setfield_flow(
1080 "setfield: handle target changed during asynchronous assignment",
1081 ));
1082 }
1083 runmat_gc::gc_record_handle_write(&handle.target, &updated);
1084 *target = updated;
1085 Ok(())
1086 })
1087 .map_err(|e| setfield_flow(format!("setfield: invalid handle target: {e}")))??;
1088 Ok(Value::HandleObject(handle))
1089}
1090
1091fn is_index_selector(value: &Value) -> bool {
1092 matches!(value, Value::Cell(_))
1093}
1094
1095fn parse_index_selector(value: Value) -> BuiltinResult<IndexSelector> {
1096 let Value::Cell(cell) = value else {
1097 return Err(setfield_flow(SETFIELD_ERROR_INDEX_SELECTOR_TYPE.message));
1098 };
1099 let mut components = Vec::with_capacity(cell.data.len());
1100 for handle in &cell.data {
1101 let entry = handle;
1102 components.push(parse_index_component(entry)?);
1103 }
1104 Ok(IndexSelector { components })
1105}
1106
1107fn parse_index_component(value: &Value) -> BuiltinResult<IndexComponent> {
1108 match value {
1109 Value::CharArray(ca) => {
1110 let text: String = ca.data.iter().collect();
1111 parse_index_text(text.trim())
1112 }
1113 Value::String(s) => parse_index_text(s.trim()),
1114 Value::StringArray(sa) if sa.data.len() == 1 => parse_index_text(sa.data[0].trim()),
1115 _ => {
1116 let idx = parse_positive_scalar(value).map_err(|err| {
1117 setfield_flow(format!(
1118 "setfield: invalid index element ({})",
1119 err.message()
1120 ))
1121 })?;
1122 Ok(IndexComponent::Scalar(idx))
1123 }
1124 }
1125}
1126
1127fn parse_index_text(text: &str) -> BuiltinResult<IndexComponent> {
1128 if text.eq_ignore_ascii_case("end") {
1129 return Ok(IndexComponent::End);
1130 }
1131 if text == ":" {
1132 return Err(setfield_flow(
1133 "setfield: ':' indexing is not currently supported",
1134 ));
1135 }
1136 if text.is_empty() {
1137 return Err(setfield_flow("setfield: index elements must not be empty"));
1138 }
1139 if let Ok(value) = text.parse::<usize>() {
1140 if value == 0 {
1141 return Err(setfield_flow("setfield: index must be >= 1"));
1142 }
1143 return Ok(IndexComponent::Scalar(value));
1144 }
1145 Err(setfield_flow(format!(
1146 "setfield: invalid index element '{}'",
1147 text
1148 )))
1149}
1150
1151fn parse_positive_scalar(value: &Value) -> BuiltinResult<usize> {
1152 let number = match value {
1153 Value::Int(i) => i.to_i64() as f64,
1154 Value::Num(n) => *n,
1155 Value::Tensor(t) if t.data.len() == 1 => t.data[0],
1156 _ => {
1157 let repr = format!("{value:?}");
1158 return Err(setfield_flow(format!(
1159 "expected positive integer index, got {repr}"
1160 )));
1161 }
1162 };
1163
1164 if !number.is_finite() {
1165 return Err(setfield_flow("index must be a finite number"));
1166 }
1167 if number.fract() != 0.0 {
1168 return Err(setfield_flow("index must be an integer"));
1169 }
1170 if number <= 0.0 {
1171 return Err(setfield_flow("index must be >= 1"));
1172 }
1173 if number > usize::MAX as f64 {
1174 return Err(setfield_flow("index exceeds platform limits"));
1175 }
1176 Ok(number as usize)
1177}
1178
1179fn parse_field_name(value: Value) -> BuiltinResult<String> {
1180 match value {
1181 Value::String(s) => Ok(s),
1182 Value::StringArray(sa) => {
1183 if sa.data.len() == 1 {
1184 Ok(sa.data[0].clone())
1185 } else {
1186 Err(setfield_flow(
1187 "setfield: field names must be scalar string arrays or character vectors",
1188 ))
1189 }
1190 }
1191 Value::CharArray(ca) => {
1192 if ca.rows == 1 {
1193 Ok(ca.data.iter().collect())
1194 } else {
1195 Err(setfield_flow(
1196 "setfield: field names must be 1-by-N character vectors",
1197 ))
1198 }
1199 }
1200 other => Err(setfield_flow(format!(
1201 "setfield: expected field name, got {other:?}"
1202 ))),
1203 }
1204}
1205
1206fn resolve_indices(value: &Value, selector: &IndexSelector) -> BuiltinResult<Vec<usize>> {
1207 let dims = selector.components.len();
1208 let mut resolved = Vec::with_capacity(dims);
1209 for (dim_idx, component) in selector.components.iter().enumerate() {
1210 let index = match component {
1211 IndexComponent::Scalar(idx) => *idx,
1212 IndexComponent::End => dimension_length(value, dims, dim_idx)?,
1213 };
1214 resolved.push(index);
1215 }
1216 Ok(resolved)
1217}
1218
1219fn dimension_length(value: &Value, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1220 match value {
1221 Value::Tensor(tensor) => tensor_dimension_length(tensor, dims, dim_idx),
1222 Value::Cell(cell) => cell_dimension_length(cell, dims, dim_idx),
1223 Value::StringArray(array) => string_array_dimension_length(array, dims, dim_idx),
1224 Value::LogicalArray(logical) => logical_array_dimension_length(logical, dims, dim_idx),
1225 Value::CharArray(array) => char_array_dimension_length(array, dims, dim_idx),
1226 Value::ComplexTensor(tensor) => complex_tensor_dimension_length(tensor, dims, dim_idx),
1227 Value::Num(_) | Value::Int(_) | Value::Bool(_) => {
1228 if dims == 1 {
1229 Ok(1)
1230 } else {
1231 Err(setfield_flow(
1232 "setfield: indexing with more than one dimension is not supported for scalars",
1233 ))
1234 }
1235 }
1236 other => Err(setfield_flow(format!(
1237 "Struct contents assignment to a {other:?} object is not supported."
1238 ))),
1239 }
1240}
1241
1242fn tensor_dimension_length(tensor: &Tensor, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1243 if dims == 1 {
1244 let total = tensor.data.len();
1245 if total == 0 {
1246 return Err(setfield_flow(
1247 "Index exceeds the number of array elements (0).",
1248 ));
1249 }
1250 return Ok(total);
1251 }
1252 if dims > 2 {
1253 return Err(setfield_flow(
1254 "setfield: indexing with more than two indices is not supported yet",
1255 ));
1256 }
1257 let len = if dim_idx == 0 {
1258 tensor.rows()
1259 } else {
1260 tensor.cols()
1261 };
1262 if len == 0 {
1263 return Err(setfield_flow(
1264 "Index exceeds the number of array elements (0).",
1265 ));
1266 }
1267 Ok(len)
1268}
1269
1270fn cell_dimension_length(cell: &CellArray, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1271 if dims == 1 {
1272 let total = cell.data.len();
1273 if total == 0 {
1274 return Err(setfield_flow(
1275 "Index exceeds the number of array elements (0).",
1276 ));
1277 }
1278 return Ok(total);
1279 }
1280 if dims > 2 {
1281 return Err(setfield_flow(
1282 "setfield: indexing with more than two indices is not supported yet",
1283 ));
1284 }
1285 let len = if dim_idx == 0 { cell.rows } else { cell.cols };
1286 if len == 0 {
1287 return Err(setfield_flow(
1288 "Index exceeds the number of array elements (0).",
1289 ));
1290 }
1291 Ok(len)
1292}
1293
1294fn string_array_dimension_length(
1295 array: &runmat_builtins::StringArray,
1296 dims: usize,
1297 dim_idx: usize,
1298) -> BuiltinResult<usize> {
1299 if dims == 1 {
1300 let total = array.data.len();
1301 if total == 0 {
1302 return Err(setfield_flow(
1303 "Index exceeds the number of array elements (0).",
1304 ));
1305 }
1306 return Ok(total);
1307 }
1308 if dims > 2 {
1309 return Err(setfield_flow(
1310 "setfield: indexing with more than two indices is not supported yet",
1311 ));
1312 }
1313 let len = if dim_idx == 0 { array.rows } else { array.cols };
1314 if len == 0 {
1315 return Err(setfield_flow(
1316 "Index exceeds the number of array elements (0).",
1317 ));
1318 }
1319 Ok(len)
1320}
1321
1322fn logical_array_dimension_length(
1323 array: &LogicalArray,
1324 dims: usize,
1325 dim_idx: usize,
1326) -> BuiltinResult<usize> {
1327 if dims == 1 {
1328 let total = array.data.len();
1329 if total == 0 {
1330 return Err(setfield_flow(
1331 "Index exceeds the number of array elements (0).",
1332 ));
1333 }
1334 return Ok(total);
1335 }
1336 if dims > 2 {
1337 return Err(setfield_flow(
1338 "setfield: indexing with more than two indices is not supported yet",
1339 ));
1340 }
1341 if array.shape.len() < dims {
1342 return Err(setfield_flow(
1343 "Index exceeds the number of array elements (0).",
1344 ));
1345 }
1346 let len = array.shape[dim_idx];
1347 if len == 0 {
1348 return Err(setfield_flow(
1349 "Index exceeds the number of array elements (0).",
1350 ));
1351 }
1352 Ok(len)
1353}
1354
1355fn char_array_dimension_length(
1356 array: &CharArray,
1357 dims: usize,
1358 dim_idx: usize,
1359) -> BuiltinResult<usize> {
1360 if dims == 1 {
1361 let total = array.data.len();
1362 if total == 0 {
1363 return Err(setfield_flow(
1364 "Index exceeds the number of array elements (0).",
1365 ));
1366 }
1367 return Ok(total);
1368 }
1369 if dims > 2 {
1370 return Err(setfield_flow(
1371 "setfield: indexing with more than two indices is not supported yet",
1372 ));
1373 }
1374 let len = if dim_idx == 0 { array.rows } else { array.cols };
1375 if len == 0 {
1376 return Err(setfield_flow(
1377 "Index exceeds the number of array elements (0).",
1378 ));
1379 }
1380 Ok(len)
1381}
1382
1383fn complex_tensor_dimension_length(
1384 tensor: &ComplexTensor,
1385 dims: usize,
1386 dim_idx: usize,
1387) -> BuiltinResult<usize> {
1388 if dims == 1 {
1389 let total = tensor.data.len();
1390 if total == 0 {
1391 return Err(setfield_flow(
1392 "Index exceeds the number of array elements (0).",
1393 ));
1394 }
1395 return Ok(total);
1396 }
1397 if dims > 2 {
1398 return Err(setfield_flow(
1399 "setfield: indexing with more than two indices is not supported yet",
1400 ));
1401 }
1402 let len = if dim_idx == 0 {
1403 tensor.rows
1404 } else {
1405 tensor.cols
1406 };
1407 if len == 0 {
1408 return Err(setfield_flow(
1409 "Index exceeds the number of array elements (0).",
1410 ));
1411 }
1412 Ok(len)
1413}
1414
1415fn value_to_scalar(value: Value) -> BuiltinResult<f64> {
1416 match value {
1417 Value::Num(n) => Ok(n),
1418 Value::Int(i) => Ok(i.to_f64()),
1419 Value::Bool(b) => Ok(if b { 1.0 } else { 0.0 }),
1420 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
1421 other => Err(setfield_flow(format!(
1422 "setfield: cannot assign {other:?} into a numeric tensor element"
1423 ))),
1424 }
1425}
1426
1427fn value_to_bool(value: Value) -> BuiltinResult<bool> {
1428 match value {
1429 Value::Bool(b) => Ok(b),
1430 Value::Num(n) => Ok(n != 0.0),
1431 Value::Int(i) => Ok(i.to_i64() != 0),
1432 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0] != 0.0),
1433 other => Err(setfield_flow(format!(
1434 "setfield: cannot assign {other:?} into a logical array element"
1435 ))),
1436 }
1437}
1438
1439fn is_struct_array(cell: &CellArray) -> bool {
1440 cell.data
1441 .iter()
1442 .all(|handle| matches!(handle, Value::Struct(_)))
1443}
1444
1445#[cfg(test)]
1446pub(crate) mod tests {
1447 use super::*;
1448 use runmat_builtins::{
1449 Access, CellArray, ClassDef, HandleRef, IntValue, ObjectInstance, PropertyDef, StructValue,
1450 };
1451 use runmat_gc::gc_allocate;
1452
1453 fn error_message(err: crate::RuntimeError) -> String {
1454 err.message().to_string()
1455 }
1456
1457 fn run_setfield(base: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
1458 futures::executor::block_on(setfield_builtin(base, rest))
1459 }
1460
1461 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1462 #[test]
1463 fn setfield_creates_scalar_field() {
1464 let struct_value = StructValue::new();
1465 let updated = run_setfield(
1466 Value::Struct(struct_value),
1467 vec![Value::from("answer"), Value::Num(42.0)],
1468 )
1469 .expect("setfield");
1470 match updated {
1471 Value::Struct(st) => {
1472 assert_eq!(
1473 st.fields.get("answer"),
1474 Some(&Value::Num(42.0)),
1475 "field should be inserted"
1476 );
1477 }
1478 other => panic!("expected struct result, got {other:?}"),
1479 }
1480 }
1481
1482 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1483 #[test]
1484 fn setfield_creates_nested_structs() {
1485 let struct_value = StructValue::new();
1486 let updated = run_setfield(
1487 Value::Struct(struct_value),
1488 vec![
1489 Value::from("solver"),
1490 Value::from("name"),
1491 Value::from("cg"),
1492 ],
1493 )
1494 .expect("setfield");
1495 match updated {
1496 Value::Struct(st) => {
1497 let solver = st.fields.get("solver").expect("solver field");
1498 match solver {
1499 Value::Struct(inner) => {
1500 assert_eq!(
1501 inner.fields.get("name"),
1502 Some(&Value::from("cg")),
1503 "inner field should exist"
1504 );
1505 }
1506 other => panic!("expected inner struct, got {other:?}"),
1507 }
1508 }
1509 other => panic!("expected struct result, got {other:?}"),
1510 }
1511 }
1512
1513 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1514 #[test]
1515 fn setfield_updates_struct_array_element() {
1516 let mut a = StructValue::new();
1517 a.fields
1518 .insert("id".to_string(), Value::Int(IntValue::I32(1)));
1519 let mut b = StructValue::new();
1520 b.fields
1521 .insert("id".to_string(), Value::Int(IntValue::I32(2)));
1522 let array = CellArray::new_with_shape(vec![Value::Struct(a), Value::Struct(b)], vec![1, 2])
1523 .unwrap();
1524 let indices =
1525 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
1526 let updated = run_setfield(
1527 Value::Cell(array),
1528 vec![
1529 Value::Cell(indices),
1530 Value::from("id"),
1531 Value::Int(IntValue::I32(42)),
1532 ],
1533 )
1534 .expect("setfield");
1535 match updated {
1536 Value::Cell(cell) => {
1537 let second = &cell.data[1].clone();
1538 match second {
1539 Value::Struct(st) => {
1540 assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(42))));
1541 }
1542 other => panic!("expected struct element, got {other:?}"),
1543 }
1544 }
1545 other => panic!("expected cell array, got {other:?}"),
1546 }
1547 }
1548
1549 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1550 #[test]
1551 fn setfield_assigns_into_cell_then_struct() {
1552 let mut inner1 = StructValue::new();
1553 inner1.fields.insert("value".to_string(), Value::Num(1.0));
1554 let mut inner2 = StructValue::new();
1555 inner2.fields.insert("value".to_string(), Value::Num(2.0));
1556 let cell = CellArray::new_with_shape(
1557 vec![Value::Struct(inner1), Value::Struct(inner2)],
1558 vec![1, 2],
1559 )
1560 .unwrap();
1561 let mut root = StructValue::new();
1562 root.fields.insert("samples".to_string(), Value::Cell(cell));
1563
1564 let index_cell =
1565 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
1566 let updated = run_setfield(
1567 Value::Struct(root),
1568 vec![
1569 Value::from("samples"),
1570 Value::Cell(index_cell),
1571 Value::from("value"),
1572 Value::Num(10.0),
1573 ],
1574 )
1575 .expect("setfield");
1576
1577 match updated {
1578 Value::Struct(st) => {
1579 let samples = st.fields.get("samples").expect("samples field");
1580 match samples {
1581 Value::Cell(cell) => {
1582 let value = &cell.data[1].clone();
1583 match value {
1584 Value::Struct(inner) => {
1585 assert_eq!(inner.fields.get("value"), Some(&Value::Num(10.0)));
1586 }
1587 other => panic!("expected struct, got {other:?}"),
1588 }
1589 }
1590 other => panic!("expected cell array, got {other:?}"),
1591 }
1592 }
1593 other => panic!("expected struct, got {other:?}"),
1594 }
1595 }
1596
1597 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1598 #[test]
1599 fn setfield_struct_array_with_end_index() {
1600 let mut first = StructValue::new();
1601 first
1602 .fields
1603 .insert("id".to_string(), Value::Int(IntValue::I32(1)));
1604 let mut second = StructValue::new();
1605 second
1606 .fields
1607 .insert("id".to_string(), Value::Int(IntValue::I32(2)));
1608 let array = CellArray::new_with_shape(
1609 vec![Value::Struct(first), Value::Struct(second)],
1610 vec![1, 2],
1611 )
1612 .unwrap();
1613 let index_cell = CellArray::new_with_shape(vec![Value::from("end")], vec![1, 1]).unwrap();
1614 let updated = run_setfield(
1615 Value::Cell(array),
1616 vec![
1617 Value::Cell(index_cell),
1618 Value::from("id"),
1619 Value::Int(IntValue::I32(99)),
1620 ],
1621 )
1622 .expect("setfield");
1623 match updated {
1624 Value::Cell(cell) => {
1625 let second = &cell.data[1].clone();
1626 match second {
1627 Value::Struct(st) => {
1628 assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(99))));
1629 }
1630 other => panic!("expected struct element, got {other:?}"),
1631 }
1632 }
1633 other => panic!("expected cell array result, got {other:?}"),
1634 }
1635 }
1636
1637 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1638 #[test]
1639 fn setfield_assigns_object_property() {
1640 let mut class_def = ClassDef {
1641 name: "Simple".to_string(),
1642 parent: None,
1643 properties: Default::default(),
1644 methods: Default::default(),
1645 };
1646 class_def.properties.insert(
1647 "x".to_string(),
1648 PropertyDef {
1649 name: "x".to_string(),
1650 is_static: false,
1651 is_constant: false,
1652 is_dependent: false,
1653 get_access: Access::Public,
1654 set_access: Access::Public,
1655 default_value: None,
1656 },
1657 );
1658 runmat_builtins::register_class(class_def);
1659
1660 let mut obj = ObjectInstance::new("Simple".to_string());
1661 obj.properties.insert("x".to_string(), Value::Num(0.0));
1662
1663 let updated = run_setfield(Value::Object(obj), vec![Value::from("x"), Value::Num(5.0)])
1664 .expect("setfield");
1665
1666 match updated {
1667 Value::Object(o) => {
1668 assert_eq!(o.properties.get("x"), Some(&Value::Num(5.0)));
1669 }
1670 other => panic!("expected object result, got {other:?}"),
1671 }
1672 }
1673
1674 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1675 #[test]
1676 fn setfield_errors_when_indexing_missing_field() {
1677 let struct_value = StructValue::new();
1678 let index_cell =
1679 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(1))], vec![1, 1]).unwrap();
1680 let err = error_message(
1681 run_setfield(
1682 Value::Struct(struct_value),
1683 vec![
1684 Value::from("missing"),
1685 Value::Cell(index_cell),
1686 Value::Num(1.0),
1687 ],
1688 )
1689 .expect_err("setfield should fail when field is missing"),
1690 );
1691 assert!(
1692 err.contains("Reference to non-existent field 'missing'."),
1693 "unexpected error message: {err}"
1694 );
1695 }
1696
1697 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1698 #[test]
1699 fn setfield_errors_on_static_property_assignment() {
1700 let mut class_def = ClassDef {
1701 name: "StaticSetfield".to_string(),
1702 parent: None,
1703 properties: Default::default(),
1704 methods: Default::default(),
1705 };
1706 class_def.properties.insert(
1707 "version".to_string(),
1708 PropertyDef {
1709 name: "version".to_string(),
1710 is_static: true,
1711 is_constant: false,
1712 is_dependent: false,
1713 get_access: Access::Public,
1714 set_access: Access::Public,
1715 default_value: None,
1716 },
1717 );
1718 runmat_builtins::register_class(class_def);
1719
1720 let obj = ObjectInstance::new("StaticSetfield".to_string());
1721 let err = error_message(
1722 run_setfield(
1723 Value::Object(obj),
1724 vec![Value::from("version"), Value::Num(2.0)],
1725 )
1726 .expect_err("setfield should reject static property writes"),
1727 );
1728 assert!(
1729 err.contains("Property 'version' is static"),
1730 "unexpected error message: {err}"
1731 );
1732 }
1733
1734 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1735 #[test]
1736 fn setfield_rejects_inherited_static_property_assignment() {
1737 let parent_name = "runmat.unittest.StaticSetfieldParent";
1738 let child_name = "runmat.unittest.StaticSetfieldChild";
1739
1740 let mut parent = ClassDef {
1741 name: parent_name.to_string(),
1742 parent: None,
1743 properties: Default::default(),
1744 methods: Default::default(),
1745 };
1746 parent.properties.insert(
1747 "version".to_string(),
1748 PropertyDef {
1749 name: "version".to_string(),
1750 is_static: true,
1751 is_constant: false,
1752 is_dependent: false,
1753 get_access: Access::Public,
1754 set_access: Access::Public,
1755 default_value: None,
1756 },
1757 );
1758 runmat_builtins::register_class(parent);
1759 runmat_builtins::register_class(ClassDef {
1760 name: child_name.to_string(),
1761 parent: Some(parent_name.to_string()),
1762 properties: Default::default(),
1763 methods: Default::default(),
1764 });
1765
1766 let obj = ObjectInstance::new(child_name.to_string());
1767 let err = error_message(
1768 run_setfield(
1769 Value::Object(obj),
1770 vec![Value::from("version"), Value::Num(2.0)],
1771 )
1772 .expect_err("setfield should reject inherited static property writes"),
1773 );
1774 assert!(
1775 err.contains("Property 'version' is static"),
1776 "unexpected error message: {err}"
1777 );
1778 }
1779
1780 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1781 #[test]
1782 fn setfield_updates_handle_target() {
1783 let mut inner = ObjectInstance::new("PointHandle".to_string());
1784 inner.properties.insert("x".to_string(), Value::Num(0.0));
1785 let gc_ptr = gc_allocate(Value::Object(inner)).expect("gc allocation");
1786 let handle_ptr = gc_ptr;
1787 let handle = HandleRef {
1788 class_name: "PointHandle".to_string(),
1789 target: handle_ptr,
1790 valid: true,
1791 };
1792
1793 let updated = run_setfield(
1794 Value::HandleObject(handle.clone()),
1795 vec![Value::from("x"), Value::Num(7.0)],
1796 )
1797 .expect("setfield handle update");
1798
1799 match updated {
1800 Value::HandleObject(h) => assert!(crate::is_handle_valid(&h)),
1801 other => panic!("expected handle, got {other:?}"),
1802 }
1803
1804 let pointee = runmat_gc::gc_clone_value(&gc_ptr).expect("valid handle target");
1805 match pointee {
1806 Value::Object(obj) => {
1807 assert_eq!(obj.properties.get("x"), Some(&Value::Num(7.0)));
1808 }
1809 other => panic!("expected object pointee, got {other:?}"),
1810 }
1811 }
1812
1813 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1814 #[test]
1815 #[cfg(feature = "wgpu")]
1816 fn setfield_gpu_tensor_indexing_gathers_to_host() {
1817 use runmat_accelerate::backend::wgpu::provider::{
1818 register_wgpu_provider, WgpuProviderOptions,
1819 };
1820 use runmat_accelerate_api::HostTensorView;
1821
1822 if runmat_accelerate_api::provider().is_none()
1823 && register_wgpu_provider(WgpuProviderOptions::default()).is_err()
1824 {
1825 runmat_accelerate::simple_provider::register_inprocess_provider();
1826 }
1827
1828 let provider = runmat_accelerate_api::provider().expect("accel provider");
1829 let data = [1.0, 2.0, 3.0, 4.0];
1830 let shape = [2usize, 2usize];
1831 let view = HostTensorView {
1832 data: &data,
1833 shape: &shape,
1834 };
1835 let handle = provider.upload(&view).expect("upload");
1836
1837 let mut root = StructValue::new();
1838 root.fields
1839 .insert("values".to_string(), Value::GpuTensor(handle));
1840
1841 let index_cell = CellArray::new_with_shape(
1842 vec![Value::Int(IntValue::I32(2)), Value::Int(IntValue::I32(2))],
1843 vec![1, 2],
1844 )
1845 .unwrap();
1846
1847 let updated = run_setfield(
1848 Value::Struct(root),
1849 vec![
1850 Value::from("values"),
1851 Value::Cell(index_cell),
1852 Value::Num(99.0),
1853 ],
1854 )
1855 .expect("setfield gpu value");
1856
1857 match updated {
1858 Value::Struct(st) => {
1859 let values = st.fields.get("values").expect("values field");
1860 match values {
1861 Value::Tensor(tensor) => {
1862 assert_eq!(tensor.shape, vec![2, 2]);
1863 assert_eq!(tensor.data[3], 99.0);
1864 }
1865 other => panic!("expected tensor after gather, got {other:?}"),
1866 }
1867 }
1868 other => panic!("expected struct result, got {other:?}"),
1869 }
1870 }
1871
1872 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1873 #[test]
1874 fn setfield_undefined_detection_requires_identifier() {
1875 let with_identifier = build_runtime_error("missing")
1876 .with_identifier(crate::IDENT_UNDEFINED_FUNCTION)
1877 .build();
1878 assert!(is_undefined_function(&with_identifier));
1879
1880 let message_only =
1881 build_runtime_error(format!("{} message only", crate::IDENT_UNDEFINED_FUNCTION))
1882 .build();
1883 assert!(
1884 !is_undefined_function(&message_only),
1885 "message-only undefined markers should not trigger setter fallback"
1886 );
1887 }
1888}