1use crate::builtins::common::spec::{
9 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
10 ReductionNaN, ResidencyPolicy, ShapeRequirements,
11};
12use crate::builtins::structs::type_resolvers::setfield_type;
13use crate::{
14 build_runtime_error, call_builtin_async, gather_if_needed_async, object_property_getter_name,
15 object_property_setter_name, BuiltinResult, RuntimeError,
16};
17use runmat_builtins::{
18 Access, BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
19 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
20 CellArray, CharArray, ComplexTensor, HandleRef, LogicalArray, ObjectInstance, StructValue,
21 Tensor, Value,
22};
23use runmat_gc_api::GcPtr;
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 = unsafe { &*handle.as_raw() }.clone();
520 let updated = assign_into_value(current, steps, rhs).await?;
521 cell.data[position] = allocate_cell_handle(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 = unsafe { &*handle.as_raw() }.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] = allocate_cell_handle(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 let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
993 if prop.is_static {
994 return Err(setfield_static_access(format!(
995 "Property '{}' is static; use classref('{}').{}",
996 name, obj.class_name, name
997 )));
998 }
999 if prop.set_access == Access::Private {
1000 return Err(setfield_private_access(format!(
1001 "Property '{name}' is private"
1002 )));
1003 }
1004 if prop.is_dependent {
1005 let setter = object_property_setter_name(name);
1006 match call_builtin_async(&setter, &[Value::Object(obj.clone()), rhs.clone()]).await {
1007 Ok(value) => {
1008 if let Value::Object(updated) = value {
1009 *obj = updated;
1010 return Ok(());
1011 }
1012 return Err(setfield_flow(format!(
1013 "Dependent property setter for '{}' must return the updated object",
1014 name
1015 )));
1016 }
1017 Err(err) => {
1018 if !is_undefined_function(&err) {
1019 return Err(remap_setfield_flow(err, None));
1020 }
1021 }
1022 }
1023 obj.properties.insert(format!("{name}_backing"), rhs);
1024 return Ok(());
1025 }
1026 }
1027
1028 obj.properties.insert(name.to_string(), rhs);
1029 Ok(())
1030}
1031
1032async fn assign_into_handle(
1033 handle: HandleRef,
1034 steps: &[FieldStep],
1035 rhs: Value,
1036) -> BuiltinResult<Value> {
1037 if steps.is_empty() {
1038 return Err(setfield_flow(
1039 "setfield: expected at least one field name when assigning into a handle",
1040 ));
1041 }
1042 if !runmat_builtins::is_handle_valid(&handle) {
1043 return Err(setfield_flow(format!(
1044 "Invalid or deleted handle object '{}'.",
1045 handle.class_name
1046 )));
1047 }
1048 let current = unsafe { &*handle.target.as_raw() }.clone();
1049 let updated = assign_into_value(current, steps, rhs).await?;
1050 let raw = unsafe { handle.target.as_raw_mut() };
1051 if raw.is_null() {
1052 return Err(setfield_flow("setfield: handle target is null"));
1053 }
1054 unsafe {
1055 *raw = updated;
1056 }
1057 Ok(Value::HandleObject(handle))
1058}
1059
1060fn is_index_selector(value: &Value) -> bool {
1061 matches!(value, Value::Cell(_))
1062}
1063
1064fn parse_index_selector(value: Value) -> BuiltinResult<IndexSelector> {
1065 let Value::Cell(cell) = value else {
1066 return Err(setfield_flow(SETFIELD_ERROR_INDEX_SELECTOR_TYPE.message));
1067 };
1068 let mut components = Vec::with_capacity(cell.data.len());
1069 for handle in &cell.data {
1070 let entry = unsafe { &*handle.as_raw() };
1071 components.push(parse_index_component(entry)?);
1072 }
1073 Ok(IndexSelector { components })
1074}
1075
1076fn parse_index_component(value: &Value) -> BuiltinResult<IndexComponent> {
1077 match value {
1078 Value::CharArray(ca) => {
1079 let text: String = ca.data.iter().collect();
1080 parse_index_text(text.trim())
1081 }
1082 Value::String(s) => parse_index_text(s.trim()),
1083 Value::StringArray(sa) if sa.data.len() == 1 => parse_index_text(sa.data[0].trim()),
1084 _ => {
1085 let idx = parse_positive_scalar(value).map_err(|err| {
1086 setfield_flow(format!(
1087 "setfield: invalid index element ({})",
1088 err.message()
1089 ))
1090 })?;
1091 Ok(IndexComponent::Scalar(idx))
1092 }
1093 }
1094}
1095
1096fn parse_index_text(text: &str) -> BuiltinResult<IndexComponent> {
1097 if text.eq_ignore_ascii_case("end") {
1098 return Ok(IndexComponent::End);
1099 }
1100 if text == ":" {
1101 return Err(setfield_flow(
1102 "setfield: ':' indexing is not currently supported",
1103 ));
1104 }
1105 if text.is_empty() {
1106 return Err(setfield_flow("setfield: index elements must not be empty"));
1107 }
1108 if let Ok(value) = text.parse::<usize>() {
1109 if value == 0 {
1110 return Err(setfield_flow("setfield: index must be >= 1"));
1111 }
1112 return Ok(IndexComponent::Scalar(value));
1113 }
1114 Err(setfield_flow(format!(
1115 "setfield: invalid index element '{}'",
1116 text
1117 )))
1118}
1119
1120fn parse_positive_scalar(value: &Value) -> BuiltinResult<usize> {
1121 let number = match value {
1122 Value::Int(i) => i.to_i64() as f64,
1123 Value::Num(n) => *n,
1124 Value::Tensor(t) if t.data.len() == 1 => t.data[0],
1125 _ => {
1126 let repr = format!("{value:?}");
1127 return Err(setfield_flow(format!(
1128 "expected positive integer index, got {repr}"
1129 )));
1130 }
1131 };
1132
1133 if !number.is_finite() {
1134 return Err(setfield_flow("index must be a finite number"));
1135 }
1136 if number.fract() != 0.0 {
1137 return Err(setfield_flow("index must be an integer"));
1138 }
1139 if number <= 0.0 {
1140 return Err(setfield_flow("index must be >= 1"));
1141 }
1142 if number > usize::MAX as f64 {
1143 return Err(setfield_flow("index exceeds platform limits"));
1144 }
1145 Ok(number as usize)
1146}
1147
1148fn parse_field_name(value: Value) -> BuiltinResult<String> {
1149 match value {
1150 Value::String(s) => Ok(s),
1151 Value::StringArray(sa) => {
1152 if sa.data.len() == 1 {
1153 Ok(sa.data[0].clone())
1154 } else {
1155 Err(setfield_flow(
1156 "setfield: field names must be scalar string arrays or character vectors",
1157 ))
1158 }
1159 }
1160 Value::CharArray(ca) => {
1161 if ca.rows == 1 {
1162 Ok(ca.data.iter().collect())
1163 } else {
1164 Err(setfield_flow(
1165 "setfield: field names must be 1-by-N character vectors",
1166 ))
1167 }
1168 }
1169 other => Err(setfield_flow(format!(
1170 "setfield: expected field name, got {other:?}"
1171 ))),
1172 }
1173}
1174
1175fn resolve_indices(value: &Value, selector: &IndexSelector) -> BuiltinResult<Vec<usize>> {
1176 let dims = selector.components.len();
1177 let mut resolved = Vec::with_capacity(dims);
1178 for (dim_idx, component) in selector.components.iter().enumerate() {
1179 let index = match component {
1180 IndexComponent::Scalar(idx) => *idx,
1181 IndexComponent::End => dimension_length(value, dims, dim_idx)?,
1182 };
1183 resolved.push(index);
1184 }
1185 Ok(resolved)
1186}
1187
1188fn dimension_length(value: &Value, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1189 match value {
1190 Value::Tensor(tensor) => tensor_dimension_length(tensor, dims, dim_idx),
1191 Value::Cell(cell) => cell_dimension_length(cell, dims, dim_idx),
1192 Value::StringArray(array) => string_array_dimension_length(array, dims, dim_idx),
1193 Value::LogicalArray(logical) => logical_array_dimension_length(logical, dims, dim_idx),
1194 Value::CharArray(array) => char_array_dimension_length(array, dims, dim_idx),
1195 Value::ComplexTensor(tensor) => complex_tensor_dimension_length(tensor, dims, dim_idx),
1196 Value::Num(_) | Value::Int(_) | Value::Bool(_) => {
1197 if dims == 1 {
1198 Ok(1)
1199 } else {
1200 Err(setfield_flow(
1201 "setfield: indexing with more than one dimension is not supported for scalars",
1202 ))
1203 }
1204 }
1205 other => Err(setfield_flow(format!(
1206 "Struct contents assignment to a {other:?} object is not supported."
1207 ))),
1208 }
1209}
1210
1211fn tensor_dimension_length(tensor: &Tensor, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1212 if dims == 1 {
1213 let total = tensor.data.len();
1214 if total == 0 {
1215 return Err(setfield_flow(
1216 "Index exceeds the number of array elements (0).",
1217 ));
1218 }
1219 return Ok(total);
1220 }
1221 if dims > 2 {
1222 return Err(setfield_flow(
1223 "setfield: indexing with more than two indices is not supported yet",
1224 ));
1225 }
1226 let len = if dim_idx == 0 {
1227 tensor.rows()
1228 } else {
1229 tensor.cols()
1230 };
1231 if len == 0 {
1232 return Err(setfield_flow(
1233 "Index exceeds the number of array elements (0).",
1234 ));
1235 }
1236 Ok(len)
1237}
1238
1239fn cell_dimension_length(cell: &CellArray, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
1240 if dims == 1 {
1241 let total = cell.data.len();
1242 if total == 0 {
1243 return Err(setfield_flow(
1244 "Index exceeds the number of array elements (0).",
1245 ));
1246 }
1247 return Ok(total);
1248 }
1249 if dims > 2 {
1250 return Err(setfield_flow(
1251 "setfield: indexing with more than two indices is not supported yet",
1252 ));
1253 }
1254 let len = if dim_idx == 0 { cell.rows } else { cell.cols };
1255 if len == 0 {
1256 return Err(setfield_flow(
1257 "Index exceeds the number of array elements (0).",
1258 ));
1259 }
1260 Ok(len)
1261}
1262
1263fn string_array_dimension_length(
1264 array: &runmat_builtins::StringArray,
1265 dims: usize,
1266 dim_idx: usize,
1267) -> BuiltinResult<usize> {
1268 if dims == 1 {
1269 let total = array.data.len();
1270 if total == 0 {
1271 return Err(setfield_flow(
1272 "Index exceeds the number of array elements (0).",
1273 ));
1274 }
1275 return Ok(total);
1276 }
1277 if dims > 2 {
1278 return Err(setfield_flow(
1279 "setfield: indexing with more than two indices is not supported yet",
1280 ));
1281 }
1282 let len = if dim_idx == 0 { array.rows } else { array.cols };
1283 if len == 0 {
1284 return Err(setfield_flow(
1285 "Index exceeds the number of array elements (0).",
1286 ));
1287 }
1288 Ok(len)
1289}
1290
1291fn logical_array_dimension_length(
1292 array: &LogicalArray,
1293 dims: usize,
1294 dim_idx: usize,
1295) -> BuiltinResult<usize> {
1296 if dims == 1 {
1297 let total = array.data.len();
1298 if total == 0 {
1299 return Err(setfield_flow(
1300 "Index exceeds the number of array elements (0).",
1301 ));
1302 }
1303 return Ok(total);
1304 }
1305 if dims > 2 {
1306 return Err(setfield_flow(
1307 "setfield: indexing with more than two indices is not supported yet",
1308 ));
1309 }
1310 if array.shape.len() < dims {
1311 return Err(setfield_flow(
1312 "Index exceeds the number of array elements (0).",
1313 ));
1314 }
1315 let len = array.shape[dim_idx];
1316 if len == 0 {
1317 return Err(setfield_flow(
1318 "Index exceeds the number of array elements (0).",
1319 ));
1320 }
1321 Ok(len)
1322}
1323
1324fn char_array_dimension_length(
1325 array: &CharArray,
1326 dims: usize,
1327 dim_idx: usize,
1328) -> BuiltinResult<usize> {
1329 if dims == 1 {
1330 let total = array.data.len();
1331 if total == 0 {
1332 return Err(setfield_flow(
1333 "Index exceeds the number of array elements (0).",
1334 ));
1335 }
1336 return Ok(total);
1337 }
1338 if dims > 2 {
1339 return Err(setfield_flow(
1340 "setfield: indexing with more than two indices is not supported yet",
1341 ));
1342 }
1343 let len = if dim_idx == 0 { array.rows } else { array.cols };
1344 if len == 0 {
1345 return Err(setfield_flow(
1346 "Index exceeds the number of array elements (0).",
1347 ));
1348 }
1349 Ok(len)
1350}
1351
1352fn complex_tensor_dimension_length(
1353 tensor: &ComplexTensor,
1354 dims: usize,
1355 dim_idx: usize,
1356) -> BuiltinResult<usize> {
1357 if dims == 1 {
1358 let total = tensor.data.len();
1359 if total == 0 {
1360 return Err(setfield_flow(
1361 "Index exceeds the number of array elements (0).",
1362 ));
1363 }
1364 return Ok(total);
1365 }
1366 if dims > 2 {
1367 return Err(setfield_flow(
1368 "setfield: indexing with more than two indices is not supported yet",
1369 ));
1370 }
1371 let len = if dim_idx == 0 {
1372 tensor.rows
1373 } else {
1374 tensor.cols
1375 };
1376 if len == 0 {
1377 return Err(setfield_flow(
1378 "Index exceeds the number of array elements (0).",
1379 ));
1380 }
1381 Ok(len)
1382}
1383
1384fn value_to_scalar(value: Value) -> BuiltinResult<f64> {
1385 match value {
1386 Value::Num(n) => Ok(n),
1387 Value::Int(i) => Ok(i.to_f64()),
1388 Value::Bool(b) => Ok(if b { 1.0 } else { 0.0 }),
1389 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
1390 other => Err(setfield_flow(format!(
1391 "setfield: cannot assign {other:?} into a numeric tensor element"
1392 ))),
1393 }
1394}
1395
1396fn value_to_bool(value: Value) -> BuiltinResult<bool> {
1397 match value {
1398 Value::Bool(b) => Ok(b),
1399 Value::Num(n) => Ok(n != 0.0),
1400 Value::Int(i) => Ok(i.to_i64() != 0),
1401 Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0] != 0.0),
1402 other => Err(setfield_flow(format!(
1403 "setfield: cannot assign {other:?} into a logical array element"
1404 ))),
1405 }
1406}
1407
1408fn allocate_cell_handle(value: Value) -> BuiltinResult<GcPtr<Value>> {
1409 runmat_gc::gc_allocate(value).map_err(|e| {
1410 setfield_flow(format!(
1411 "setfield: failed to allocate cell element in GC: {e}"
1412 ))
1413 })
1414}
1415
1416fn is_struct_array(cell: &CellArray) -> bool {
1417 cell.data
1418 .iter()
1419 .all(|handle| matches!(unsafe { &*handle.as_raw() }, Value::Struct(_)))
1420}
1421
1422#[cfg(test)]
1423pub(crate) mod tests {
1424 use super::*;
1425 use runmat_builtins::{
1426 Access, CellArray, ClassDef, HandleRef, IntValue, ObjectInstance, PropertyDef, StructValue,
1427 };
1428 use runmat_gc::gc_allocate;
1429
1430 fn error_message(err: crate::RuntimeError) -> String {
1431 err.message().to_string()
1432 }
1433
1434 fn run_setfield(base: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
1435 futures::executor::block_on(setfield_builtin(base, rest))
1436 }
1437
1438 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1439 #[test]
1440 fn setfield_creates_scalar_field() {
1441 let struct_value = StructValue::new();
1442 let updated = run_setfield(
1443 Value::Struct(struct_value),
1444 vec![Value::from("answer"), Value::Num(42.0)],
1445 )
1446 .expect("setfield");
1447 match updated {
1448 Value::Struct(st) => {
1449 assert_eq!(
1450 st.fields.get("answer"),
1451 Some(&Value::Num(42.0)),
1452 "field should be inserted"
1453 );
1454 }
1455 other => panic!("expected struct result, got {other:?}"),
1456 }
1457 }
1458
1459 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1460 #[test]
1461 fn setfield_creates_nested_structs() {
1462 let struct_value = StructValue::new();
1463 let updated = run_setfield(
1464 Value::Struct(struct_value),
1465 vec![
1466 Value::from("solver"),
1467 Value::from("name"),
1468 Value::from("cg"),
1469 ],
1470 )
1471 .expect("setfield");
1472 match updated {
1473 Value::Struct(st) => {
1474 let solver = st.fields.get("solver").expect("solver field");
1475 match solver {
1476 Value::Struct(inner) => {
1477 assert_eq!(
1478 inner.fields.get("name"),
1479 Some(&Value::from("cg")),
1480 "inner field should exist"
1481 );
1482 }
1483 other => panic!("expected inner struct, got {other:?}"),
1484 }
1485 }
1486 other => panic!("expected struct result, got {other:?}"),
1487 }
1488 }
1489
1490 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1491 #[test]
1492 fn setfield_updates_struct_array_element() {
1493 let mut a = StructValue::new();
1494 a.fields
1495 .insert("id".to_string(), Value::Int(IntValue::I32(1)));
1496 let mut b = StructValue::new();
1497 b.fields
1498 .insert("id".to_string(), Value::Int(IntValue::I32(2)));
1499 let array = CellArray::new_with_shape(vec![Value::Struct(a), Value::Struct(b)], vec![1, 2])
1500 .unwrap();
1501 let indices =
1502 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
1503 let updated = run_setfield(
1504 Value::Cell(array),
1505 vec![
1506 Value::Cell(indices),
1507 Value::from("id"),
1508 Value::Int(IntValue::I32(42)),
1509 ],
1510 )
1511 .expect("setfield");
1512 match updated {
1513 Value::Cell(cell) => {
1514 let second = unsafe { &*cell.data[1].as_raw() }.clone();
1515 match second {
1516 Value::Struct(st) => {
1517 assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(42))));
1518 }
1519 other => panic!("expected struct element, got {other:?}"),
1520 }
1521 }
1522 other => panic!("expected cell array, got {other:?}"),
1523 }
1524 }
1525
1526 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1527 #[test]
1528 fn setfield_assigns_into_cell_then_struct() {
1529 let mut inner1 = StructValue::new();
1530 inner1.fields.insert("value".to_string(), Value::Num(1.0));
1531 let mut inner2 = StructValue::new();
1532 inner2.fields.insert("value".to_string(), Value::Num(2.0));
1533 let cell = CellArray::new_with_shape(
1534 vec![Value::Struct(inner1), Value::Struct(inner2)],
1535 vec![1, 2],
1536 )
1537 .unwrap();
1538 let mut root = StructValue::new();
1539 root.fields.insert("samples".to_string(), Value::Cell(cell));
1540
1541 let index_cell =
1542 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
1543 let updated = run_setfield(
1544 Value::Struct(root),
1545 vec![
1546 Value::from("samples"),
1547 Value::Cell(index_cell),
1548 Value::from("value"),
1549 Value::Num(10.0),
1550 ],
1551 )
1552 .expect("setfield");
1553
1554 match updated {
1555 Value::Struct(st) => {
1556 let samples = st.fields.get("samples").expect("samples field");
1557 match samples {
1558 Value::Cell(cell) => {
1559 let value = unsafe { &*cell.data[1].as_raw() }.clone();
1560 match value {
1561 Value::Struct(inner) => {
1562 assert_eq!(inner.fields.get("value"), Some(&Value::Num(10.0)));
1563 }
1564 other => panic!("expected struct, got {other:?}"),
1565 }
1566 }
1567 other => panic!("expected cell array, got {other:?}"),
1568 }
1569 }
1570 other => panic!("expected struct, got {other:?}"),
1571 }
1572 }
1573
1574 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1575 #[test]
1576 fn setfield_struct_array_with_end_index() {
1577 let mut first = StructValue::new();
1578 first
1579 .fields
1580 .insert("id".to_string(), Value::Int(IntValue::I32(1)));
1581 let mut second = StructValue::new();
1582 second
1583 .fields
1584 .insert("id".to_string(), Value::Int(IntValue::I32(2)));
1585 let array = CellArray::new_with_shape(
1586 vec![Value::Struct(first), Value::Struct(second)],
1587 vec![1, 2],
1588 )
1589 .unwrap();
1590 let index_cell = CellArray::new_with_shape(vec![Value::from("end")], vec![1, 1]).unwrap();
1591 let updated = run_setfield(
1592 Value::Cell(array),
1593 vec![
1594 Value::Cell(index_cell),
1595 Value::from("id"),
1596 Value::Int(IntValue::I32(99)),
1597 ],
1598 )
1599 .expect("setfield");
1600 match updated {
1601 Value::Cell(cell) => {
1602 let second = unsafe { &*cell.data[1].as_raw() }.clone();
1603 match second {
1604 Value::Struct(st) => {
1605 assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(99))));
1606 }
1607 other => panic!("expected struct element, got {other:?}"),
1608 }
1609 }
1610 other => panic!("expected cell array result, got {other:?}"),
1611 }
1612 }
1613
1614 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1615 #[test]
1616 fn setfield_assigns_object_property() {
1617 let mut class_def = ClassDef {
1618 name: "Simple".to_string(),
1619 parent: None,
1620 properties: Default::default(),
1621 methods: Default::default(),
1622 };
1623 class_def.properties.insert(
1624 "x".to_string(),
1625 PropertyDef {
1626 name: "x".to_string(),
1627 is_static: false,
1628 is_constant: false,
1629 is_dependent: false,
1630 get_access: Access::Public,
1631 set_access: Access::Public,
1632 default_value: None,
1633 },
1634 );
1635 runmat_builtins::register_class(class_def);
1636
1637 let mut obj = ObjectInstance::new("Simple".to_string());
1638 obj.properties.insert("x".to_string(), Value::Num(0.0));
1639
1640 let updated = run_setfield(Value::Object(obj), vec![Value::from("x"), Value::Num(5.0)])
1641 .expect("setfield");
1642
1643 match updated {
1644 Value::Object(o) => {
1645 assert_eq!(o.properties.get("x"), Some(&Value::Num(5.0)));
1646 }
1647 other => panic!("expected object result, got {other:?}"),
1648 }
1649 }
1650
1651 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1652 #[test]
1653 fn setfield_errors_when_indexing_missing_field() {
1654 let struct_value = StructValue::new();
1655 let index_cell =
1656 CellArray::new_with_shape(vec![Value::Int(IntValue::I32(1))], vec![1, 1]).unwrap();
1657 let err = error_message(
1658 run_setfield(
1659 Value::Struct(struct_value),
1660 vec![
1661 Value::from("missing"),
1662 Value::Cell(index_cell),
1663 Value::Num(1.0),
1664 ],
1665 )
1666 .expect_err("setfield should fail when field is missing"),
1667 );
1668 assert!(
1669 err.contains("Reference to non-existent field 'missing'."),
1670 "unexpected error message: {err}"
1671 );
1672 }
1673
1674 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1675 #[test]
1676 fn setfield_errors_on_static_property_assignment() {
1677 let mut class_def = ClassDef {
1678 name: "StaticSetfield".to_string(),
1679 parent: None,
1680 properties: Default::default(),
1681 methods: Default::default(),
1682 };
1683 class_def.properties.insert(
1684 "version".to_string(),
1685 PropertyDef {
1686 name: "version".to_string(),
1687 is_static: true,
1688 is_constant: false,
1689 is_dependent: false,
1690 get_access: Access::Public,
1691 set_access: Access::Public,
1692 default_value: None,
1693 },
1694 );
1695 runmat_builtins::register_class(class_def);
1696
1697 let obj = ObjectInstance::new("StaticSetfield".to_string());
1698 let err = error_message(
1699 run_setfield(
1700 Value::Object(obj),
1701 vec![Value::from("version"), Value::Num(2.0)],
1702 )
1703 .expect_err("setfield should reject static property writes"),
1704 );
1705 assert!(
1706 err.contains("Property 'version' is static"),
1707 "unexpected error message: {err}"
1708 );
1709 }
1710
1711 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1712 #[test]
1713 fn setfield_rejects_inherited_static_property_assignment() {
1714 let parent_name = "runmat.unittest.StaticSetfieldParent";
1715 let child_name = "runmat.unittest.StaticSetfieldChild";
1716
1717 let mut parent = ClassDef {
1718 name: parent_name.to_string(),
1719 parent: None,
1720 properties: Default::default(),
1721 methods: Default::default(),
1722 };
1723 parent.properties.insert(
1724 "version".to_string(),
1725 PropertyDef {
1726 name: "version".to_string(),
1727 is_static: true,
1728 is_constant: false,
1729 is_dependent: false,
1730 get_access: Access::Public,
1731 set_access: Access::Public,
1732 default_value: None,
1733 },
1734 );
1735 runmat_builtins::register_class(parent);
1736 runmat_builtins::register_class(ClassDef {
1737 name: child_name.to_string(),
1738 parent: Some(parent_name.to_string()),
1739 properties: Default::default(),
1740 methods: Default::default(),
1741 });
1742
1743 let obj = ObjectInstance::new(child_name.to_string());
1744 let err = error_message(
1745 run_setfield(
1746 Value::Object(obj),
1747 vec![Value::from("version"), Value::Num(2.0)],
1748 )
1749 .expect_err("setfield should reject inherited static property writes"),
1750 );
1751 assert!(
1752 err.contains("Property 'version' is static"),
1753 "unexpected error message: {err}"
1754 );
1755 }
1756
1757 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1758 #[test]
1759 fn setfield_updates_handle_target() {
1760 let mut inner = StructValue::new();
1761 inner.fields.insert("x".to_string(), Value::Num(0.0));
1762 let gc_ptr = gc_allocate(Value::Struct(inner)).expect("gc allocation");
1763 let handle_ptr = gc_ptr.clone();
1764 let handle = HandleRef {
1765 class_name: "PointHandle".to_string(),
1766 target: handle_ptr,
1767 valid: true,
1768 };
1769
1770 let updated = run_setfield(
1771 Value::HandleObject(handle.clone()),
1772 vec![Value::from("x"), Value::Num(7.0)],
1773 )
1774 .expect("setfield handle update");
1775
1776 match updated {
1777 Value::HandleObject(h) => assert!(runmat_builtins::is_handle_valid(&h)),
1778 other => panic!("expected handle, got {other:?}"),
1779 }
1780
1781 let pointee = unsafe { &*gc_ptr.as_raw() };
1782 match pointee {
1783 Value::Struct(st) => {
1784 assert_eq!(st.fields.get("x"), Some(&Value::Num(7.0)));
1785 }
1786 other => panic!("expected struct pointee, got {other:?}"),
1787 }
1788 }
1789
1790 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1791 #[test]
1792 #[cfg(feature = "wgpu")]
1793 fn setfield_gpu_tensor_indexing_gathers_to_host() {
1794 use runmat_accelerate::backend::wgpu::provider::{
1795 register_wgpu_provider, WgpuProviderOptions,
1796 };
1797 use runmat_accelerate_api::HostTensorView;
1798
1799 if runmat_accelerate_api::provider().is_none()
1800 && register_wgpu_provider(WgpuProviderOptions::default()).is_err()
1801 {
1802 runmat_accelerate::simple_provider::register_inprocess_provider();
1803 }
1804
1805 let provider = runmat_accelerate_api::provider().expect("accel provider");
1806 let data = [1.0, 2.0, 3.0, 4.0];
1807 let shape = [2usize, 2usize];
1808 let view = HostTensorView {
1809 data: &data,
1810 shape: &shape,
1811 };
1812 let handle = provider.upload(&view).expect("upload");
1813
1814 let mut root = StructValue::new();
1815 root.fields
1816 .insert("values".to_string(), Value::GpuTensor(handle));
1817
1818 let index_cell = CellArray::new_with_shape(
1819 vec![Value::Int(IntValue::I32(2)), Value::Int(IntValue::I32(2))],
1820 vec![1, 2],
1821 )
1822 .unwrap();
1823
1824 let updated = run_setfield(
1825 Value::Struct(root),
1826 vec![
1827 Value::from("values"),
1828 Value::Cell(index_cell),
1829 Value::Num(99.0),
1830 ],
1831 )
1832 .expect("setfield gpu value");
1833
1834 match updated {
1835 Value::Struct(st) => {
1836 let values = st.fields.get("values").expect("values field");
1837 match values {
1838 Value::Tensor(tensor) => {
1839 assert_eq!(tensor.shape, vec![2, 2]);
1840 assert_eq!(tensor.data[3], 99.0);
1841 }
1842 other => panic!("expected tensor after gather, got {other:?}"),
1843 }
1844 }
1845 other => panic!("expected struct result, got {other:?}"),
1846 }
1847 }
1848
1849 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1850 #[test]
1851 fn setfield_undefined_detection_requires_identifier() {
1852 let with_identifier = build_runtime_error("missing")
1853 .with_identifier(crate::IDENT_UNDEFINED_FUNCTION)
1854 .build();
1855 assert!(is_undefined_function(&with_identifier));
1856
1857 let message_only =
1858 build_runtime_error(format!("{} message only", crate::IDENT_UNDEFINED_FUNCTION))
1859 .build();
1860 assert!(
1861 !is_undefined_function(&message_only),
1862 "message-only undefined markers should not trigger setter fallback"
1863 );
1864 }
1865}