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 insert_at(&mut self, index: usize, node_id: NodeId) -> Result<(), InsertErrorKind> {
354        if index > self.0.len() {
355            return Err(InsertErrorKind::ArrayIndexInvalid {
356                index,
357                expected_index: self.0.len(),
358            });
359        }
360        self.0.insert(index, node_id);
361        Ok(())
362    }
363
364    pub fn remove_at(&mut self, index: usize) -> Option<NodeId> {
365        if index >= self.0.len() {
366            return None;
367        }
368        Some(self.0.remove(index))
369    }
370
371    pub fn to_vec(&self) -> Vec<NodeId> {
372        self.0.clone()
373    }
374
375    pub fn from_vec(vec: Vec<NodeId>) -> Self {
376        Self(vec)
377    }
378
379    /// Try to convert to a fixed-size array.
380    /// Returns `None` if the length doesn't match.
381    pub fn try_into_array<const N: usize>(&self) -> Option<[NodeId; N]> {
382        self.0.as_slice().try_into().ok()
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    fn identifier(s: &str) -> Identifier {
391        s.parse().unwrap()
392    }
393
394    #[test]
395    fn test_require_map_on_uninitialized() {
396        let mut node = Node {
397            content: NodeValue::hole(),
398            extensions: Map::new(),
399        };
400
401        let map = node.require_map().expect("Should convert to map");
402        assert_eq!(map.len(), 0);
403
404        // Verify content was changed
405        assert!(node.as_map().is_some());
406    }
407
408    #[test]
409    fn test_require_map_on_existing_map() {
410        let mut node = Node {
411            content: NodeValue::Map(Default::default()),
412            extensions: Map::new(),
413        };
414
415        let map = node.require_map().expect("Should return existing map");
416        assert_eq!(map.len(), 0);
417    }
418
419    #[test]
420    fn test_require_map_on_wrong_type() {
421        let mut node = Node {
422            content: NodeValue::Primitive(PrimitiveValue::Null),
423            extensions: Map::new(),
424        };
425
426        let result = node.require_map();
427        assert_eq!(result, Err(InsertErrorKind::ExpectedMap));
428    }
429
430    #[test]
431    fn test_require_tuple_on_uninitialized() {
432        let mut node = Node {
433            content: NodeValue::hole(),
434            extensions: Map::new(),
435        };
436
437        let tuple = node.require_tuple().expect("Should convert to tuple");
438        assert_eq!(tuple.len(), 0);
439
440        // Verify content was changed
441        assert!(node.as_tuple().is_some());
442    }
443
444    #[test]
445    fn test_require_tuple_on_existing_tuple() {
446        let mut node = Node {
447            content: NodeValue::Tuple(Default::default()),
448            extensions: Map::new(),
449        };
450
451        let tuple = node.require_tuple().expect("Should return existing tuple");
452        assert_eq!(tuple.len(), 0);
453    }
454
455    #[test]
456    fn test_require_tuple_on_wrong_type() {
457        let mut node = Node {
458            content: NodeValue::Primitive(PrimitiveValue::Null),
459            extensions: Map::new(),
460        };
461
462        let result = node.require_tuple();
463        assert_eq!(result, Err(InsertErrorKind::ExpectedTuple));
464    }
465
466    #[test]
467    fn test_require_array_on_uninitialized() {
468        let mut node = Node {
469            content: NodeValue::hole(),
470            extensions: Map::new(),
471        };
472
473        let array = node.require_array().expect("Should convert to array");
474        assert_eq!(array.len(), 0);
475
476        // Verify content was changed
477        assert!(node.as_array().is_some());
478    }
479
480    #[test]
481    fn test_require_array_on_existing_array() {
482        let mut node = Node {
483            content: NodeValue::Array(Default::default()),
484            extensions: Map::new(),
485        };
486
487        let array = node.require_array().expect("Should return existing array");
488        assert_eq!(array.len(), 0);
489    }
490
491    #[test]
492    fn test_require_array_on_wrong_type() {
493        let mut node = Node {
494            content: NodeValue::Primitive(PrimitiveValue::Null),
495            extensions: Map::new(),
496        };
497
498        let result = node.require_array();
499        assert_eq!(result, Err(InsertErrorKind::ExpectedArray));
500    }
501
502    #[test]
503    fn test_node_get_extension_exists() {
504        let mut doc = EureDocument::new();
505        let root_id = doc.get_root_id();
506        let ext_identifier = identifier("test_ext");
507
508        // Add an extension
509        let ext_node_id = doc
510            .add_extension(ext_identifier.clone(), root_id)
511            .expect("Failed to add extension")
512            .node_id;
513
514        // Test get_extension on the node
515        let root_node = doc.node(root_id);
516        let result = root_node.get_extension(&ext_identifier);
517
518        assert!(result.is_some());
519        assert_eq!(result.unwrap(), ext_node_id);
520    }
521
522    #[test]
523    fn test_node_get_extension_missing() {
524        let doc = EureDocument::new();
525        let root_id = doc.get_root_id();
526        let ext_identifier = identifier("nonexistent");
527
528        // Test get_extension for a missing extension
529        let root_node = doc.node(root_id);
530        let result = root_node.get_extension(&ext_identifier);
531
532        assert!(result.is_none());
533    }
534
535    #[test]
536    fn test_node_mut_get_extension_exists() {
537        let mut doc = EureDocument::new();
538        let root_id = doc.get_root_id();
539        let ext_identifier = identifier("test_ext");
540
541        // Add an extension
542        let ext_node_id = doc
543            .add_extension(ext_identifier.clone(), root_id)
544            .expect("Failed to add extension")
545            .node_id;
546
547        // Test NodeMut::get_extension
548        let node_mut = NodeMut::new(&mut doc, root_id);
549        let result = node_mut.get_extension(&ext_identifier);
550
551        assert!(result.is_some());
552        let ext_node_mut = result.unwrap();
553        assert_eq!(ext_node_mut.node_id, ext_node_id);
554    }
555
556    #[test]
557    fn test_node_mut_get_extension_missing() {
558        let mut doc = EureDocument::new();
559        let root_id = doc.get_root_id();
560        let ext_identifier = identifier("nonexistent");
561
562        // Test NodeMut::get_extension for a missing extension
563        let node_mut = NodeMut::new(&mut doc, root_id);
564        let result = node_mut.get_extension(&ext_identifier);
565
566        assert!(result.is_none());
567    }
568
569    #[test]
570    fn test_node_mut_debug_valid_node() {
571        let mut doc = EureDocument::new();
572        let root_id = doc.get_root_id();
573
574        // Create a NodeMut with valid node_id
575        let node_mut = NodeMut::new(&mut doc, root_id);
576        let debug_output = alloc::format!("{:?}", node_mut);
577
578        // Should contain NodeMut, node_id, and delegate to Node's Debug
579        assert!(debug_output.contains("NodeMut"));
580        assert!(debug_output.contains("NodeId"));
581        assert!(debug_output.contains("Node"));
582        assert!(debug_output.contains("Hole"));
583    }
584
585    #[test]
586    fn test_node_mut_debug_invalid_node() {
587        let mut doc = EureDocument::new();
588        let invalid_id = NodeId(999999); // Invalid NodeId
589
590        // Create a NodeMut with invalid node_id
591        let node_mut = NodeMut::new(&mut doc, invalid_id);
592        let debug_output = alloc::format!("{:?}", node_mut);
593
594        // Should contain NodeMut and "<invalid>"
595        assert!(debug_output.contains("NodeMut"));
596        assert!(debug_output.contains("<invalid>"));
597    }
598
599    // =====================================================================
600    // Deterministic tests moved from proptests
601    // =====================================================================
602
603    #[test]
604    fn test_node_value_hole_is_hole() {
605        let value = NodeValue::hole();
606        assert!(value.is_hole());
607        assert_eq!(value, NodeValue::Hole(None));
608    }
609
610    #[test]
611    fn test_empty_containers_are_empty() {
612        let map = NodeValue::empty_map();
613        let array = NodeValue::empty_array();
614        let tuple = NodeValue::empty_tuple();
615
616        if let NodeValue::Map(m) = map {
617            assert!(m.is_empty());
618        } else {
619            panic!("empty_map should create Map");
620        }
621
622        if let NodeValue::Array(a) = array {
623            assert!(a.is_empty());
624        } else {
625            panic!("empty_array should create Array");
626        }
627
628        if let NodeValue::Tuple(t) = tuple {
629            assert!(t.is_empty());
630        } else {
631            panic!("empty_tuple should create Tuple");
632        }
633    }
634
635    #[test]
636    fn test_value_kind_correct() {
637        use crate::value::ValueKind;
638
639        let hole = NodeValue::hole();
640        assert_eq!(hole.value_kind(), ValueKind::Hole);
641
642        let primitive = NodeValue::Primitive(PrimitiveValue::Null);
643        assert_eq!(primitive.value_kind(), ValueKind::Null);
644
645        let bool_val = NodeValue::Primitive(PrimitiveValue::Bool(true));
646        assert_eq!(bool_val.value_kind(), ValueKind::Bool);
647
648        let array = NodeValue::empty_array();
649        assert_eq!(array.value_kind(), ValueKind::Array);
650
651        let map = NodeValue::empty_map();
652        assert_eq!(map.value_kind(), ValueKind::Map);
653
654        let tuple = NodeValue::empty_tuple();
655        assert_eq!(tuple.value_kind(), ValueKind::Tuple);
656    }
657
658    #[test]
659    fn test_require_map_idempotent() {
660        let mut node = Node {
661            content: NodeValue::empty_map(),
662            extensions: Map::new(),
663        };
664
665        for _ in 0..5 {
666            let result = node.require_map();
667            assert!(result.is_ok());
668        }
669
670        assert!(node.as_map().is_some());
671    }
672
673    #[test]
674    fn test_require_array_idempotent() {
675        let mut node = Node {
676            content: NodeValue::empty_array(),
677            extensions: Map::new(),
678        };
679
680        for _ in 0..5 {
681            let result = node.require_array();
682            assert!(result.is_ok());
683        }
684
685        assert!(node.as_array().is_some());
686    }
687
688    #[test]
689    fn test_require_tuple_idempotent() {
690        let mut node = Node {
691            content: NodeValue::empty_tuple(),
692            extensions: Map::new(),
693        };
694
695        for _ in 0..5 {
696            let result = node.require_tuple();
697            assert!(result.is_ok());
698        }
699
700        assert!(node.as_tuple().is_some());
701    }
702
703    #[test]
704    fn test_require_methods_type_mismatch() {
705        // Array node
706        let mut array_node = Node {
707            content: NodeValue::empty_array(),
708            extensions: Map::new(),
709        };
710        assert_eq!(
711            array_node.require_map().err(),
712            Some(InsertErrorKind::ExpectedMap)
713        );
714        assert_eq!(
715            array_node.require_tuple().err(),
716            Some(InsertErrorKind::ExpectedTuple)
717        );
718
719        // Map node
720        let mut map_node = Node {
721            content: NodeValue::empty_map(),
722            extensions: Map::new(),
723        };
724        assert_eq!(
725            map_node.require_array().err(),
726            Some(InsertErrorKind::ExpectedArray)
727        );
728        assert_eq!(
729            map_node.require_tuple().err(),
730            Some(InsertErrorKind::ExpectedTuple)
731        );
732
733        // Tuple node
734        let mut tuple_node = Node {
735            content: NodeValue::empty_tuple(),
736            extensions: Map::new(),
737        };
738        assert_eq!(
739            tuple_node.require_map().err(),
740            Some(InsertErrorKind::ExpectedMap)
741        );
742        assert_eq!(
743            tuple_node.require_array().err(),
744            Some(InsertErrorKind::ExpectedArray)
745        );
746    }
747}
748
749#[cfg(test)]
750mod proptests {
751    extern crate std;
752
753    use super::*;
754    use proptest::prelude::*;
755    use std::vec::Vec;
756
757    // =========================================================================
758    // NodeArray sequential index invariants
759    // =========================================================================
760
761    proptest! {
762        /// Invariant: NodeArray requires sequential indices from 0.
763        /// add_at(0) on empty array succeeds.
764        #[test]
765        fn array_add_at_zero_on_empty_succeeds(_dummy in Just(())) {
766            let mut array = NodeArray::new();
767            let result = array.add_at(0, NodeId(1));
768            prop_assert!(result.is_ok(), "add_at(0) on empty array should succeed");
769            prop_assert_eq!(array.len(), 1);
770        }
771
772        /// Invariant: NodeArray add_at(n) fails when length != n.
773        #[test]
774        fn array_add_at_wrong_index_fails(index in 1usize..100) {
775            let mut array = NodeArray::new();
776
777            let result = array.add_at(index, NodeId(1));
778            prop_assert!(result.is_err(), "add_at({}) on empty array should fail", index);
779
780            match result {
781                Err(InsertErrorKind::ArrayIndexInvalid { index: i, expected_index }) => {
782                    prop_assert_eq!(i, index);
783                    prop_assert_eq!(expected_index, 0);
784                }
785                other => prop_assert!(false, "Expected ArrayIndexInvalid, got {:?}", other),
786            }
787        }
788
789        /// Invariant: NodeArray add_at succeeds for sequential indices.
790        #[test]
791        fn array_sequential_add_succeeds(count in 1usize..50) {
792            let mut array = NodeArray::new();
793
794            for i in 0..count {
795                let result = array.add_at(i, NodeId(i));
796                prop_assert!(result.is_ok(), "add_at({}) should succeed", i);
797            }
798
799            prop_assert_eq!(array.len(), count);
800        }
801
802        /// Invariant: NodeArray add_at fails when skipping an index.
803        #[test]
804        fn array_skip_index_fails(
805            fill_count in 0usize..10,
806            skip_amount in 1usize..10,
807        ) {
808            let mut array = NodeArray::new();
809
810            // Fill sequentially
811            for i in 0..fill_count {
812                array.add_at(i, NodeId(i)).expect("Sequential add failed");
813            }
814
815            // Try to skip indices
816            let bad_index = fill_count + skip_amount;
817            let result = array.add_at(bad_index, NodeId(bad_index));
818
819            match result {
820                Err(InsertErrorKind::ArrayIndexInvalid { index, expected_index }) => {
821                    prop_assert_eq!(index, bad_index);
822                    prop_assert_eq!(expected_index, fill_count);
823                }
824                other => prop_assert!(false, "Expected ArrayIndexInvalid, got {:?}", other),
825            }
826        }
827
828        /// Invariant: NodeArray push always succeeds and appends.
829        #[test]
830        fn array_push_always_succeeds(count in 1usize..50) {
831            let mut array = NodeArray::new();
832
833            for i in 0..count {
834                let result = array.push(NodeId(i));
835                prop_assert!(result.is_ok(), "push should always succeed");
836                prop_assert_eq!(array.len(), i + 1);
837            }
838        }
839
840        /// Invariant: NodeArray get returns correct values for valid indices.
841        #[test]
842        fn array_get_returns_correct_values(count in 1usize..20) {
843            let mut array = NodeArray::new();
844            let mut expected = Vec::new();
845
846            for i in 0..count {
847                let node_id = NodeId(i * 10); // Use distinct values
848                array.push(node_id).unwrap();
849                expected.push(node_id);
850            }
851
852            for (i, &expected_id) in expected.iter().enumerate() {
853                prop_assert_eq!(array.get(i), Some(expected_id),
854                    "get({}) should return {:?}", i, expected_id);
855            }
856
857            // Out of bounds
858            prop_assert_eq!(array.get(count), None);
859            prop_assert_eq!(array.get(count + 100), None);
860        }
861
862        /// Invariant: NodeArray to_vec preserves order and values.
863        #[test]
864        fn array_to_vec_preserves_order(count in 0usize..20) {
865            let mut array = NodeArray::new();
866            let mut expected = Vec::new();
867
868            for i in 0..count {
869                let node_id = NodeId(i);
870                array.push(node_id).unwrap();
871                expected.push(node_id);
872            }
873
874            prop_assert_eq!(array.to_vec(), expected);
875        }
876
877        /// Invariant: NodeArray from_vec creates correct array.
878        #[test]
879        fn array_from_vec_roundtrip(ids in proptest::collection::vec(0usize..1000, 0..20)) {
880            let node_ids: Vec<NodeId> = ids.iter().map(|&i| NodeId(i)).collect();
881            let array = NodeArray::from_vec(node_ids.clone());
882
883            prop_assert_eq!(array.len(), node_ids.len());
884            prop_assert_eq!(array.to_vec(), node_ids);
885        }
886    }
887
888    // =========================================================================
889    // NodeTuple sequential index invariants
890    // =========================================================================
891
892    proptest! {
893        /// Invariant: NodeTuple requires sequential indices from 0.
894        /// add_at(0) on empty tuple succeeds.
895        #[test]
896        fn tuple_add_at_zero_on_empty_succeeds(_dummy in Just(())) {
897            let mut tuple = NodeTuple::new();
898            let result = tuple.add_at(0, NodeId(1));
899            prop_assert!(result.is_ok(), "add_at(0) on empty tuple should succeed");
900            prop_assert_eq!(tuple.len(), 1);
901        }
902
903        /// Invariant: NodeTuple add_at(n) fails when length != n.
904        #[test]
905        fn tuple_add_at_wrong_index_fails(index in 1u8..100) {
906            let mut tuple = NodeTuple::new();
907
908            let result = tuple.add_at(index, NodeId(1));
909            prop_assert!(result.is_err(), "add_at({}) on empty tuple should fail", index);
910
911            match result {
912                Err(InsertErrorKind::TupleIndexInvalid { index: i, expected_index }) => {
913                    prop_assert_eq!(i, index);
914                    prop_assert_eq!(expected_index, 0);
915                }
916                other => prop_assert!(false, "Expected TupleIndexInvalid, got {:?}", other),
917            }
918        }
919
920        /// Invariant: NodeTuple add_at succeeds for sequential indices.
921        #[test]
922        fn tuple_sequential_add_succeeds(count in 1u8..50) {
923            let mut tuple = NodeTuple::new();
924
925            for i in 0..count {
926                let result = tuple.add_at(i, NodeId(i as usize));
927                prop_assert!(result.is_ok(), "add_at({}) should succeed", i);
928            }
929
930            prop_assert_eq!(tuple.len(), count as usize);
931        }
932
933        /// Invariant: NodeTuple add_at fails when skipping an index.
934        #[test]
935        fn tuple_skip_index_fails(
936            fill_count in 0u8..10,
937            skip_amount in 1u8..10,
938        ) {
939            let mut tuple = NodeTuple::new();
940
941            // Fill sequentially
942            for i in 0..fill_count {
943                tuple.add_at(i, NodeId(i as usize)).expect("Sequential add failed");
944            }
945
946            // Try to skip indices
947            let bad_index = fill_count + skip_amount;
948            let result = tuple.add_at(bad_index, NodeId(bad_index as usize));
949
950            match result {
951                Err(InsertErrorKind::TupleIndexInvalid { index, expected_index }) => {
952                    prop_assert_eq!(index, bad_index);
953                    prop_assert_eq!(expected_index, fill_count as usize);
954                }
955                other => prop_assert!(false, "Expected TupleIndexInvalid, got {:?}", other),
956            }
957        }
958
959        /// Invariant: NodeTuple push always succeeds and appends.
960        #[test]
961        fn tuple_push_always_succeeds(count in 1usize..50) {
962            let mut tuple = NodeTuple::new();
963
964            for i in 0..count {
965                let result = tuple.push(NodeId(i));
966                prop_assert!(result.is_ok(), "push should always succeed");
967                prop_assert_eq!(tuple.len(), i + 1);
968            }
969        }
970
971        /// Invariant: NodeTuple get returns correct values for valid indices.
972        #[test]
973        fn tuple_get_returns_correct_values(count in 1usize..20) {
974            let mut tuple = NodeTuple::new();
975            let mut expected = Vec::new();
976
977            for i in 0..count {
978                let node_id = NodeId(i * 10);
979                tuple.push(node_id).unwrap();
980                expected.push(node_id);
981            }
982
983            for (i, &expected_id) in expected.iter().enumerate() {
984                prop_assert_eq!(tuple.get(i), Some(expected_id),
985                    "get({}) should return {:?}", i, expected_id);
986            }
987
988            // Out of bounds
989            prop_assert_eq!(tuple.get(count), None);
990            prop_assert_eq!(tuple.get(count + 100), None);
991        }
992
993        /// Invariant: NodeTuple to_vec preserves order and values.
994        #[test]
995        fn tuple_to_vec_preserves_order(count in 0usize..20) {
996            let mut tuple = NodeTuple::new();
997            let mut expected = Vec::new();
998
999            for i in 0..count {
1000                let node_id = NodeId(i);
1001                tuple.push(node_id).unwrap();
1002                expected.push(node_id);
1003            }
1004
1005            prop_assert_eq!(tuple.to_vec(), expected);
1006        }
1007
1008        /// Invariant: NodeTuple from_vec creates correct tuple.
1009        #[test]
1010        fn tuple_from_vec_roundtrip(ids in proptest::collection::vec(0usize..1000, 0..20)) {
1011            let node_ids: Vec<NodeId> = ids.iter().map(|&i| NodeId(i)).collect();
1012            let tuple = NodeTuple::from_vec(node_ids.clone());
1013
1014            prop_assert_eq!(tuple.len(), node_ids.len());
1015            prop_assert_eq!(tuple.to_vec(), node_ids);
1016        }
1017    }
1018
1019    // =========================================================================
1020    // NodeValue type tests
1021    // =========================================================================
1022
1023    proptest! {
1024        /// Invariant: NodeValue::labeled_hole preserves label.
1025        #[test]
1026        fn node_value_labeled_hole_preserves_label(label in "[a-z][a-z0-9_-]{0,10}") {
1027            let identifier: Identifier = label.parse().unwrap();
1028            let value = NodeValue::labeled_hole(identifier.clone());
1029
1030            prop_assert!(value.is_hole());
1031            prop_assert_eq!(value, NodeValue::Hole(Some(identifier)));
1032        }
1033    }
1034}