Skip to main content

eure_document/document/
node.rs

1use crate::{prelude_internal::*, value::ValueKind};
2
3#[derive(Debug, Clone)]
4/// A node in the Eure document.
5///
6/// This does not implement PartialEq since content may refer to other nodes, and so equality is not well-defined.
7pub struct Node {
8    pub content: NodeValue,
9    pub extensions: Map<Identifier, NodeId>,
10}
11
12pub struct NodeMut<'d> {
13    document: &'d mut EureDocument,
14    pub node_id: NodeId,
15}
16
17impl<'d> core::fmt::Debug for NodeMut<'d> {
18    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19        match self.document.get_node(self.node_id) {
20            Some(node) => f
21                .debug_tuple("NodeMut")
22                .field(&self.node_id)
23                .field(node)
24                .finish(),
25            None => f
26                .debug_tuple("NodeMut")
27                .field(&self.node_id)
28                .field(&"<invalid>")
29                .finish(),
30        }
31    }
32}
33
34impl<'d> NodeMut<'d> {
35    pub fn new(document: &'d mut EureDocument, node_id: NodeId) -> Self {
36        Self { document, node_id }
37    }
38
39    pub fn add_map_child(self, object_key: ObjectKey) -> Result<NodeMut<'d>, InsertErrorKind> {
40        self.document.add_map_child(object_key, self.node_id)
41    }
42
43    pub fn add_extension(self, identifier: Identifier) -> Result<NodeMut<'d>, InsertErrorKind> {
44        self.document.add_extension(identifier, self.node_id)
45    }
46
47    pub fn add_tuple_element(self, index: u8) -> Result<NodeMut<'d>, InsertErrorKind> {
48        self.document.add_tuple_element(index, self.node_id)
49    }
50
51    pub fn add_array_element(self, index: Option<usize>) -> Result<NodeMut<'d>, InsertErrorKind> {
52        self.document.add_array_element(index, self.node_id)
53    }
54
55    pub fn add_child_by_segment(
56        self,
57        segment: PathSegment,
58    ) -> Result<NodeMut<'d>, InsertErrorKind> {
59        self.document.add_child_by_segment(segment, self.node_id)
60    }
61
62    pub fn get_extension(self, ident: &Identifier) -> Option<NodeMut<'d>> {
63        let node_id = self.document.node(self.node_id).extensions.get(ident)?;
64        Some(NodeMut::new(self.document, *node_id))
65    }
66
67    // Content access methods
68
69    pub fn as_map(self) -> Option<&'d NodeMap> {
70        self.document.node(self.node_id).as_map()
71    }
72
73    pub fn as_array(self) -> Option<&'d NodeArray> {
74        self.document.node(self.node_id).as_array()
75    }
76
77    pub fn as_tuple(self) -> Option<&'d NodeTuple> {
78        self.document.node(self.node_id).as_tuple()
79    }
80
81    pub fn require_map(self) -> Result<&'d mut NodeMap, InsertErrorKind> {
82        self.document.node_mut(self.node_id).require_map()
83    }
84
85    pub fn require_tuple(self) -> Result<&'d mut NodeTuple, InsertErrorKind> {
86        self.document.node_mut(self.node_id).require_tuple()
87    }
88
89    pub fn require_array(self) -> Result<&'d mut NodeArray, InsertErrorKind> {
90        self.document.node_mut(self.node_id).require_array()
91    }
92}
93
94impl Node {
95    pub fn as_map(&self) -> Option<&NodeMap> {
96        match &self.content {
97            NodeValue::Map(map) => Some(map),
98            _ => None,
99        }
100    }
101
102    pub fn as_array(&self) -> Option<&NodeArray> {
103        match &self.content {
104            NodeValue::Array(array) => Some(array),
105            _ => None,
106        }
107    }
108
109    pub fn as_tuple(&self) -> Option<&NodeTuple> {
110        match &self.content {
111            NodeValue::Tuple(tuple) => Some(tuple),
112            _ => None,
113        }
114    }
115
116    pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
117        match &self.content {
118            NodeValue::Primitive(primitive) => Some(primitive),
119            _ => None,
120        }
121    }
122
123    pub fn get_extension(&self, ident: &Identifier) -> Option<NodeId> {
124        self.extensions.get(ident).copied()
125    }
126
127    pub(crate) fn require_map(&mut self) -> Result<&mut NodeMap, InsertErrorKind> {
128        if self.content.is_hole() {
129            self.content = NodeValue::Map(Default::default());
130            let NodeValue::Map(map) = &mut self.content else {
131                unreachable!();
132            };
133            Ok(map)
134        } else if let NodeValue::Map(map) = &mut self.content {
135            Ok(map)
136        } else {
137            Err(InsertErrorKind::ExpectedMap)
138        }
139    }
140
141    pub(crate) fn require_tuple(&mut self) -> Result<&mut NodeTuple, InsertErrorKind> {
142        if self.content.is_hole() {
143            self.content = NodeValue::Tuple(Default::default());
144            let NodeValue::Tuple(tuple) = &mut self.content else {
145                unreachable!();
146            };
147            Ok(tuple)
148        } else if let NodeValue::Tuple(tuple) = &mut self.content {
149            Ok(tuple)
150        } else {
151            Err(InsertErrorKind::ExpectedTuple)
152        }
153    }
154
155    pub(crate) fn require_array(&mut self) -> Result<&mut NodeArray, InsertErrorKind> {
156        if self.content.is_hole() {
157            self.content = NodeValue::Array(Default::default());
158            let NodeValue::Array(array) = &mut self.content else {
159                unreachable!();
160            };
161            Ok(array)
162        } else if let NodeValue::Array(array) = &mut self.content {
163            Ok(array)
164        } else {
165            Err(InsertErrorKind::ExpectedArray)
166        }
167    }
168}
169
170#[derive(Debug, PartialEq, Clone)]
171pub enum NodeValue {
172    /// A hole represents an uninitialized or placeholder value.
173    /// Optionally includes a label for identification (e.g., `!todo`, `!wip`).
174    Hole(Option<Identifier>),
175    Primitive(PrimitiveValue),
176    Array(NodeArray),
177    Map(NodeMap),
178    Tuple(NodeTuple),
179}
180
181impl NodeValue {
182    /// Creates an anonymous hole (no label).
183    pub fn hole() -> Self {
184        Self::Hole(None)
185    }
186
187    /// Creates a labeled hole.
188    pub fn labeled_hole(label: Identifier) -> Self {
189        Self::Hole(Some(label))
190    }
191
192    /// Returns true if this is a hole (labeled or anonymous).
193    pub fn is_hole(&self) -> bool {
194        matches!(self, Self::Hole(_))
195    }
196
197    pub fn empty_map() -> Self {
198        Self::Map(NodeMap::new())
199    }
200
201    pub fn empty_array() -> Self {
202        Self::Array(NodeArray::new())
203    }
204
205    pub fn empty_tuple() -> Self {
206        Self::Tuple(NodeTuple::new())
207    }
208
209    pub fn value_kind(&self) -> ValueKind {
210        match self {
211            Self::Hole(_) => ValueKind::Hole,
212            Self::Primitive(primitive) => primitive.kind(),
213            Self::Array(_) => ValueKind::Array,
214            Self::Map(_) => ValueKind::Map,
215            Self::Tuple(_) => ValueKind::Tuple,
216        }
217    }
218}
219
220// ============================================================================
221// From implementations for NodeValue
222// ============================================================================
223
224impl From<PrimitiveValue> for NodeValue {
225    fn from(p: PrimitiveValue) -> Self {
226        NodeValue::Primitive(p)
227    }
228}
229
230#[derive(Debug, Default, Clone, PartialEq, Eq, Plural)]
231#[plural(len, is_empty, iter, into_iter, new)]
232pub struct NodeArray(Vec<NodeId>);
233
234#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Plural)]
235#[plural(len, is_empty, iter, into_iter, new)]
236pub struct NodeTuple(Vec<NodeId>);
237
238pub type NodeMap = Map<ObjectKey, NodeId>;
239
240impl NodeTuple {
241    pub fn get(&self, index: usize) -> Option<NodeId> {
242        self.0.get(index).copied()
243    }
244
245    pub fn push(&mut self, node_id: NodeId) -> Result<(), InsertErrorKind> {
246        self.0.push(node_id);
247        Ok(())
248    }
249
250    pub fn add_at(&mut self, index: u8, node_id: NodeId) -> Result<(), InsertErrorKind> {
251        if index as usize != self.0.len() {
252            return Err(InsertErrorKind::TupleIndexInvalid {
253                index,
254                expected_index: self.0.len(),
255            });
256        }
257        self.0.insert(index as usize, node_id);
258        Ok(())
259    }
260
261    pub fn to_vec(&self) -> Vec<NodeId> {
262        self.0.clone()
263    }
264
265    pub fn from_vec(vec: Vec<NodeId>) -> Self {
266        Self(vec)
267    }
268}
269
270impl NodeArray {
271    pub fn get(&self, index: usize) -> Option<NodeId> {
272        self.0.get(index).copied()
273    }
274
275    pub fn push(&mut self, node_id: NodeId) -> Result<(), InsertErrorKind> {
276        self.0.push(node_id);
277        Ok(())
278    }
279
280    pub fn add_at(&mut self, index: usize, node_id: NodeId) -> Result<(), InsertErrorKind> {
281        if index != self.0.len() {
282            return Err(InsertErrorKind::ArrayIndexInvalid {
283                index,
284                expected_index: self.0.len(),
285            });
286        }
287        self.0.insert(index, node_id);
288        Ok(())
289    }
290
291    pub fn to_vec(&self) -> Vec<NodeId> {
292        self.0.clone()
293    }
294
295    pub fn from_vec(vec: Vec<NodeId>) -> Self {
296        Self(vec)
297    }
298
299    /// Try to convert to a fixed-size array.
300    /// Returns `None` if the length doesn't match.
301    pub fn try_into_array<const N: usize>(&self) -> Option<[NodeId; N]> {
302        self.0.as_slice().try_into().ok()
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    fn identifier(s: &str) -> Identifier {
311        s.parse().unwrap()
312    }
313
314    #[test]
315    fn test_require_map_on_uninitialized() {
316        let mut node = Node {
317            content: NodeValue::hole(),
318            extensions: Map::new(),
319        };
320
321        let map = node.require_map().expect("Should convert to map");
322        assert_eq!(map.len(), 0);
323
324        // Verify content was changed
325        assert!(node.as_map().is_some());
326    }
327
328    #[test]
329    fn test_require_map_on_existing_map() {
330        let mut node = Node {
331            content: NodeValue::Map(Default::default()),
332            extensions: Map::new(),
333        };
334
335        let map = node.require_map().expect("Should return existing map");
336        assert_eq!(map.len(), 0);
337    }
338
339    #[test]
340    fn test_require_map_on_wrong_type() {
341        let mut node = Node {
342            content: NodeValue::Primitive(PrimitiveValue::Null),
343            extensions: Map::new(),
344        };
345
346        let result = node.require_map();
347        assert_eq!(result, Err(InsertErrorKind::ExpectedMap));
348    }
349
350    #[test]
351    fn test_require_tuple_on_uninitialized() {
352        let mut node = Node {
353            content: NodeValue::hole(),
354            extensions: Map::new(),
355        };
356
357        let tuple = node.require_tuple().expect("Should convert to tuple");
358        assert_eq!(tuple.len(), 0);
359
360        // Verify content was changed
361        assert!(node.as_tuple().is_some());
362    }
363
364    #[test]
365    fn test_require_tuple_on_existing_tuple() {
366        let mut node = Node {
367            content: NodeValue::Tuple(Default::default()),
368            extensions: Map::new(),
369        };
370
371        let tuple = node.require_tuple().expect("Should return existing tuple");
372        assert_eq!(tuple.len(), 0);
373    }
374
375    #[test]
376    fn test_require_tuple_on_wrong_type() {
377        let mut node = Node {
378            content: NodeValue::Primitive(PrimitiveValue::Null),
379            extensions: Map::new(),
380        };
381
382        let result = node.require_tuple();
383        assert_eq!(result, Err(InsertErrorKind::ExpectedTuple));
384    }
385
386    #[test]
387    fn test_require_array_on_uninitialized() {
388        let mut node = Node {
389            content: NodeValue::hole(),
390            extensions: Map::new(),
391        };
392
393        let array = node.require_array().expect("Should convert to array");
394        assert_eq!(array.len(), 0);
395
396        // Verify content was changed
397        assert!(node.as_array().is_some());
398    }
399
400    #[test]
401    fn test_require_array_on_existing_array() {
402        let mut node = Node {
403            content: NodeValue::Array(Default::default()),
404            extensions: Map::new(),
405        };
406
407        let array = node.require_array().expect("Should return existing array");
408        assert_eq!(array.len(), 0);
409    }
410
411    #[test]
412    fn test_require_array_on_wrong_type() {
413        let mut node = Node {
414            content: NodeValue::Primitive(PrimitiveValue::Null),
415            extensions: Map::new(),
416        };
417
418        let result = node.require_array();
419        assert_eq!(result, Err(InsertErrorKind::ExpectedArray));
420    }
421
422    #[test]
423    fn test_node_get_extension_exists() {
424        let mut doc = EureDocument::new();
425        let root_id = doc.get_root_id();
426        let ext_identifier = identifier("test_ext");
427
428        // Add an extension
429        let ext_node_id = doc
430            .add_extension(ext_identifier.clone(), root_id)
431            .expect("Failed to add extension")
432            .node_id;
433
434        // Test get_extension on the node
435        let root_node = doc.node(root_id);
436        let result = root_node.get_extension(&ext_identifier);
437
438        assert!(result.is_some());
439        assert_eq!(result.unwrap(), ext_node_id);
440    }
441
442    #[test]
443    fn test_node_get_extension_missing() {
444        let doc = EureDocument::new();
445        let root_id = doc.get_root_id();
446        let ext_identifier = identifier("nonexistent");
447
448        // Test get_extension for a missing extension
449        let root_node = doc.node(root_id);
450        let result = root_node.get_extension(&ext_identifier);
451
452        assert!(result.is_none());
453    }
454
455    #[test]
456    fn test_node_mut_get_extension_exists() {
457        let mut doc = EureDocument::new();
458        let root_id = doc.get_root_id();
459        let ext_identifier = identifier("test_ext");
460
461        // Add an extension
462        let ext_node_id = doc
463            .add_extension(ext_identifier.clone(), root_id)
464            .expect("Failed to add extension")
465            .node_id;
466
467        // Test NodeMut::get_extension
468        let node_mut = NodeMut::new(&mut doc, root_id);
469        let result = node_mut.get_extension(&ext_identifier);
470
471        assert!(result.is_some());
472        let ext_node_mut = result.unwrap();
473        assert_eq!(ext_node_mut.node_id, ext_node_id);
474    }
475
476    #[test]
477    fn test_node_mut_get_extension_missing() {
478        let mut doc = EureDocument::new();
479        let root_id = doc.get_root_id();
480        let ext_identifier = identifier("nonexistent");
481
482        // Test NodeMut::get_extension for a missing extension
483        let node_mut = NodeMut::new(&mut doc, root_id);
484        let result = node_mut.get_extension(&ext_identifier);
485
486        assert!(result.is_none());
487    }
488
489    #[test]
490    fn test_node_mut_debug_valid_node() {
491        let mut doc = EureDocument::new();
492        let root_id = doc.get_root_id();
493
494        // Create a NodeMut with valid node_id
495        let node_mut = NodeMut::new(&mut doc, root_id);
496        let debug_output = alloc::format!("{:?}", node_mut);
497
498        // Should contain NodeMut, node_id, and delegate to Node's Debug
499        assert!(debug_output.contains("NodeMut"));
500        assert!(debug_output.contains("NodeId"));
501        assert!(debug_output.contains("Node"));
502        assert!(debug_output.contains("Hole"));
503    }
504
505    #[test]
506    fn test_node_mut_debug_invalid_node() {
507        let mut doc = EureDocument::new();
508        let invalid_id = NodeId(999999); // Invalid NodeId
509
510        // Create a NodeMut with invalid node_id
511        let node_mut = NodeMut::new(&mut doc, invalid_id);
512        let debug_output = alloc::format!("{:?}", node_mut);
513
514        // Should contain NodeMut and "<invalid>"
515        assert!(debug_output.contains("NodeMut"));
516        assert!(debug_output.contains("<invalid>"));
517    }
518
519    // =====================================================================
520    // Deterministic tests moved from proptests
521    // =====================================================================
522
523    #[test]
524    fn test_node_value_hole_is_hole() {
525        let value = NodeValue::hole();
526        assert!(value.is_hole());
527        assert_eq!(value, NodeValue::Hole(None));
528    }
529
530    #[test]
531    fn test_empty_containers_are_empty() {
532        let map = NodeValue::empty_map();
533        let array = NodeValue::empty_array();
534        let tuple = NodeValue::empty_tuple();
535
536        if let NodeValue::Map(m) = map {
537            assert!(m.is_empty());
538        } else {
539            panic!("empty_map should create Map");
540        }
541
542        if let NodeValue::Array(a) = array {
543            assert!(a.is_empty());
544        } else {
545            panic!("empty_array should create Array");
546        }
547
548        if let NodeValue::Tuple(t) = tuple {
549            assert!(t.is_empty());
550        } else {
551            panic!("empty_tuple should create Tuple");
552        }
553    }
554
555    #[test]
556    fn test_value_kind_correct() {
557        use crate::value::ValueKind;
558
559        let hole = NodeValue::hole();
560        assert_eq!(hole.value_kind(), ValueKind::Hole);
561
562        let primitive = NodeValue::Primitive(PrimitiveValue::Null);
563        assert_eq!(primitive.value_kind(), ValueKind::Null);
564
565        let bool_val = NodeValue::Primitive(PrimitiveValue::Bool(true));
566        assert_eq!(bool_val.value_kind(), ValueKind::Bool);
567
568        let array = NodeValue::empty_array();
569        assert_eq!(array.value_kind(), ValueKind::Array);
570
571        let map = NodeValue::empty_map();
572        assert_eq!(map.value_kind(), ValueKind::Map);
573
574        let tuple = NodeValue::empty_tuple();
575        assert_eq!(tuple.value_kind(), ValueKind::Tuple);
576    }
577
578    #[test]
579    fn test_require_map_idempotent() {
580        let mut node = Node {
581            content: NodeValue::empty_map(),
582            extensions: Map::new(),
583        };
584
585        for _ in 0..5 {
586            let result = node.require_map();
587            assert!(result.is_ok());
588        }
589
590        assert!(node.as_map().is_some());
591    }
592
593    #[test]
594    fn test_require_array_idempotent() {
595        let mut node = Node {
596            content: NodeValue::empty_array(),
597            extensions: Map::new(),
598        };
599
600        for _ in 0..5 {
601            let result = node.require_array();
602            assert!(result.is_ok());
603        }
604
605        assert!(node.as_array().is_some());
606    }
607
608    #[test]
609    fn test_require_tuple_idempotent() {
610        let mut node = Node {
611            content: NodeValue::empty_tuple(),
612            extensions: Map::new(),
613        };
614
615        for _ in 0..5 {
616            let result = node.require_tuple();
617            assert!(result.is_ok());
618        }
619
620        assert!(node.as_tuple().is_some());
621    }
622
623    #[test]
624    fn test_require_methods_type_mismatch() {
625        // Array node
626        let mut array_node = Node {
627            content: NodeValue::empty_array(),
628            extensions: Map::new(),
629        };
630        assert_eq!(
631            array_node.require_map().err(),
632            Some(InsertErrorKind::ExpectedMap)
633        );
634        assert_eq!(
635            array_node.require_tuple().err(),
636            Some(InsertErrorKind::ExpectedTuple)
637        );
638
639        // Map node
640        let mut map_node = Node {
641            content: NodeValue::empty_map(),
642            extensions: Map::new(),
643        };
644        assert_eq!(
645            map_node.require_array().err(),
646            Some(InsertErrorKind::ExpectedArray)
647        );
648        assert_eq!(
649            map_node.require_tuple().err(),
650            Some(InsertErrorKind::ExpectedTuple)
651        );
652
653        // Tuple node
654        let mut tuple_node = Node {
655            content: NodeValue::empty_tuple(),
656            extensions: Map::new(),
657        };
658        assert_eq!(
659            tuple_node.require_map().err(),
660            Some(InsertErrorKind::ExpectedMap)
661        );
662        assert_eq!(
663            tuple_node.require_array().err(),
664            Some(InsertErrorKind::ExpectedArray)
665        );
666    }
667}
668
669#[cfg(test)]
670mod proptests {
671    extern crate std;
672
673    use super::*;
674    use proptest::prelude::*;
675    use std::vec::Vec;
676
677    // =========================================================================
678    // NodeArray sequential index invariants
679    // =========================================================================
680
681    proptest! {
682        /// Invariant: NodeArray requires sequential indices from 0.
683        /// add_at(0) on empty array succeeds.
684        #[test]
685        fn array_add_at_zero_on_empty_succeeds(_dummy in Just(())) {
686            let mut array = NodeArray::new();
687            let result = array.add_at(0, NodeId(1));
688            prop_assert!(result.is_ok(), "add_at(0) on empty array should succeed");
689            prop_assert_eq!(array.len(), 1);
690        }
691
692        /// Invariant: NodeArray add_at(n) fails when length != n.
693        #[test]
694        fn array_add_at_wrong_index_fails(index in 1usize..100) {
695            let mut array = NodeArray::new();
696
697            let result = array.add_at(index, NodeId(1));
698            prop_assert!(result.is_err(), "add_at({}) on empty array should fail", index);
699
700            match result {
701                Err(InsertErrorKind::ArrayIndexInvalid { index: i, expected_index }) => {
702                    prop_assert_eq!(i, index);
703                    prop_assert_eq!(expected_index, 0);
704                }
705                other => prop_assert!(false, "Expected ArrayIndexInvalid, got {:?}", other),
706            }
707        }
708
709        /// Invariant: NodeArray add_at succeeds for sequential indices.
710        #[test]
711        fn array_sequential_add_succeeds(count in 1usize..50) {
712            let mut array = NodeArray::new();
713
714            for i in 0..count {
715                let result = array.add_at(i, NodeId(i));
716                prop_assert!(result.is_ok(), "add_at({}) should succeed", i);
717            }
718
719            prop_assert_eq!(array.len(), count);
720        }
721
722        /// Invariant: NodeArray add_at fails when skipping an index.
723        #[test]
724        fn array_skip_index_fails(
725            fill_count in 0usize..10,
726            skip_amount in 1usize..10,
727        ) {
728            let mut array = NodeArray::new();
729
730            // Fill sequentially
731            for i in 0..fill_count {
732                array.add_at(i, NodeId(i)).expect("Sequential add failed");
733            }
734
735            // Try to skip indices
736            let bad_index = fill_count + skip_amount;
737            let result = array.add_at(bad_index, NodeId(bad_index));
738
739            match result {
740                Err(InsertErrorKind::ArrayIndexInvalid { index, expected_index }) => {
741                    prop_assert_eq!(index, bad_index);
742                    prop_assert_eq!(expected_index, fill_count);
743                }
744                other => prop_assert!(false, "Expected ArrayIndexInvalid, got {:?}", other),
745            }
746        }
747
748        /// Invariant: NodeArray push always succeeds and appends.
749        #[test]
750        fn array_push_always_succeeds(count in 1usize..50) {
751            let mut array = NodeArray::new();
752
753            for i in 0..count {
754                let result = array.push(NodeId(i));
755                prop_assert!(result.is_ok(), "push should always succeed");
756                prop_assert_eq!(array.len(), i + 1);
757            }
758        }
759
760        /// Invariant: NodeArray get returns correct values for valid indices.
761        #[test]
762        fn array_get_returns_correct_values(count in 1usize..20) {
763            let mut array = NodeArray::new();
764            let mut expected = Vec::new();
765
766            for i in 0..count {
767                let node_id = NodeId(i * 10); // Use distinct values
768                array.push(node_id).unwrap();
769                expected.push(node_id);
770            }
771
772            for (i, &expected_id) in expected.iter().enumerate() {
773                prop_assert_eq!(array.get(i), Some(expected_id),
774                    "get({}) should return {:?}", i, expected_id);
775            }
776
777            // Out of bounds
778            prop_assert_eq!(array.get(count), None);
779            prop_assert_eq!(array.get(count + 100), None);
780        }
781
782        /// Invariant: NodeArray to_vec preserves order and values.
783        #[test]
784        fn array_to_vec_preserves_order(count in 0usize..20) {
785            let mut array = NodeArray::new();
786            let mut expected = Vec::new();
787
788            for i in 0..count {
789                let node_id = NodeId(i);
790                array.push(node_id).unwrap();
791                expected.push(node_id);
792            }
793
794            prop_assert_eq!(array.to_vec(), expected);
795        }
796
797        /// Invariant: NodeArray from_vec creates correct array.
798        #[test]
799        fn array_from_vec_roundtrip(ids in proptest::collection::vec(0usize..1000, 0..20)) {
800            let node_ids: Vec<NodeId> = ids.iter().map(|&i| NodeId(i)).collect();
801            let array = NodeArray::from_vec(node_ids.clone());
802
803            prop_assert_eq!(array.len(), node_ids.len());
804            prop_assert_eq!(array.to_vec(), node_ids);
805        }
806    }
807
808    // =========================================================================
809    // NodeTuple sequential index invariants
810    // =========================================================================
811
812    proptest! {
813        /// Invariant: NodeTuple requires sequential indices from 0.
814        /// add_at(0) on empty tuple succeeds.
815        #[test]
816        fn tuple_add_at_zero_on_empty_succeeds(_dummy in Just(())) {
817            let mut tuple = NodeTuple::new();
818            let result = tuple.add_at(0, NodeId(1));
819            prop_assert!(result.is_ok(), "add_at(0) on empty tuple should succeed");
820            prop_assert_eq!(tuple.len(), 1);
821        }
822
823        /// Invariant: NodeTuple add_at(n) fails when length != n.
824        #[test]
825        fn tuple_add_at_wrong_index_fails(index in 1u8..100) {
826            let mut tuple = NodeTuple::new();
827
828            let result = tuple.add_at(index, NodeId(1));
829            prop_assert!(result.is_err(), "add_at({}) on empty tuple should fail", index);
830
831            match result {
832                Err(InsertErrorKind::TupleIndexInvalid { index: i, expected_index }) => {
833                    prop_assert_eq!(i, index);
834                    prop_assert_eq!(expected_index, 0);
835                }
836                other => prop_assert!(false, "Expected TupleIndexInvalid, got {:?}", other),
837            }
838        }
839
840        /// Invariant: NodeTuple add_at succeeds for sequential indices.
841        #[test]
842        fn tuple_sequential_add_succeeds(count in 1u8..50) {
843            let mut tuple = NodeTuple::new();
844
845            for i in 0..count {
846                let result = tuple.add_at(i, NodeId(i as usize));
847                prop_assert!(result.is_ok(), "add_at({}) should succeed", i);
848            }
849
850            prop_assert_eq!(tuple.len(), count as usize);
851        }
852
853        /// Invariant: NodeTuple add_at fails when skipping an index.
854        #[test]
855        fn tuple_skip_index_fails(
856            fill_count in 0u8..10,
857            skip_amount in 1u8..10,
858        ) {
859            let mut tuple = NodeTuple::new();
860
861            // Fill sequentially
862            for i in 0..fill_count {
863                tuple.add_at(i, NodeId(i as usize)).expect("Sequential add failed");
864            }
865
866            // Try to skip indices
867            let bad_index = fill_count + skip_amount;
868            let result = tuple.add_at(bad_index, NodeId(bad_index as usize));
869
870            match result {
871                Err(InsertErrorKind::TupleIndexInvalid { index, expected_index }) => {
872                    prop_assert_eq!(index, bad_index);
873                    prop_assert_eq!(expected_index, fill_count as usize);
874                }
875                other => prop_assert!(false, "Expected TupleIndexInvalid, got {:?}", other),
876            }
877        }
878
879        /// Invariant: NodeTuple push always succeeds and appends.
880        #[test]
881        fn tuple_push_always_succeeds(count in 1usize..50) {
882            let mut tuple = NodeTuple::new();
883
884            for i in 0..count {
885                let result = tuple.push(NodeId(i));
886                prop_assert!(result.is_ok(), "push should always succeed");
887                prop_assert_eq!(tuple.len(), i + 1);
888            }
889        }
890
891        /// Invariant: NodeTuple get returns correct values for valid indices.
892        #[test]
893        fn tuple_get_returns_correct_values(count in 1usize..20) {
894            let mut tuple = NodeTuple::new();
895            let mut expected = Vec::new();
896
897            for i in 0..count {
898                let node_id = NodeId(i * 10);
899                tuple.push(node_id).unwrap();
900                expected.push(node_id);
901            }
902
903            for (i, &expected_id) in expected.iter().enumerate() {
904                prop_assert_eq!(tuple.get(i), Some(expected_id),
905                    "get({}) should return {:?}", i, expected_id);
906            }
907
908            // Out of bounds
909            prop_assert_eq!(tuple.get(count), None);
910            prop_assert_eq!(tuple.get(count + 100), None);
911        }
912
913        /// Invariant: NodeTuple to_vec preserves order and values.
914        #[test]
915        fn tuple_to_vec_preserves_order(count in 0usize..20) {
916            let mut tuple = NodeTuple::new();
917            let mut expected = Vec::new();
918
919            for i in 0..count {
920                let node_id = NodeId(i);
921                tuple.push(node_id).unwrap();
922                expected.push(node_id);
923            }
924
925            prop_assert_eq!(tuple.to_vec(), expected);
926        }
927
928        /// Invariant: NodeTuple from_vec creates correct tuple.
929        #[test]
930        fn tuple_from_vec_roundtrip(ids in proptest::collection::vec(0usize..1000, 0..20)) {
931            let node_ids: Vec<NodeId> = ids.iter().map(|&i| NodeId(i)).collect();
932            let tuple = NodeTuple::from_vec(node_ids.clone());
933
934            prop_assert_eq!(tuple.len(), node_ids.len());
935            prop_assert_eq!(tuple.to_vec(), node_ids);
936        }
937    }
938
939    // =========================================================================
940    // NodeValue type tests
941    // =========================================================================
942
943    proptest! {
944        /// Invariant: NodeValue::labeled_hole preserves label.
945        #[test]
946        fn node_value_labeled_hole_preserves_label(label in "[a-z][a-z0-9_-]{0,10}") {
947            let identifier: Identifier = label.parse().unwrap();
948            let value = NodeValue::labeled_hole(identifier.clone());
949
950            prop_assert!(value.is_hole());
951            prop_assert_eq!(value, NodeValue::Hole(Some(identifier)));
952        }
953    }
954}