1use std::fmt;
19
20use serde::{Deserialize, Serialize};
21
22use super::super::file::id::FileId;
23use super::super::node::id::{GenerationOverflowError, NodeId};
24use super::super::node::kind::NodeKind;
25use super::super::string::id::StringId;
26use crate::graph::body_hash::BodyHash128;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum ArenaError {
34 GenerationOverflow(GenerationOverflowError),
36
37 FreeListCorruption {
43 index: u32,
45 },
46
47 CapacityExceeded,
49}
50
51impl fmt::Display for ArenaError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 ArenaError::GenerationOverflow(e) => write!(f, "{e}"),
55 ArenaError::FreeListCorruption { index } => {
56 write!(
57 f,
58 "free list corruption: occupied slot found at index {index}"
59 )
60 }
61 ArenaError::CapacityExceeded => {
62 write!(
63 f,
64 "arena capacity exceeded: cannot allocate more than {} nodes",
65 u32::MAX
66 )
67 }
68 }
69 }
70}
71
72impl std::error::Error for ArenaError {
73 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
74 match self {
75 ArenaError::GenerationOverflow(e) => Some(e),
76 _ => None,
77 }
78 }
79}
80
81impl From<GenerationOverflowError> for ArenaError {
82 fn from(e: GenerationOverflowError) -> Self {
83 ArenaError::GenerationOverflow(e)
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub enum SlotState<T> {
90 Occupied(T),
92 Vacant {
94 next_free: Option<u32>,
96 },
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct Slot<T> {
102 generation: u64,
104 state: SlotState<T>,
106}
107
108impl<T> Slot<T> {
109 #[allow(dead_code)] pub(crate) fn new_vacant(generation: u64, next_free: Option<u32>) -> Self {
112 Self {
113 generation,
114 state: SlotState::Vacant { next_free },
115 }
116 }
117
118 pub(crate) fn new_occupied(generation: u64, data: T) -> Self {
120 Self {
121 generation,
122 state: SlotState::Occupied(data),
123 }
124 }
125
126 #[inline]
128 pub fn is_occupied(&self) -> bool {
129 matches!(self.state, SlotState::Occupied(_))
130 }
131
132 #[inline]
134 pub fn is_vacant(&self) -> bool {
135 matches!(self.state, SlotState::Vacant { .. })
136 }
137
138 #[inline]
140 pub fn generation(&self) -> u64 {
141 self.generation
142 }
143
144 pub fn get(&self) -> Option<&T> {
146 match &self.state {
147 SlotState::Occupied(data) => Some(data),
148 SlotState::Vacant { .. } => None,
149 }
150 }
151
152 #[inline]
154 pub fn state(&self) -> &SlotState<T> {
155 &self.state
156 }
157
158 pub fn get_mut(&mut self) -> Option<&mut T> {
160 match &mut self.state {
161 SlotState::Occupied(data) => Some(data),
162 SlotState::Vacant { .. } => None,
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct NodeEntry {
172 pub kind: NodeKind,
174 pub name: StringId,
176 pub file: FileId,
178 pub start_byte: u32,
180 pub end_byte: u32,
182 pub start_line: u32,
184 pub start_column: u32,
186 pub end_line: u32,
188 pub end_column: u32,
190 pub signature: Option<StringId>,
192 pub doc: Option<StringId>,
194 pub qualified_name: Option<StringId>,
196 pub visibility: Option<StringId>,
198 pub is_async: bool,
200 pub is_static: bool,
202 #[serde(default)]
212 pub body_hash: Option<BodyHash128>,
213 #[serde(default)]
224 pub is_unsafe: bool,
225}
226
227impl NodeEntry {
228 #[must_use]
230 pub fn new(kind: NodeKind, name: StringId, file: FileId) -> Self {
231 Self {
232 kind,
233 name,
234 file,
235 start_byte: 0,
236 end_byte: 0,
237 start_line: 0,
238 start_column: 0,
239 end_line: 0,
240 end_column: 0,
241 signature: None,
242 doc: None,
243 qualified_name: None,
244 visibility: None,
245 is_async: false,
246 is_static: false,
247 is_unsafe: false,
248 body_hash: None,
249 }
250 }
251
252 #[must_use]
254 pub fn with_byte_range(mut self, start: u32, end: u32) -> Self {
255 self.start_byte = start;
256 self.end_byte = end;
257 self
258 }
259
260 #[must_use]
262 pub fn with_location(
263 mut self,
264 start_line: u32,
265 start_column: u32,
266 end_line: u32,
267 end_column: u32,
268 ) -> Self {
269 self.start_line = start_line;
270 self.start_column = start_column;
271 self.end_line = end_line;
272 self.end_column = end_column;
273 self
274 }
275
276 #[inline]
296 #[must_use]
297 pub fn is_unified_loser(&self) -> bool {
298 self.name == StringId::INVALID
299 }
300
301 #[inline]
340 #[must_use]
341 pub fn is_synthetic_placeholder_name(display_name: &str) -> bool {
342 is_synthetic_placeholder_name_shape(display_name)
343 }
344
345 #[must_use]
347 pub fn with_signature(mut self, signature: StringId) -> Self {
348 self.signature = Some(signature);
349 self
350 }
351
352 #[must_use]
354 pub fn with_doc(mut self, doc: StringId) -> Self {
355 self.doc = Some(doc);
356 self
357 }
358
359 #[must_use]
361 pub fn with_qualified_name(mut self, qualified_name: StringId) -> Self {
362 self.qualified_name = Some(qualified_name);
363 self
364 }
365
366 #[must_use]
368 pub fn with_visibility(mut self, visibility: StringId) -> Self {
369 self.visibility = Some(visibility);
370 self
371 }
372
373 #[must_use]
375 pub fn with_async(mut self, is_async: bool) -> Self {
376 self.is_async = is_async;
377 self
378 }
379
380 #[must_use]
382 pub fn with_static(mut self, is_static: bool) -> Self {
383 self.is_static = is_static;
384 self
385 }
386
387 #[must_use]
389 pub fn with_unsafe(mut self, is_unsafe: bool) -> Self {
390 self.is_unsafe = is_unsafe;
391 self
392 }
393
394 #[must_use]
399 pub fn with_body_hash(mut self, hash: BodyHash128) -> Self {
400 self.body_hash = Some(hash);
401 self
402 }
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct NodeArena {
429 slots: Vec<Slot<NodeEntry>>,
431 free_head: Option<u32>,
433 len: usize,
435}
436
437impl NodeArena {
438 #[must_use]
440 pub fn new() -> Self {
441 Self {
442 slots: Vec::new(),
443 free_head: None,
444 len: 0,
445 }
446 }
447
448 #[must_use]
450 pub fn with_capacity(capacity: usize) -> Self {
451 Self {
452 slots: Vec::with_capacity(capacity),
453 free_head: None,
454 len: 0,
455 }
456 }
457
458 #[must_use]
466 pub(crate) fn from_raw_parts(
467 slots: Vec<Slot<NodeEntry>>,
468 free_head: Option<u32>,
469 len: usize,
470 ) -> Self {
471 Self {
472 slots,
473 free_head,
474 len,
475 }
476 }
477
478 #[inline]
480 #[must_use]
481 pub fn len(&self) -> usize {
482 self.len
483 }
484
485 #[inline]
487 #[must_use]
488 pub fn is_empty(&self) -> bool {
489 self.len == 0
490 }
491
492 #[inline]
494 #[must_use]
495 pub fn capacity(&self) -> usize {
496 self.slots.capacity()
497 }
498
499 #[inline]
504 #[must_use]
505 pub fn slot_count(&self) -> usize {
506 self.slots.len()
507 }
508
509 pub fn alloc(&mut self, entry: NodeEntry) -> Result<NodeId, ArenaError> {
521 self.len += 1;
522
523 if let Some(free_idx) = self.free_head {
524 let slot_index = free_idx as usize;
526 if slot_index >= self.slots.len() {
527 self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
529 }
530
531 let slot = &mut self.slots[slot_index];
533
534 self.free_head = match &slot.state {
536 SlotState::Vacant { next_free } => *next_free,
537 SlotState::Occupied(_) => {
538 self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
541 }
542 };
543
544 let temp_id = NodeId::new(free_idx, slot.generation);
547 let new_generation = temp_id.try_increment_generation().map_err(|mut e| {
548 self.len -= 1; e.index = free_idx; ArenaError::GenerationOverflow(e)
551 })?;
552
553 slot.generation = new_generation;
554 slot.state = SlotState::Occupied(entry);
555
556 Ok(NodeId::new(free_idx, new_generation))
557 } else {
558 let index = self.slots.len();
560
561 let Ok(index_u32) = u32::try_from(index) else {
563 self.len -= 1; return Err(ArenaError::CapacityExceeded);
565 };
566
567 let slot = Slot::new_occupied(1, entry);
568 self.slots.push(slot);
569
570 Ok(NodeId::new(index_u32, 1))
571 }
572 }
573
574 #[must_use]
578 pub fn get(&self, id: NodeId) -> Option<&NodeEntry> {
579 if id.is_invalid() {
580 return None;
581 }
582
583 let index = id.index() as usize;
584 let slot = self.slots.get(index)?;
585
586 if slot.generation != id.generation() {
588 return None;
589 }
590
591 slot.get()
592 }
593
594 #[must_use]
598 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut NodeEntry> {
599 if id.is_invalid() {
600 return None;
601 }
602
603 let index = id.index() as usize;
604 let slot = self.slots.get_mut(index)?;
605
606 if slot.generation != id.generation() {
608 return None;
609 }
610
611 slot.get_mut()
612 }
613
614 #[must_use]
616 pub fn contains(&self, id: NodeId) -> bool {
617 self.get(id).is_some()
618 }
619
620 pub fn remove(&mut self, id: NodeId) -> Option<NodeEntry> {
626 if id.is_invalid() {
627 return None;
628 }
629
630 let index = id.index() as usize;
631 let slot = self.slots.get_mut(index)?;
632
633 if slot.generation != id.generation() {
635 return None;
636 }
637
638 if let SlotState::Occupied(_) = &slot.state {
641 let old_state = std::mem::replace(
642 &mut slot.state,
643 SlotState::Vacant {
644 next_free: self.free_head,
645 },
646 );
647
648 self.free_head = Some(id.index());
650 self.len -= 1;
651
652 if let SlotState::Occupied(entry) = old_state {
653 return Some(entry);
654 }
655 }
656
657 None
658 }
659
660 pub fn iter(&self) -> impl Iterator<Item = (NodeId, &NodeEntry)> {
662 self.slots.iter().enumerate().filter_map(|(index, slot)| {
663 if let SlotState::Occupied(entry) = &slot.state {
664 let index_u32 = u32::try_from(index).ok()?;
665 Some((NodeId::new(index_u32, slot.generation), entry))
666 } else {
667 None
668 }
669 })
670 }
671
672 pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut NodeEntry)> {
674 self.slots
675 .iter_mut()
676 .enumerate()
677 .filter_map(|(index, slot)| {
678 let generation = slot.generation;
679 if let SlotState::Occupied(entry) = &mut slot.state {
680 let index_u32 = u32::try_from(index).ok()?;
681 Some((NodeId::new(index_u32, generation), entry))
682 } else {
683 None
684 }
685 })
686 }
687
688 pub fn clear(&mut self) {
692 self.slots.clear();
693 self.free_head = None;
694 self.len = 0;
695 }
696
697 pub fn reserve(&mut self, additional: usize) {
699 self.slots.reserve(additional);
700 }
701
702 #[must_use]
706 pub fn slot(&self, index: u32) -> Option<&Slot<NodeEntry>> {
707 self.slots.get(index as usize)
708 }
709
710 pub fn alloc_range(&mut self, count: u32, placeholder: &NodeEntry) -> Result<u32, ArenaError> {
724 if count == 0 {
725 return Ok(u32::try_from(self.slots.len())
728 .unwrap_or_else(|_| unreachable!("slot_count invariant violated")));
729 }
730
731 let start = self.slots.len();
732 let new_total = start
733 .checked_add(count as usize)
734 .ok_or(ArenaError::CapacityExceeded)?;
735
736 if new_total > u32::MAX as usize + 1 {
738 return Err(ArenaError::CapacityExceeded);
739 }
740
741 let start_u32 = u32::try_from(start).map_err(|_| ArenaError::CapacityExceeded)?;
742
743 self.slots.reserve(count as usize);
745 for _ in 0..count {
746 self.slots.push(Slot::new_occupied(1, placeholder.clone()));
747 }
748
749 self.len += count as usize;
751
752 Ok(start_u32)
753 }
754
755 #[must_use]
764 pub fn bulk_slice_mut(&mut self, start: u32, count: u32) -> &mut [Slot<NodeEntry>] {
765 let begin = start as usize;
766 let end = begin + count as usize;
767 &mut self.slots[begin..end]
768 }
769
770 pub fn truncate_to(&mut self, saved_slot_count: usize) {
781 assert!(
782 saved_slot_count <= self.slots.len(),
783 "truncate_to({saved_slot_count}) exceeds current slot count ({})",
784 self.slots.len(),
785 );
786
787 let dropped_occupied = self.slots[saved_slot_count..]
789 .iter()
790 .filter(|s| s.is_occupied())
791 .count();
792
793 self.slots.truncate(saved_slot_count);
794 self.len -= dropped_occupied;
795
796 self.rebuild_free_list_after_truncate(saved_slot_count);
799 }
800
801 fn rebuild_free_list_after_truncate(&mut self, cutoff: usize) {
807 let mut new_head: Option<u32> = None;
811 for i in (0..cutoff).rev() {
814 if self.slots[i].is_vacant() {
815 let i_u32 = u32::try_from(i)
816 .unwrap_or_else(|_| unreachable!("free-list index exceeds u32 invariant"));
817 self.slots[i].state = SlotState::Vacant {
818 next_free: new_head,
819 };
820 new_head = Some(i_u32);
821 }
822 }
823 self.free_head = new_head;
824 }
825
826 #[must_use]
831 pub fn slots(&self) -> &[Slot<NodeEntry>] {
832 &self.slots
833 }
834}
835
836impl Default for NodeArena {
837 fn default() -> Self {
838 Self::new()
839 }
840}
841
842impl fmt::Display for NodeArena {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844 write!(
845 f,
846 "NodeArena(len={}, capacity={})",
847 self.len,
848 self.capacity()
849 )
850 }
851}
852
853impl crate::graph::unified::memory::GraphMemorySize for NodeArena {
854 fn heap_bytes(&self) -> usize {
855 self.slots.capacity() * std::mem::size_of::<Slot<NodeEntry>>()
856 }
857}
858
859fn is_synthetic_placeholder_name_shape(name: &str) -> bool {
873 if name.starts_with('<') {
874 return true;
875 }
876 if let Some((_, rest)) = name.rsplit_once('@')
878 && !rest.is_empty()
879 && rest.bytes().all(|b| b.is_ascii_digit())
880 {
881 return true;
882 }
883 false
884}
885
886#[cfg(test)]
887mod tests {
888 use super::*;
889
890 fn test_file() -> FileId {
891 FileId::new(1)
892 }
893
894 fn test_name() -> StringId {
895 StringId::new(1)
896 }
897
898 fn test_entry(kind: NodeKind) -> NodeEntry {
899 NodeEntry::new(kind, test_name(), test_file())
900 }
901
902 #[test]
903 fn test_arena_new() {
904 let arena = NodeArena::new();
905 assert_eq!(arena.len(), 0);
906 assert!(arena.is_empty());
907 assert_eq!(arena.capacity(), 0);
908 }
909
910 #[test]
911 fn synthetic_placeholder_name_shape_recognises_known_synthetics() {
912 assert!(is_synthetic_placeholder_name_shape("<field:s.NeedTags>"));
914 assert!(is_synthetic_placeholder_name_shape(
915 "<field:selector.NeedTags>"
916 ));
917 assert!(is_synthetic_placeholder_name_shape("<type:main.Foo>"));
919 assert!(is_synthetic_placeholder_name_shape("NeedTags@469"));
921 assert!(is_synthetic_placeholder_name_shape("NeedTags@508"));
922 assert!(is_synthetic_placeholder_name_shape("foo@0"));
923 assert!(NodeEntry::is_synthetic_placeholder_name("<field:x.y>"));
925 assert!(NodeEntry::is_synthetic_placeholder_name("x@123"));
926 }
927
928 #[test]
929 fn synthetic_placeholder_name_shape_passes_real_identifiers() {
930 assert!(!is_synthetic_placeholder_name_shape("NeedTags"));
932 assert!(!is_synthetic_placeholder_name_shape(
933 "main.SelectorSource.NeedTags"
934 ));
935 assert!(!is_synthetic_placeholder_name_shape("main.useSelector"));
936 assert!(!is_synthetic_placeholder_name_shape("foo"));
937 assert!(!is_synthetic_placeholder_name_shape(""));
938 assert!(!is_synthetic_placeholder_name_shape("foo@bar"));
940 assert!(!is_synthetic_placeholder_name_shape("foo@"));
942 }
943
944 #[test]
945 fn test_arena_with_capacity() {
946 let arena = NodeArena::with_capacity(100);
947 assert_eq!(arena.len(), 0);
948 assert!(arena.capacity() >= 100);
949 }
950
951 #[test]
952 fn test_alloc_and_get() {
953 let mut arena = NodeArena::new();
954 let entry = test_entry(NodeKind::Function);
955
956 let id = arena.alloc(entry).unwrap();
957 assert!(!id.is_invalid());
958 assert_eq!(arena.len(), 1);
959
960 let node = arena.get(id).unwrap();
961 assert_eq!(node.kind, NodeKind::Function);
962 }
963
964 #[test]
965 fn test_alloc_multiple() {
966 let mut arena = NodeArena::new();
967
968 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
969 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
970 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
971
972 assert_eq!(arena.len(), 3);
973 assert_ne!(id1, id2);
974 assert_ne!(id2, id3);
975
976 assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Function);
977 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
978 assert_eq!(arena.get(id3).unwrap().kind, NodeKind::Method);
979 }
980
981 #[test]
982 fn test_get_mut() {
983 let mut arena = NodeArena::new();
984 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
985
986 {
987 let node = arena.get_mut(id).unwrap();
988 node.start_line = 42;
989 }
990
991 assert_eq!(arena.get(id).unwrap().start_line, 42);
992 }
993
994 #[test]
995 fn test_contains() {
996 let mut arena = NodeArena::new();
997 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
998
999 assert!(arena.contains(id));
1000 assert!(!arena.contains(NodeId::INVALID));
1001 }
1002
1003 #[test]
1004 fn test_remove() {
1005 let mut arena = NodeArena::new();
1006 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1007
1008 assert_eq!(arena.len(), 1);
1009 assert!(arena.contains(id));
1010
1011 let removed = arena.remove(id).unwrap();
1012 assert_eq!(removed.kind, NodeKind::Function);
1013 assert_eq!(arena.len(), 0);
1014 assert!(!arena.contains(id));
1015 }
1016
1017 #[test]
1018 fn test_stale_id_after_remove() {
1019 let mut arena = NodeArena::new();
1020 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1021
1022 arena.remove(id1);
1024 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1025
1026 assert!(arena.get(id1).is_none());
1028 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
1030 assert_eq!(id1.index(), id2.index());
1032 assert_ne!(id1.generation(), id2.generation());
1033 }
1034
1035 #[test]
1036 fn test_remove_idempotent() {
1037 let mut arena = NodeArena::new();
1038 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1039 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1040
1041 assert!(arena.remove(id1).is_some());
1043 assert_eq!(arena.len(), 1);
1044
1045 assert!(arena.remove(id1).is_none());
1047 assert_eq!(arena.len(), 1); assert!(arena.remove(id1).is_none());
1051 assert_eq!(arena.len(), 1); assert!(arena.contains(id2));
1055
1056 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1058 assert_eq!(id3.index(), id1.index()); assert_eq!(arena.len(), 2);
1060 }
1061
1062 #[test]
1063 fn test_free_list_reuse() {
1064 let mut arena = NodeArena::new();
1065
1066 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1068 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1069 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1070
1071 arena.remove(id2);
1073 assert_eq!(arena.len(), 2);
1074 assert_eq!(arena.slot_count(), 3);
1075
1076 let id4 = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1078 assert_eq!(id4.index(), id2.index());
1079 assert_eq!(arena.len(), 3);
1080 assert_eq!(arena.slot_count(), 3); assert!(arena.get(id2).is_none());
1084 assert_eq!(arena.get(id4).unwrap().kind, NodeKind::Variable);
1086 assert!(arena.contains(id1));
1088 assert!(arena.contains(id3));
1089 }
1090
1091 #[test]
1092 fn test_invalid_id() {
1093 let arena = NodeArena::new();
1094 assert!(arena.get(NodeId::INVALID).is_none());
1095 }
1096
1097 #[test]
1098 fn test_out_of_bounds_id() {
1099 let arena = NodeArena::new();
1100 let fake_id = NodeId::new(999, 1);
1101 assert!(arena.get(fake_id).is_none());
1102 }
1103
1104 #[test]
1105 fn test_wrong_generation() {
1106 let mut arena = NodeArena::new();
1107 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1108
1109 let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
1111 assert!(arena.get(wrong_gen_id).is_none());
1112 }
1113
1114 #[test]
1115 fn test_iter() {
1116 let mut arena = NodeArena::new();
1117 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1118 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1119 arena.alloc(test_entry(NodeKind::Method)).unwrap();
1120
1121 let entries: Vec<_> = arena.iter().collect();
1122 assert_eq!(entries.len(), 3);
1123
1124 let kinds: Vec<_> = entries.iter().map(|(_, e)| e.kind).collect();
1125 assert!(kinds.contains(&NodeKind::Function));
1126 assert!(kinds.contains(&NodeKind::Class));
1127 assert!(kinds.contains(&NodeKind::Method));
1128 }
1129
1130 #[test]
1131 fn test_iter_with_removed() {
1132 let mut arena = NodeArena::new();
1133 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1134 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1135 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1136
1137 arena.remove(id2);
1138
1139 let entries: Vec<_> = arena.iter().collect();
1140 assert_eq!(entries.len(), 2);
1141
1142 let collected_ids: Vec<_> = entries.iter().map(|(id, _)| *id).collect();
1143 assert!(collected_ids.contains(&id1));
1144 assert!(!collected_ids.contains(&id2));
1145 assert!(collected_ids.contains(&id3));
1146 }
1147
1148 #[test]
1149 fn test_iter_mut() {
1150 let mut arena = NodeArena::new();
1151 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1152 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1153
1154 for (_, entry) in arena.iter_mut() {
1155 entry.start_line = 100;
1156 }
1157
1158 for (_, entry) in arena.iter() {
1159 assert_eq!(entry.start_line, 100);
1160 }
1161 }
1162
1163 #[test]
1164 fn test_clear() {
1165 let mut arena = NodeArena::new();
1166 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1167 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1168
1169 assert_eq!(arena.len(), 2);
1170 arena.clear();
1171 assert_eq!(arena.len(), 0);
1172 assert!(arena.is_empty());
1173 }
1174
1175 #[test]
1176 fn test_reserve() {
1177 let mut arena = NodeArena::new();
1178 arena.reserve(1000);
1179 assert!(arena.capacity() >= 1000);
1180 }
1181
1182 #[test]
1183 fn test_display() {
1184 let mut arena = NodeArena::new();
1185 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1186
1187 let display = format!("{arena}");
1188 assert!(display.contains("NodeArena"));
1189 assert!(display.contains("len=1"));
1190 }
1191
1192 #[test]
1193 fn test_node_entry_builder() {
1194 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
1195 .with_byte_range(10, 100)
1196 .with_location(1, 0, 5, 10)
1197 .with_signature(StringId::new(2))
1198 .with_doc(StringId::new(3))
1199 .with_qualified_name(StringId::new(4));
1200
1201 assert_eq!(entry.kind, NodeKind::Function);
1202 assert_eq!(entry.start_byte, 10);
1203 assert_eq!(entry.end_byte, 100);
1204 assert_eq!(entry.start_line, 1);
1205 assert_eq!(entry.start_column, 0);
1206 assert_eq!(entry.end_line, 5);
1207 assert_eq!(entry.end_column, 10);
1208 assert_eq!(entry.signature, Some(StringId::new(2)));
1209 assert_eq!(entry.doc, Some(StringId::new(3)));
1210 assert_eq!(entry.qualified_name, Some(StringId::new(4)));
1211 }
1212
1213 #[test]
1214 fn test_slot_state() {
1215 let occupied: Slot<i32> = Slot::new_occupied(1, 42);
1216 assert!(occupied.is_occupied());
1217 assert!(!occupied.is_vacant());
1218 assert_eq!(occupied.get(), Some(&42));
1219
1220 let vacant: Slot<i32> = Slot::new_vacant(2, Some(5));
1221 assert!(!vacant.is_occupied());
1222 assert!(vacant.is_vacant());
1223 assert_eq!(vacant.get(), None);
1224 }
1225
1226 #[test]
1227 fn test_slot_generation() {
1228 let slot: Slot<i32> = Slot::new_occupied(42, 100);
1229 assert_eq!(slot.generation(), 42);
1230 }
1231
1232 #[test]
1233 fn test_slot_state_accessor() {
1234 let occupied: Slot<i32> = Slot::new_occupied(1, 42);
1235 assert!(matches!(occupied.state(), SlotState::Occupied(42)));
1236
1237 let vacant: Slot<i32> = Slot::new_vacant(1, Some(3));
1238 assert!(matches!(
1239 vacant.state(),
1240 SlotState::Vacant { next_free: Some(3) }
1241 ));
1242 }
1243
1244 #[test]
1247 fn test_alloc_range_basic() {
1248 let mut arena = NodeArena::new();
1249 let placeholder = test_entry(NodeKind::Function);
1250
1251 let start = arena.alloc_range(5, &placeholder).unwrap();
1252 assert_eq!(start, 0);
1253 assert_eq!(arena.len(), 5);
1254 assert_eq!(arena.slot_count(), 5);
1255
1256 for i in 0..5u32 {
1258 let id = NodeId::new(i, 1);
1259 let entry = arena.get(id).expect("slot should be occupied");
1260 assert_eq!(entry.kind, NodeKind::Function);
1261 }
1262 }
1263
1264 #[test]
1265 fn test_alloc_range_after_existing() {
1266 let mut arena = NodeArena::new();
1267
1268 let id0 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1270 assert_eq!(id0.index(), 0);
1271 assert_eq!(arena.len(), 1);
1272
1273 let placeholder = test_entry(NodeKind::Method);
1275 let start = arena.alloc_range(3, &placeholder).unwrap();
1276 assert_eq!(start, 1);
1277 assert_eq!(arena.len(), 4);
1278 assert_eq!(arena.slot_count(), 4);
1279
1280 assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Class);
1282
1283 for i in 1..4u32 {
1285 let id = NodeId::new(i, 1);
1286 assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
1287 }
1288 }
1289
1290 #[test]
1291 fn test_alloc_range_zero_is_noop() {
1292 let mut arena = NodeArena::new();
1293
1294 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1296 let len_before = arena.len();
1297 let slot_count_before = arena.slot_count();
1298
1299 let start = arena.alloc_range(0, &test_entry(NodeKind::Class)).unwrap();
1300 #[allow(clippy::cast_possible_truncation)]
1301 let expected_start = slot_count_before as u32;
1303 assert_eq!(start, expected_start);
1304 assert_eq!(arena.len(), len_before);
1305 assert_eq!(arena.slot_count(), slot_count_before);
1306 }
1307
1308 #[test]
1309 fn test_bulk_slice_mut() {
1310 let mut arena = NodeArena::new();
1311 let placeholder = test_entry(NodeKind::Function);
1312 let start = arena.alloc_range(3, &placeholder).unwrap();
1313
1314 {
1316 let slice = arena.bulk_slice_mut(start, 3);
1317 assert_eq!(slice.len(), 3);
1318
1319 slice[1] = Slot::new_occupied(1, test_entry(NodeKind::Class));
1321 }
1322
1323 let id1 = NodeId::new(1, 1);
1325 assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Class);
1326
1327 let id0 = NodeId::new(0, 1);
1329 assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Function);
1330 let id2 = NodeId::new(2, 1);
1331 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Function);
1332 }
1333
1334 #[test]
1335 fn test_truncate_to() {
1336 let mut arena = NodeArena::new();
1337 let placeholder = test_entry(NodeKind::Function);
1338
1339 arena.alloc_range(3, &placeholder).unwrap();
1341 assert_eq!(arena.len(), 3);
1342 assert_eq!(arena.slot_count(), 3);
1343
1344 let saved = arena.slot_count();
1346
1347 arena.alloc_range(4, &placeholder).unwrap();
1349 assert_eq!(arena.len(), 7);
1350 assert_eq!(arena.slot_count(), 7);
1351
1352 arena.truncate_to(saved);
1354 assert_eq!(arena.len(), 3);
1355 assert_eq!(arena.slot_count(), 3);
1356
1357 for i in 0..3u32 {
1359 let id = NodeId::new(i, 1);
1360 assert!(arena.get(id).is_some());
1361 }
1362 }
1363
1364 #[test]
1365 fn test_alloc_range_with_free_list() {
1366 let mut arena = NodeArena::new();
1367
1368 let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1370 let _id1 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1371 arena.remove(id0);
1372
1373 assert_eq!(arena.len(), 1);
1375 assert_eq!(arena.slot_count(), 2);
1376
1377 let start = arena.alloc_range(3, &test_entry(NodeKind::Method)).unwrap();
1379 assert_eq!(start, 2); assert_eq!(arena.len(), 4); assert_eq!(arena.slot_count(), 5); let slot0 = arena.slot(0).unwrap();
1385 assert!(slot0.is_vacant());
1386
1387 for i in 2..5u32 {
1389 let id = NodeId::new(i, 1);
1390 assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
1391 }
1392 }
1393
1394 #[test]
1395 fn test_truncate_to_clamps_free_head() {
1396 let mut arena = NodeArena::new();
1397
1398 let mut ids = Vec::new();
1400 for _ in 0..5 {
1401 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1402 }
1403
1404 let saved = arena.slot_count();
1406
1407 let extra_ids: Vec<_> = (0..3)
1409 .map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
1410 .collect();
1411 arena.remove(extra_ids[1]); arena.truncate_to(saved);
1415 assert_eq!(arena.slot_count(), 5);
1416 assert_eq!(arena.len(), 5);
1417
1418 let new_id = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1420 assert_eq!(new_id.index(), 5); assert_eq!(arena.get(new_id).unwrap().kind, NodeKind::Variable);
1422 }
1423
1424 #[test]
1425 fn test_truncate_to_preserves_retained_free_list() {
1426 let mut arena = NodeArena::new();
1427
1428 let mut ids = Vec::new();
1430 for _ in 0..8 {
1431 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1432 }
1433 assert_eq!(arena.len(), 8);
1434
1435 arena.remove(ids[2]);
1438 arena.remove(ids[6]);
1439 assert_eq!(arena.len(), 6);
1440
1441 arena.truncate_to(5);
1445
1446 assert_eq!(arena.slot_count(), 5);
1450 assert_eq!(arena.len(), 4);
1451
1452 let reused = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1455 assert_eq!(reused.index(), 2);
1456 assert_eq!(arena.get(reused).unwrap().kind, NodeKind::Variable);
1457 assert_eq!(arena.len(), 5);
1458
1459 let appended = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1461 assert_eq!(appended.index(), 5);
1462 assert_eq!(arena.len(), 6);
1463 }
1464
1465 #[test]
1466 fn test_slots_read_access() {
1467 let mut arena = NodeArena::new();
1468 let placeholder = test_entry(NodeKind::Variable);
1469 arena.alloc_range(4, &placeholder).unwrap();
1470
1471 let slots = arena.slots();
1472 assert_eq!(slots.len(), 4);
1473
1474 for slot in slots {
1475 assert!(slot.is_occupied());
1476 assert_eq!(slot.generation(), 1);
1477 assert_eq!(slot.get().unwrap().kind, NodeKind::Variable);
1478 }
1479 }
1480
1481 #[test]
1482 fn test_multiple_remove_reuse_cycle() {
1483 let mut arena = NodeArena::new();
1484
1485 let mut ids = Vec::new();
1487 for _ in 0..5 {
1488 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1489 }
1490
1491 for &id in ids.iter().rev() {
1493 arena.remove(id);
1494 }
1495
1496 let new_ids: Vec<_> = (0..5)
1498 .map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
1499 .collect();
1500
1501 for &old_id in &ids {
1503 assert!(arena.get(old_id).is_none());
1504 }
1505
1506 for &new_id in &new_ids {
1508 assert!(arena.get(new_id).is_some());
1509 }
1510 }
1511
1512 #[test]
1513 fn test_generation_overflow_at_max_generation() {
1514 let mut arena = NodeArena::new();
1516
1517 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1519 assert_eq!(id.index(), 0);
1520
1521 arena.remove(id);
1523
1524 arena.slots[0].generation = NodeId::MAX_GENERATION;
1527
1528 let result = arena.alloc(test_entry(NodeKind::Class));
1530 assert!(result.is_err());
1531
1532 let err = result.unwrap_err();
1533 match err {
1534 ArenaError::GenerationOverflow(inner) => {
1535 assert_eq!(inner.index, 0);
1536 assert_eq!(inner.generation, NodeId::MAX_GENERATION);
1537 }
1538 _ => panic!("expected GenerationOverflow, got {err:?}"),
1539 }
1540 }
1541
1542 #[test]
1543 fn test_free_list_corruption_returns_error() {
1544 let mut arena = NodeArena::new();
1546
1547 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1549 arena.remove(id);
1550
1551 arena.slots[0].state = SlotState::Occupied(test_entry(NodeKind::Class));
1553
1554 let result = arena.alloc(test_entry(NodeKind::Method));
1556 assert!(result.is_err());
1557
1558 let err = result.unwrap_err();
1559 match err {
1560 ArenaError::FreeListCorruption { index } => {
1561 assert_eq!(index, 0);
1562 }
1563 _ => panic!("expected FreeListCorruption, got {err:?}"),
1564 }
1565
1566 assert_eq!(arena.len(), 0);
1568 }
1569
1570 #[test]
1571 fn test_arena_error_display() {
1572 let gen_err = ArenaError::GenerationOverflow(GenerationOverflowError {
1573 index: 42,
1574 generation: 100,
1575 });
1576 let display = format!("{gen_err}");
1577 assert!(display.contains("42"));
1578 assert!(display.contains("100"));
1579
1580 let corruption_err = ArenaError::FreeListCorruption { index: 5 };
1581 let display = format!("{corruption_err}");
1582 assert!(display.contains("free list corruption"));
1583 assert!(display.contains('5'));
1584
1585 let capacity_err = ArenaError::CapacityExceeded;
1586 let display = format!("{capacity_err}");
1587 assert!(display.contains("capacity exceeded"));
1588 }
1589
1590 #[test]
1591 fn test_arena_error_source_generation_overflow() {
1592 use std::error::Error;
1593
1594 let inner = GenerationOverflowError {
1595 index: 0,
1596 generation: NodeId::MAX_GENERATION,
1597 };
1598 let err = ArenaError::GenerationOverflow(inner);
1599 assert!(err.source().is_some());
1601 }
1602
1603 #[test]
1604 fn test_arena_error_source_none_for_other_variants() {
1605 use std::error::Error;
1606
1607 let corruption = ArenaError::FreeListCorruption { index: 0 };
1608 assert!(corruption.source().is_none());
1609
1610 let capacity = ArenaError::CapacityExceeded;
1611 assert!(capacity.source().is_none());
1612 }
1613
1614 #[test]
1615 fn test_arena_from_generation_overflow_error() {
1616 let inner = GenerationOverflowError {
1617 index: 10,
1618 generation: 99,
1619 };
1620 let err: ArenaError = inner.into();
1621 assert!(matches!(err, ArenaError::GenerationOverflow(_)));
1622 }
1623
1624 #[test]
1625 fn test_arena_error_clone_and_eq() {
1626 let err = ArenaError::CapacityExceeded;
1627 let cloned = err.clone();
1628 assert_eq!(err, cloned);
1629
1630 let err2 = ArenaError::FreeListCorruption { index: 3 };
1631 let cloned2 = err2.clone();
1632 assert_eq!(err2, cloned2);
1633 }
1634
1635 #[test]
1636 fn test_node_entry_with_visibility() {
1637 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
1638 .with_visibility(StringId::new(5));
1639
1640 assert_eq!(entry.visibility, Some(StringId::new(5)));
1641 }
1642
1643 #[test]
1644 fn test_node_entry_with_async_and_static() {
1645 let entry = NodeEntry::new(NodeKind::Method, test_name(), test_file())
1646 .with_async(true)
1647 .with_static(true);
1648
1649 assert!(entry.is_async);
1650 assert!(entry.is_static);
1651 }
1652
1653 #[test]
1654 fn test_node_entry_with_unsafe_flag() {
1655 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_unsafe(true);
1656
1657 assert!(entry.is_unsafe);
1658 }
1659
1660 #[test]
1661 fn test_node_entry_with_body_hash() {
1662 use crate::graph::body_hash::BodyHash128;
1663 let hash = BodyHash128::from_u128(0u128);
1664 let entry =
1665 NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_body_hash(hash);
1666
1667 assert!(entry.body_hash.is_some());
1668 }
1669
1670 #[test]
1671 fn test_node_entry_defaults() {
1672 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file());
1673 assert_eq!(entry.start_byte, 0);
1674 assert_eq!(entry.end_byte, 0);
1675 assert_eq!(entry.start_line, 0);
1676 assert_eq!(entry.start_column, 0);
1677 assert_eq!(entry.end_line, 0);
1678 assert_eq!(entry.end_column, 0);
1679 assert!(entry.signature.is_none());
1680 assert!(entry.doc.is_none());
1681 assert!(entry.qualified_name.is_none());
1682 assert!(entry.visibility.is_none());
1683 assert!(!entry.is_async);
1684 assert!(!entry.is_static);
1685 assert!(!entry.is_unsafe);
1686 assert!(entry.body_hash.is_none());
1687 }
1688
1689 #[test]
1690 fn test_arena_default_impl() {
1691 let arena = NodeArena::default();
1692 assert_eq!(arena.len(), 0);
1693 assert!(arena.is_empty());
1694 }
1695
1696 #[test]
1697 fn test_get_invalid_id() {
1698 let mut arena = NodeArena::new();
1699 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1700 assert!(arena.get(NodeId::INVALID).is_none());
1702 }
1703
1704 #[test]
1705 fn test_get_mut_invalid_id() {
1706 let mut arena = NodeArena::new();
1707 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1708 assert!(arena.get_mut(NodeId::INVALID).is_none());
1709 }
1710
1711 #[test]
1712 fn test_get_mut_wrong_generation() {
1713 let mut arena = NodeArena::new();
1714 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1715 let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
1716 assert!(arena.get_mut(wrong_gen_id).is_none());
1717 }
1718
1719 #[test]
1720 fn test_remove_invalid_id() {
1721 let mut arena = NodeArena::new();
1722 assert!(arena.remove(NodeId::INVALID).is_none());
1723 }
1724
1725 #[test]
1726 fn test_slot_get_mut_occupied() {
1727 let mut slot: Slot<i32> = Slot::new_occupied(1, 42);
1728 let val = slot.get_mut().unwrap();
1729 *val = 99;
1730 assert_eq!(slot.get(), Some(&99));
1731 }
1732
1733 #[test]
1734 fn test_slot_get_mut_vacant() {
1735 let mut slot: Slot<i32> = Slot::new_vacant(1, None);
1736 assert!(slot.get_mut().is_none());
1737 }
1738
1739 #[test]
1740 fn test_truncate_to_zero_occupied_dropped() {
1741 let mut arena = NodeArena::new();
1742 let placeholder = test_entry(NodeKind::Function);
1743 arena.alloc_range(5, &placeholder).unwrap();
1744 arena.truncate_to(0);
1746 assert_eq!(arena.len(), 0);
1747 assert_eq!(arena.slot_count(), 0);
1748 }
1749
1750 #[test]
1751 fn test_slot_count_vs_len() {
1752 let mut arena = NodeArena::new();
1753 let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1754 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1755 arena.remove(id0);
1756 assert_eq!(arena.slot_count(), 2);
1758 assert_eq!(arena.len(), 1);
1759 }
1760}