ruchy/runtime/
object_helpers.rs

1//! Helper functions for working with Object and `ObjectMut` values
2//! All functions maintain ≤10 complexity budget following Toyota Way
3
4use super::{InterpreterError, Value};
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8/// Check if value is a mutable object
9/// Complexity: 1
10#[inline]
11pub fn is_mutable_object(value: &Value) -> bool {
12    matches!(value, Value::ObjectMut(_))
13}
14
15/// Check if value is any kind of object (mutable or immutable)
16/// Complexity: 2
17#[inline]
18pub fn is_object(value: &Value) -> bool {
19    matches!(value, Value::Object(_) | Value::ObjectMut(_))
20}
21
22/// Get field from object (handles both Object and `ObjectMut`)
23///
24/// # Complexity
25/// Cyclomatic complexity: 5
26///
27/// # Examples
28///
29/// ```
30/// use ruchy::runtime::object_helpers;
31/// use ruchy::runtime::interpreter::Value;
32/// use std::collections::HashMap;
33///
34/// let mut map = HashMap::new();
35/// map.insert("key".to_string(), Value::Integer(42));
36/// let obj = object_helpers::new_mutable_object(map);
37///
38/// assert_eq!(
39///     object_helpers::get_object_field(&obj, "key"),
40///     Some(Value::Integer(42))
41/// );
42/// ```
43pub fn get_object_field(value: &Value, field: &str) -> Option<Value> {
44    match value {
45        Value::Object(map) => map.get(field).cloned(),
46        Value::ObjectMut(cell) => cell.lock().unwrap().get(field).cloned(),
47        _ => None,
48    }
49}
50
51/// Set field in mutable object (returns error for immutable)
52///
53/// # Complexity
54/// Cyclomatic complexity: 7
55///
56/// # Examples
57///
58/// ```
59/// use ruchy::runtime::object_helpers;
60/// use ruchy::runtime::interpreter::Value;
61/// use std::collections::HashMap;
62///
63/// let mut map = HashMap::new();
64/// map.insert("key".to_string(), Value::Integer(42));
65/// let obj = object_helpers::new_mutable_object(map);
66///
67/// // Mutate the object
68/// assert!(object_helpers::set_object_field(&obj, "key", Value::Integer(99)).is_ok());
69/// assert_eq!(
70///     object_helpers::get_object_field(&obj, "key"),
71///     Some(Value::Integer(99))
72/// );
73/// ```
74pub fn set_object_field(
75    value: &Value,
76    field: &str,
77    new_value: Value,
78) -> Result<(), InterpreterError> {
79    match value {
80        Value::Object(_) => Err(InterpreterError::RuntimeError(format!(
81            "Cannot mutate immutable object field '{field}'"
82        ))),
83        Value::ObjectMut(cell) => {
84            cell.lock().unwrap().insert(field.to_string(), new_value);
85            Ok(())
86        }
87        _ => Err(InterpreterError::RuntimeError(format!(
88            "Cannot access field '{field}' on non-object"
89        ))),
90    }
91}
92
93/// Create new mutable object from `HashMap`
94///
95/// # Complexity
96/// Cyclomatic complexity: 2
97///
98/// # Examples
99///
100/// ```
101/// use ruchy::runtime::object_helpers;
102/// use ruchy::runtime::interpreter::Value;
103/// use std::collections::HashMap;
104///
105/// let mut map = HashMap::new();
106/// map.insert("x".to_string(), Value::Integer(10));
107/// let obj = object_helpers::new_mutable_object(map);
108///
109/// assert!(object_helpers::is_mutable_object(&obj));
110/// ```
111#[inline]
112pub fn new_mutable_object(map: HashMap<String, Value>) -> Value {
113    Value::ObjectMut(Arc::new(Mutex::new(map)))
114}
115
116/// Create new immutable object from `HashMap`
117///
118/// # Complexity
119/// Cyclomatic complexity: 2
120///
121/// # Examples
122///
123/// ```
124/// use ruchy::runtime::object_helpers;
125/// use ruchy::runtime::interpreter::Value;
126/// use std::collections::HashMap;
127///
128/// let mut map = HashMap::new();
129/// map.insert("x".to_string(), Value::Integer(10));
130/// let obj = object_helpers::new_immutable_object(map);
131///
132/// assert!(!object_helpers::is_mutable_object(&obj));
133/// ```
134#[inline]
135pub fn new_immutable_object(map: HashMap<String, Value>) -> Value {
136    Value::Object(Arc::new(map))
137}
138
139/// Convert immutable Object to mutable `ObjectMut` (copies data)
140///
141/// # Complexity
142/// Cyclomatic complexity: 4
143///
144/// # Examples
145///
146/// ```
147/// use ruchy::runtime::object_helpers;
148/// use ruchy::runtime::interpreter::Value;
149/// use std::collections::HashMap;
150///
151/// let mut map = HashMap::new();
152/// map.insert("x".to_string(), Value::Integer(10));
153/// let immutable = object_helpers::new_immutable_object(map);
154///
155/// let mutable = object_helpers::to_mutable(&immutable);
156/// assert!(object_helpers::is_mutable_object(&mutable));
157/// ```
158pub fn to_mutable(value: &Value) -> Value {
159    match value {
160        Value::Object(map) => Value::ObjectMut(Arc::new(Mutex::new((**map).clone()))),
161        Value::ObjectMut(_) => value.clone(),
162        _ => value.clone(),
163    }
164}
165
166/// Convert mutable `ObjectMut` to immutable Object (copies data)
167///
168/// # Complexity
169/// Cyclomatic complexity: 4
170///
171/// # Examples
172///
173/// ```
174/// use ruchy::runtime::object_helpers;
175/// use ruchy::runtime::interpreter::Value;
176/// use std::collections::HashMap;
177///
178/// let mut map = HashMap::new();
179/// map.insert("x".to_string(), Value::Integer(10));
180/// let mutable = object_helpers::new_mutable_object(map);
181///
182/// let immutable = object_helpers::to_immutable(&mutable);
183/// assert!(!object_helpers::is_mutable_object(&immutable));
184/// ```
185pub fn to_immutable(value: &Value) -> Value {
186    match value {
187        Value::ObjectMut(cell) => Value::Object(Arc::new(cell.lock().unwrap().clone())),
188        Value::Object(_) => value.clone(),
189        _ => value.clone(),
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_is_mutable_object() {
199        let mut map = HashMap::new();
200        map.insert("key".to_string(), Value::Integer(42));
201
202        let immutable = new_immutable_object(map.clone());
203        let mutable = new_mutable_object(map);
204
205        assert!(!is_mutable_object(&immutable));
206        assert!(is_mutable_object(&mutable));
207    }
208
209    #[test]
210    fn test_is_object() {
211        let mut map = HashMap::new();
212        map.insert("key".to_string(), Value::Integer(42));
213
214        let immutable = new_immutable_object(map.clone());
215        let mutable = new_mutable_object(map);
216
217        assert!(is_object(&immutable));
218        assert!(is_object(&mutable));
219        assert!(!is_object(&Value::Integer(42)));
220    }
221
222    #[test]
223    fn test_get_object_field() {
224        let mut map = HashMap::new();
225        map.insert("key".to_string(), Value::Integer(42));
226
227        let immutable = new_immutable_object(map.clone());
228        let mutable = new_mutable_object(map);
229
230        assert_eq!(
231            get_object_field(&immutable, "key"),
232            Some(Value::Integer(42))
233        );
234        assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(42)));
235        assert_eq!(get_object_field(&immutable, "missing"), None);
236    }
237
238    #[test]
239    fn test_set_object_field() {
240        let mut map = HashMap::new();
241        map.insert("key".to_string(), Value::Integer(42));
242
243        let immutable = new_immutable_object(map.clone());
244        let mutable = new_mutable_object(map);
245
246        // Immutable should fail
247        assert!(set_object_field(&immutable, "key", Value::Integer(99)).is_err());
248
249        // Mutable should succeed
250        assert!(set_object_field(&mutable, "key", Value::Integer(99)).is_ok());
251        assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(99)));
252    }
253
254    #[test]
255    fn test_set_new_field() {
256        let map = HashMap::new();
257        let mutable = new_mutable_object(map);
258
259        // Add new field
260        assert!(set_object_field(&mutable, "new_key", Value::Integer(123)).is_ok());
261        assert_eq!(
262            get_object_field(&mutable, "new_key"),
263            Some(Value::Integer(123))
264        );
265    }
266
267    #[test]
268    fn test_to_mutable() {
269        let mut map = HashMap::new();
270        map.insert("key".to_string(), Value::Integer(42));
271
272        let immutable = new_immutable_object(map);
273        let mutable = to_mutable(&immutable);
274
275        assert!(is_mutable_object(&mutable));
276        assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(42)));
277
278        // Should be able to mutate it now
279        assert!(set_object_field(&mutable, "key", Value::Integer(99)).is_ok());
280        assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(99)));
281    }
282
283    #[test]
284    fn test_to_immutable() {
285        let mut map = HashMap::new();
286        map.insert("key".to_string(), Value::Integer(42));
287
288        let mutable = new_mutable_object(map);
289        let immutable = to_immutable(&mutable);
290
291        assert!(!is_mutable_object(&immutable));
292        assert_eq!(
293            get_object_field(&immutable, "key"),
294            Some(Value::Integer(42))
295        );
296    }
297
298    #[test]
299    fn test_to_mutable_idempotent() {
300        let mut map = HashMap::new();
301        map.insert("key".to_string(), Value::Integer(42));
302
303        let mutable1 = new_mutable_object(map);
304        let mutable2 = to_mutable(&mutable1);
305
306        // Converting already mutable object should return clone
307        assert!(is_mutable_object(&mutable2));
308    }
309
310    #[test]
311    fn test_non_object_type() {
312        let int_val = Value::Integer(42);
313
314        // Getting field from non-object returns None
315        assert_eq!(get_object_field(&int_val, "key"), None);
316
317        // Setting field on non-object returns error
318        assert!(set_object_field(&int_val, "key", Value::Integer(99)).is_err());
319    }
320}
321
322#[test]
323fn test_to_immutable_object_match_arm() {
324    // MISSED: delete match arm Value::Object(_) in to_immutable (line 189)
325    use std::sync::Arc;
326
327    let mut map = HashMap::new();
328    map.insert("test".to_string(), Value::Integer(42));
329
330    let immutable_obj = Value::Object(Arc::new(map));
331    let result = to_immutable(&immutable_obj);
332
333    // Should return clone of immutable object (match arm test)
334    if let Value::Object(obj) = result {
335        assert_eq!(obj.get("test"), Some(&Value::Integer(42)));
336    } else {
337        panic!("Should return Object variant");
338    }
339}