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