Skip to main content

eure_document/document/
node.rs

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