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)] 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 #[inline]
460 #[must_use]
461 pub fn len(&self) -> usize {
462 self.len
463 }
464
465 #[inline]
467 #[must_use]
468 pub fn is_empty(&self) -> bool {
469 self.len == 0
470 }
471
472 #[inline]
474 #[must_use]
475 pub fn capacity(&self) -> usize {
476 self.slots.capacity()
477 }
478
479 #[inline]
484 #[must_use]
485 pub fn slot_count(&self) -> usize {
486 self.slots.len()
487 }
488
489 pub fn alloc(&mut self, entry: NodeEntry) -> Result<NodeId, ArenaError> {
501 self.len += 1;
502
503 if let Some(free_idx) = self.free_head {
504 let slot_index = free_idx as usize;
506 if slot_index >= self.slots.len() {
507 self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
509 }
510
511 let slot = &mut self.slots[slot_index];
513
514 self.free_head = match &slot.state {
516 SlotState::Vacant { next_free } => *next_free,
517 SlotState::Occupied(_) => {
518 self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
521 }
522 };
523
524 let temp_id = NodeId::new(free_idx, slot.generation);
527 let new_generation = temp_id.try_increment_generation().map_err(|mut e| {
528 self.len -= 1; e.index = free_idx; ArenaError::GenerationOverflow(e)
531 })?;
532
533 slot.generation = new_generation;
534 slot.state = SlotState::Occupied(entry);
535
536 Ok(NodeId::new(free_idx, new_generation))
537 } else {
538 let index = self.slots.len();
540
541 let Ok(index_u32) = u32::try_from(index) else {
543 self.len -= 1; return Err(ArenaError::CapacityExceeded);
545 };
546
547 let slot = Slot::new_occupied(1, entry);
548 self.slots.push(slot);
549
550 Ok(NodeId::new(index_u32, 1))
551 }
552 }
553
554 #[must_use]
558 pub fn get(&self, id: NodeId) -> Option<&NodeEntry> {
559 if id.is_invalid() {
560 return None;
561 }
562
563 let index = id.index() as usize;
564 let slot = self.slots.get(index)?;
565
566 if slot.generation != id.generation() {
568 return None;
569 }
570
571 slot.get()
572 }
573
574 #[must_use]
578 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut NodeEntry> {
579 if id.is_invalid() {
580 return None;
581 }
582
583 let index = id.index() as usize;
584 let slot = self.slots.get_mut(index)?;
585
586 if slot.generation != id.generation() {
588 return None;
589 }
590
591 slot.get_mut()
592 }
593
594 #[must_use]
596 pub fn contains(&self, id: NodeId) -> bool {
597 self.get(id).is_some()
598 }
599
600 pub fn remove(&mut self, id: NodeId) -> Option<NodeEntry> {
606 if id.is_invalid() {
607 return None;
608 }
609
610 let index = id.index() as usize;
611 let slot = self.slots.get_mut(index)?;
612
613 if slot.generation != id.generation() {
615 return None;
616 }
617
618 if let SlotState::Occupied(_) = &slot.state {
621 let old_state = std::mem::replace(
622 &mut slot.state,
623 SlotState::Vacant {
624 next_free: self.free_head,
625 },
626 );
627
628 self.free_head = Some(id.index());
630 self.len -= 1;
631
632 if let SlotState::Occupied(entry) = old_state {
633 return Some(entry);
634 }
635 }
636
637 None
638 }
639
640 pub fn iter(&self) -> impl Iterator<Item = (NodeId, &NodeEntry)> {
642 self.slots.iter().enumerate().filter_map(|(index, slot)| {
643 if let SlotState::Occupied(entry) = &slot.state {
644 let index_u32 = u32::try_from(index).ok()?;
645 Some((NodeId::new(index_u32, slot.generation), entry))
646 } else {
647 None
648 }
649 })
650 }
651
652 pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut NodeEntry)> {
654 self.slots
655 .iter_mut()
656 .enumerate()
657 .filter_map(|(index, slot)| {
658 let generation = slot.generation;
659 if let SlotState::Occupied(entry) = &mut slot.state {
660 let index_u32 = u32::try_from(index).ok()?;
661 Some((NodeId::new(index_u32, generation), entry))
662 } else {
663 None
664 }
665 })
666 }
667
668 pub fn clear(&mut self) {
672 self.slots.clear();
673 self.free_head = None;
674 self.len = 0;
675 }
676
677 pub fn reserve(&mut self, additional: usize) {
679 self.slots.reserve(additional);
680 }
681
682 #[must_use]
686 pub fn slot(&self, index: u32) -> Option<&Slot<NodeEntry>> {
687 self.slots.get(index as usize)
688 }
689
690 pub fn alloc_range(&mut self, count: u32, placeholder: &NodeEntry) -> Result<u32, ArenaError> {
704 if count == 0 {
705 return Ok(u32::try_from(self.slots.len())
708 .unwrap_or_else(|_| unreachable!("slot_count invariant violated")));
709 }
710
711 let start = self.slots.len();
712 let new_total = start
713 .checked_add(count as usize)
714 .ok_or(ArenaError::CapacityExceeded)?;
715
716 if new_total > u32::MAX as usize + 1 {
718 return Err(ArenaError::CapacityExceeded);
719 }
720
721 let start_u32 = u32::try_from(start).map_err(|_| ArenaError::CapacityExceeded)?;
722
723 self.slots.reserve(count as usize);
725 for _ in 0..count {
726 self.slots.push(Slot::new_occupied(1, placeholder.clone()));
727 }
728
729 self.len += count as usize;
731
732 Ok(start_u32)
733 }
734
735 #[must_use]
744 pub fn bulk_slice_mut(&mut self, start: u32, count: u32) -> &mut [Slot<NodeEntry>] {
745 let begin = start as usize;
746 let end = begin + count as usize;
747 &mut self.slots[begin..end]
748 }
749
750 pub fn truncate_to(&mut self, saved_slot_count: usize) {
761 assert!(
762 saved_slot_count <= self.slots.len(),
763 "truncate_to({saved_slot_count}) exceeds current slot count ({})",
764 self.slots.len(),
765 );
766
767 let dropped_occupied = self.slots[saved_slot_count..]
769 .iter()
770 .filter(|s| s.is_occupied())
771 .count();
772
773 self.slots.truncate(saved_slot_count);
774 self.len -= dropped_occupied;
775
776 self.rebuild_free_list_after_truncate(saved_slot_count);
779 }
780
781 fn rebuild_free_list_after_truncate(&mut self, cutoff: usize) {
787 let mut new_head: Option<u32> = None;
791 for i in (0..cutoff).rev() {
794 if self.slots[i].is_vacant() {
795 let i_u32 = u32::try_from(i)
796 .unwrap_or_else(|_| unreachable!("free-list index exceeds u32 invariant"));
797 self.slots[i].state = SlotState::Vacant {
798 next_free: new_head,
799 };
800 new_head = Some(i_u32);
801 }
802 }
803 self.free_head = new_head;
804 }
805
806 #[must_use]
811 pub fn slots(&self) -> &[Slot<NodeEntry>] {
812 &self.slots
813 }
814}
815
816impl Default for NodeArena {
817 fn default() -> Self {
818 Self::new()
819 }
820}
821
822impl fmt::Display for NodeArena {
823 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
824 write!(
825 f,
826 "NodeArena(len={}, capacity={})",
827 self.len,
828 self.capacity()
829 )
830 }
831}
832
833impl crate::graph::unified::memory::GraphMemorySize for NodeArena {
834 fn heap_bytes(&self) -> usize {
835 self.slots.capacity() * std::mem::size_of::<Slot<NodeEntry>>()
836 }
837}
838
839fn is_synthetic_placeholder_name_shape(name: &str) -> bool {
853 if name.starts_with('<') {
854 return true;
855 }
856 if let Some((_, rest)) = name.rsplit_once('@')
858 && !rest.is_empty()
859 && rest.bytes().all(|b| b.is_ascii_digit())
860 {
861 return true;
862 }
863 false
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869
870 fn test_file() -> FileId {
871 FileId::new(1)
872 }
873
874 fn test_name() -> StringId {
875 StringId::new(1)
876 }
877
878 fn test_entry(kind: NodeKind) -> NodeEntry {
879 NodeEntry::new(kind, test_name(), test_file())
880 }
881
882 #[test]
883 fn test_arena_new() {
884 let arena = NodeArena::new();
885 assert_eq!(arena.len(), 0);
886 assert!(arena.is_empty());
887 assert_eq!(arena.capacity(), 0);
888 }
889
890 #[test]
891 fn synthetic_placeholder_name_shape_recognises_known_synthetics() {
892 assert!(is_synthetic_placeholder_name_shape("<field:s.NeedTags>"));
894 assert!(is_synthetic_placeholder_name_shape(
895 "<field:selector.NeedTags>"
896 ));
897 assert!(is_synthetic_placeholder_name_shape("<type:main.Foo>"));
899 assert!(is_synthetic_placeholder_name_shape("NeedTags@469"));
901 assert!(is_synthetic_placeholder_name_shape("NeedTags@508"));
902 assert!(is_synthetic_placeholder_name_shape("foo@0"));
903 assert!(NodeEntry::is_synthetic_placeholder_name("<field:x.y>"));
905 assert!(NodeEntry::is_synthetic_placeholder_name("x@123"));
906 }
907
908 #[test]
909 fn synthetic_placeholder_name_shape_passes_real_identifiers() {
910 assert!(!is_synthetic_placeholder_name_shape("NeedTags"));
912 assert!(!is_synthetic_placeholder_name_shape(
913 "main.SelectorSource.NeedTags"
914 ));
915 assert!(!is_synthetic_placeholder_name_shape("main.useSelector"));
916 assert!(!is_synthetic_placeholder_name_shape("foo"));
917 assert!(!is_synthetic_placeholder_name_shape(""));
918 assert!(!is_synthetic_placeholder_name_shape("foo@bar"));
920 assert!(!is_synthetic_placeholder_name_shape("foo@"));
922 }
923
924 #[test]
925 fn test_arena_with_capacity() {
926 let arena = NodeArena::with_capacity(100);
927 assert_eq!(arena.len(), 0);
928 assert!(arena.capacity() >= 100);
929 }
930
931 #[test]
932 fn test_alloc_and_get() {
933 let mut arena = NodeArena::new();
934 let entry = test_entry(NodeKind::Function);
935
936 let id = arena.alloc(entry).unwrap();
937 assert!(!id.is_invalid());
938 assert_eq!(arena.len(), 1);
939
940 let node = arena.get(id).unwrap();
941 assert_eq!(node.kind, NodeKind::Function);
942 }
943
944 #[test]
945 fn test_alloc_multiple() {
946 let mut arena = NodeArena::new();
947
948 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
949 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
950 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
951
952 assert_eq!(arena.len(), 3);
953 assert_ne!(id1, id2);
954 assert_ne!(id2, id3);
955
956 assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Function);
957 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
958 assert_eq!(arena.get(id3).unwrap().kind, NodeKind::Method);
959 }
960
961 #[test]
962 fn test_get_mut() {
963 let mut arena = NodeArena::new();
964 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
965
966 {
967 let node = arena.get_mut(id).unwrap();
968 node.start_line = 42;
969 }
970
971 assert_eq!(arena.get(id).unwrap().start_line, 42);
972 }
973
974 #[test]
975 fn test_contains() {
976 let mut arena = NodeArena::new();
977 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
978
979 assert!(arena.contains(id));
980 assert!(!arena.contains(NodeId::INVALID));
981 }
982
983 #[test]
984 fn test_remove() {
985 let mut arena = NodeArena::new();
986 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
987
988 assert_eq!(arena.len(), 1);
989 assert!(arena.contains(id));
990
991 let removed = arena.remove(id).unwrap();
992 assert_eq!(removed.kind, NodeKind::Function);
993 assert_eq!(arena.len(), 0);
994 assert!(!arena.contains(id));
995 }
996
997 #[test]
998 fn test_stale_id_after_remove() {
999 let mut arena = NodeArena::new();
1000 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1001
1002 arena.remove(id1);
1004 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1005
1006 assert!(arena.get(id1).is_none());
1008 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
1010 assert_eq!(id1.index(), id2.index());
1012 assert_ne!(id1.generation(), id2.generation());
1013 }
1014
1015 #[test]
1016 fn test_remove_idempotent() {
1017 let mut arena = NodeArena::new();
1018 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1019 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1020
1021 assert!(arena.remove(id1).is_some());
1023 assert_eq!(arena.len(), 1);
1024
1025 assert!(arena.remove(id1).is_none());
1027 assert_eq!(arena.len(), 1); assert!(arena.remove(id1).is_none());
1031 assert_eq!(arena.len(), 1); assert!(arena.contains(id2));
1035
1036 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1038 assert_eq!(id3.index(), id1.index()); assert_eq!(arena.len(), 2);
1040 }
1041
1042 #[test]
1043 fn test_free_list_reuse() {
1044 let mut arena = NodeArena::new();
1045
1046 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1048 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1049 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1050
1051 arena.remove(id2);
1053 assert_eq!(arena.len(), 2);
1054 assert_eq!(arena.slot_count(), 3);
1055
1056 let id4 = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1058 assert_eq!(id4.index(), id2.index());
1059 assert_eq!(arena.len(), 3);
1060 assert_eq!(arena.slot_count(), 3); assert!(arena.get(id2).is_none());
1064 assert_eq!(arena.get(id4).unwrap().kind, NodeKind::Variable);
1066 assert!(arena.contains(id1));
1068 assert!(arena.contains(id3));
1069 }
1070
1071 #[test]
1072 fn test_invalid_id() {
1073 let arena = NodeArena::new();
1074 assert!(arena.get(NodeId::INVALID).is_none());
1075 }
1076
1077 #[test]
1078 fn test_out_of_bounds_id() {
1079 let arena = NodeArena::new();
1080 let fake_id = NodeId::new(999, 1);
1081 assert!(arena.get(fake_id).is_none());
1082 }
1083
1084 #[test]
1085 fn test_wrong_generation() {
1086 let mut arena = NodeArena::new();
1087 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1088
1089 let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
1091 assert!(arena.get(wrong_gen_id).is_none());
1092 }
1093
1094 #[test]
1095 fn test_iter() {
1096 let mut arena = NodeArena::new();
1097 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1098 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1099 arena.alloc(test_entry(NodeKind::Method)).unwrap();
1100
1101 let entries: Vec<_> = arena.iter().collect();
1102 assert_eq!(entries.len(), 3);
1103
1104 let kinds: Vec<_> = entries.iter().map(|(_, e)| e.kind).collect();
1105 assert!(kinds.contains(&NodeKind::Function));
1106 assert!(kinds.contains(&NodeKind::Class));
1107 assert!(kinds.contains(&NodeKind::Method));
1108 }
1109
1110 #[test]
1111 fn test_iter_with_removed() {
1112 let mut arena = NodeArena::new();
1113 let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1114 let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1115 let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
1116
1117 arena.remove(id2);
1118
1119 let entries: Vec<_> = arena.iter().collect();
1120 assert_eq!(entries.len(), 2);
1121
1122 let collected_ids: Vec<_> = entries.iter().map(|(id, _)| *id).collect();
1123 assert!(collected_ids.contains(&id1));
1124 assert!(!collected_ids.contains(&id2));
1125 assert!(collected_ids.contains(&id3));
1126 }
1127
1128 #[test]
1129 fn test_iter_mut() {
1130 let mut arena = NodeArena::new();
1131 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1132 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1133
1134 for (_, entry) in arena.iter_mut() {
1135 entry.start_line = 100;
1136 }
1137
1138 for (_, entry) in arena.iter() {
1139 assert_eq!(entry.start_line, 100);
1140 }
1141 }
1142
1143 #[test]
1144 fn test_clear() {
1145 let mut arena = NodeArena::new();
1146 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1147 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1148
1149 assert_eq!(arena.len(), 2);
1150 arena.clear();
1151 assert_eq!(arena.len(), 0);
1152 assert!(arena.is_empty());
1153 }
1154
1155 #[test]
1156 fn test_reserve() {
1157 let mut arena = NodeArena::new();
1158 arena.reserve(1000);
1159 assert!(arena.capacity() >= 1000);
1160 }
1161
1162 #[test]
1163 fn test_display() {
1164 let mut arena = NodeArena::new();
1165 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1166
1167 let display = format!("{arena}");
1168 assert!(display.contains("NodeArena"));
1169 assert!(display.contains("len=1"));
1170 }
1171
1172 #[test]
1173 fn test_node_entry_builder() {
1174 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
1175 .with_byte_range(10, 100)
1176 .with_location(1, 0, 5, 10)
1177 .with_signature(StringId::new(2))
1178 .with_doc(StringId::new(3))
1179 .with_qualified_name(StringId::new(4));
1180
1181 assert_eq!(entry.kind, NodeKind::Function);
1182 assert_eq!(entry.start_byte, 10);
1183 assert_eq!(entry.end_byte, 100);
1184 assert_eq!(entry.start_line, 1);
1185 assert_eq!(entry.start_column, 0);
1186 assert_eq!(entry.end_line, 5);
1187 assert_eq!(entry.end_column, 10);
1188 assert_eq!(entry.signature, Some(StringId::new(2)));
1189 assert_eq!(entry.doc, Some(StringId::new(3)));
1190 assert_eq!(entry.qualified_name, Some(StringId::new(4)));
1191 }
1192
1193 #[test]
1194 fn test_slot_state() {
1195 let occupied: Slot<i32> = Slot::new_occupied(1, 42);
1196 assert!(occupied.is_occupied());
1197 assert!(!occupied.is_vacant());
1198 assert_eq!(occupied.get(), Some(&42));
1199
1200 let vacant: Slot<i32> = Slot::new_vacant(2, Some(5));
1201 assert!(!vacant.is_occupied());
1202 assert!(vacant.is_vacant());
1203 assert_eq!(vacant.get(), None);
1204 }
1205
1206 #[test]
1207 fn test_slot_generation() {
1208 let slot: Slot<i32> = Slot::new_occupied(42, 100);
1209 assert_eq!(slot.generation(), 42);
1210 }
1211
1212 #[test]
1213 fn test_slot_state_accessor() {
1214 let occupied: Slot<i32> = Slot::new_occupied(1, 42);
1215 assert!(matches!(occupied.state(), SlotState::Occupied(42)));
1216
1217 let vacant: Slot<i32> = Slot::new_vacant(1, Some(3));
1218 assert!(matches!(
1219 vacant.state(),
1220 SlotState::Vacant { next_free: Some(3) }
1221 ));
1222 }
1223
1224 #[test]
1227 fn test_alloc_range_basic() {
1228 let mut arena = NodeArena::new();
1229 let placeholder = test_entry(NodeKind::Function);
1230
1231 let start = arena.alloc_range(5, &placeholder).unwrap();
1232 assert_eq!(start, 0);
1233 assert_eq!(arena.len(), 5);
1234 assert_eq!(arena.slot_count(), 5);
1235
1236 for i in 0..5u32 {
1238 let id = NodeId::new(i, 1);
1239 let entry = arena.get(id).expect("slot should be occupied");
1240 assert_eq!(entry.kind, NodeKind::Function);
1241 }
1242 }
1243
1244 #[test]
1245 fn test_alloc_range_after_existing() {
1246 let mut arena = NodeArena::new();
1247
1248 let id0 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1250 assert_eq!(id0.index(), 0);
1251 assert_eq!(arena.len(), 1);
1252
1253 let placeholder = test_entry(NodeKind::Method);
1255 let start = arena.alloc_range(3, &placeholder).unwrap();
1256 assert_eq!(start, 1);
1257 assert_eq!(arena.len(), 4);
1258 assert_eq!(arena.slot_count(), 4);
1259
1260 assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Class);
1262
1263 for i in 1..4u32 {
1265 let id = NodeId::new(i, 1);
1266 assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
1267 }
1268 }
1269
1270 #[test]
1271 fn test_alloc_range_zero_is_noop() {
1272 let mut arena = NodeArena::new();
1273
1274 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1276 let len_before = arena.len();
1277 let slot_count_before = arena.slot_count();
1278
1279 let start = arena.alloc_range(0, &test_entry(NodeKind::Class)).unwrap();
1280 #[allow(clippy::cast_possible_truncation)]
1281 let expected_start = slot_count_before as u32;
1283 assert_eq!(start, expected_start);
1284 assert_eq!(arena.len(), len_before);
1285 assert_eq!(arena.slot_count(), slot_count_before);
1286 }
1287
1288 #[test]
1289 fn test_bulk_slice_mut() {
1290 let mut arena = NodeArena::new();
1291 let placeholder = test_entry(NodeKind::Function);
1292 let start = arena.alloc_range(3, &placeholder).unwrap();
1293
1294 {
1296 let slice = arena.bulk_slice_mut(start, 3);
1297 assert_eq!(slice.len(), 3);
1298
1299 slice[1] = Slot::new_occupied(1, test_entry(NodeKind::Class));
1301 }
1302
1303 let id1 = NodeId::new(1, 1);
1305 assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Class);
1306
1307 let id0 = NodeId::new(0, 1);
1309 assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Function);
1310 let id2 = NodeId::new(2, 1);
1311 assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Function);
1312 }
1313
1314 #[test]
1315 fn test_truncate_to() {
1316 let mut arena = NodeArena::new();
1317 let placeholder = test_entry(NodeKind::Function);
1318
1319 arena.alloc_range(3, &placeholder).unwrap();
1321 assert_eq!(arena.len(), 3);
1322 assert_eq!(arena.slot_count(), 3);
1323
1324 let saved = arena.slot_count();
1326
1327 arena.alloc_range(4, &placeholder).unwrap();
1329 assert_eq!(arena.len(), 7);
1330 assert_eq!(arena.slot_count(), 7);
1331
1332 arena.truncate_to(saved);
1334 assert_eq!(arena.len(), 3);
1335 assert_eq!(arena.slot_count(), 3);
1336
1337 for i in 0..3u32 {
1339 let id = NodeId::new(i, 1);
1340 assert!(arena.get(id).is_some());
1341 }
1342 }
1343
1344 #[test]
1345 fn test_alloc_range_with_free_list() {
1346 let mut arena = NodeArena::new();
1347
1348 let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1350 let _id1 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1351 arena.remove(id0);
1352
1353 assert_eq!(arena.len(), 1);
1355 assert_eq!(arena.slot_count(), 2);
1356
1357 let start = arena.alloc_range(3, &test_entry(NodeKind::Method)).unwrap();
1359 assert_eq!(start, 2); assert_eq!(arena.len(), 4); assert_eq!(arena.slot_count(), 5); let slot0 = arena.slot(0).unwrap();
1365 assert!(slot0.is_vacant());
1366
1367 for i in 2..5u32 {
1369 let id = NodeId::new(i, 1);
1370 assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
1371 }
1372 }
1373
1374 #[test]
1375 fn test_truncate_to_clamps_free_head() {
1376 let mut arena = NodeArena::new();
1377
1378 let mut ids = Vec::new();
1380 for _ in 0..5 {
1381 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1382 }
1383
1384 let saved = arena.slot_count();
1386
1387 let extra_ids: Vec<_> = (0..3)
1389 .map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
1390 .collect();
1391 arena.remove(extra_ids[1]); arena.truncate_to(saved);
1395 assert_eq!(arena.slot_count(), 5);
1396 assert_eq!(arena.len(), 5);
1397
1398 let new_id = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1400 assert_eq!(new_id.index(), 5); assert_eq!(arena.get(new_id).unwrap().kind, NodeKind::Variable);
1402 }
1403
1404 #[test]
1405 fn test_truncate_to_preserves_retained_free_list() {
1406 let mut arena = NodeArena::new();
1407
1408 let mut ids = Vec::new();
1410 for _ in 0..8 {
1411 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1412 }
1413 assert_eq!(arena.len(), 8);
1414
1415 arena.remove(ids[2]);
1418 arena.remove(ids[6]);
1419 assert_eq!(arena.len(), 6);
1420
1421 arena.truncate_to(5);
1425
1426 assert_eq!(arena.slot_count(), 5);
1430 assert_eq!(arena.len(), 4);
1431
1432 let reused = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
1435 assert_eq!(reused.index(), 2);
1436 assert_eq!(arena.get(reused).unwrap().kind, NodeKind::Variable);
1437 assert_eq!(arena.len(), 5);
1438
1439 let appended = arena.alloc(test_entry(NodeKind::Class)).unwrap();
1441 assert_eq!(appended.index(), 5);
1442 assert_eq!(arena.len(), 6);
1443 }
1444
1445 #[test]
1446 fn test_slots_read_access() {
1447 let mut arena = NodeArena::new();
1448 let placeholder = test_entry(NodeKind::Variable);
1449 arena.alloc_range(4, &placeholder).unwrap();
1450
1451 let slots = arena.slots();
1452 assert_eq!(slots.len(), 4);
1453
1454 for slot in slots {
1455 assert!(slot.is_occupied());
1456 assert_eq!(slot.generation(), 1);
1457 assert_eq!(slot.get().unwrap().kind, NodeKind::Variable);
1458 }
1459 }
1460
1461 #[test]
1462 fn test_multiple_remove_reuse_cycle() {
1463 let mut arena = NodeArena::new();
1464
1465 let mut ids = Vec::new();
1467 for _ in 0..5 {
1468 ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
1469 }
1470
1471 for &id in ids.iter().rev() {
1473 arena.remove(id);
1474 }
1475
1476 let new_ids: Vec<_> = (0..5)
1478 .map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
1479 .collect();
1480
1481 for &old_id in &ids {
1483 assert!(arena.get(old_id).is_none());
1484 }
1485
1486 for &new_id in &new_ids {
1488 assert!(arena.get(new_id).is_some());
1489 }
1490 }
1491
1492 #[test]
1493 fn test_generation_overflow_at_max_generation() {
1494 let mut arena = NodeArena::new();
1496
1497 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1499 assert_eq!(id.index(), 0);
1500
1501 arena.remove(id);
1503
1504 arena.slots[0].generation = NodeId::MAX_GENERATION;
1507
1508 let result = arena.alloc(test_entry(NodeKind::Class));
1510 assert!(result.is_err());
1511
1512 let err = result.unwrap_err();
1513 match err {
1514 ArenaError::GenerationOverflow(inner) => {
1515 assert_eq!(inner.index, 0);
1516 assert_eq!(inner.generation, NodeId::MAX_GENERATION);
1517 }
1518 _ => panic!("expected GenerationOverflow, got {err:?}"),
1519 }
1520 }
1521
1522 #[test]
1523 fn test_free_list_corruption_returns_error() {
1524 let mut arena = NodeArena::new();
1526
1527 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1529 arena.remove(id);
1530
1531 arena.slots[0].state = SlotState::Occupied(test_entry(NodeKind::Class));
1533
1534 let result = arena.alloc(test_entry(NodeKind::Method));
1536 assert!(result.is_err());
1537
1538 let err = result.unwrap_err();
1539 match err {
1540 ArenaError::FreeListCorruption { index } => {
1541 assert_eq!(index, 0);
1542 }
1543 _ => panic!("expected FreeListCorruption, got {err:?}"),
1544 }
1545
1546 assert_eq!(arena.len(), 0);
1548 }
1549
1550 #[test]
1551 fn test_arena_error_display() {
1552 let gen_err = ArenaError::GenerationOverflow(GenerationOverflowError {
1553 index: 42,
1554 generation: 100,
1555 });
1556 let display = format!("{gen_err}");
1557 assert!(display.contains("42"));
1558 assert!(display.contains("100"));
1559
1560 let corruption_err = ArenaError::FreeListCorruption { index: 5 };
1561 let display = format!("{corruption_err}");
1562 assert!(display.contains("free list corruption"));
1563 assert!(display.contains('5'));
1564
1565 let capacity_err = ArenaError::CapacityExceeded;
1566 let display = format!("{capacity_err}");
1567 assert!(display.contains("capacity exceeded"));
1568 }
1569
1570 #[test]
1571 fn test_arena_error_source_generation_overflow() {
1572 use std::error::Error;
1573
1574 let inner = GenerationOverflowError {
1575 index: 0,
1576 generation: NodeId::MAX_GENERATION,
1577 };
1578 let err = ArenaError::GenerationOverflow(inner);
1579 assert!(err.source().is_some());
1581 }
1582
1583 #[test]
1584 fn test_arena_error_source_none_for_other_variants() {
1585 use std::error::Error;
1586
1587 let corruption = ArenaError::FreeListCorruption { index: 0 };
1588 assert!(corruption.source().is_none());
1589
1590 let capacity = ArenaError::CapacityExceeded;
1591 assert!(capacity.source().is_none());
1592 }
1593
1594 #[test]
1595 fn test_arena_from_generation_overflow_error() {
1596 let inner = GenerationOverflowError {
1597 index: 10,
1598 generation: 99,
1599 };
1600 let err: ArenaError = inner.into();
1601 assert!(matches!(err, ArenaError::GenerationOverflow(_)));
1602 }
1603
1604 #[test]
1605 fn test_arena_error_clone_and_eq() {
1606 let err = ArenaError::CapacityExceeded;
1607 let cloned = err.clone();
1608 assert_eq!(err, cloned);
1609
1610 let err2 = ArenaError::FreeListCorruption { index: 3 };
1611 let cloned2 = err2.clone();
1612 assert_eq!(err2, cloned2);
1613 }
1614
1615 #[test]
1616 fn test_node_entry_with_visibility() {
1617 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
1618 .with_visibility(StringId::new(5));
1619
1620 assert_eq!(entry.visibility, Some(StringId::new(5)));
1621 }
1622
1623 #[test]
1624 fn test_node_entry_with_async_and_static() {
1625 let entry = NodeEntry::new(NodeKind::Method, test_name(), test_file())
1626 .with_async(true)
1627 .with_static(true);
1628
1629 assert!(entry.is_async);
1630 assert!(entry.is_static);
1631 }
1632
1633 #[test]
1634 fn test_node_entry_with_unsafe_flag() {
1635 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_unsafe(true);
1636
1637 assert!(entry.is_unsafe);
1638 }
1639
1640 #[test]
1641 fn test_node_entry_with_body_hash() {
1642 use crate::graph::body_hash::BodyHash128;
1643 let hash = BodyHash128::from_u128(0u128);
1644 let entry =
1645 NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_body_hash(hash);
1646
1647 assert!(entry.body_hash.is_some());
1648 }
1649
1650 #[test]
1651 fn test_node_entry_defaults() {
1652 let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file());
1653 assert_eq!(entry.start_byte, 0);
1654 assert_eq!(entry.end_byte, 0);
1655 assert_eq!(entry.start_line, 0);
1656 assert_eq!(entry.start_column, 0);
1657 assert_eq!(entry.end_line, 0);
1658 assert_eq!(entry.end_column, 0);
1659 assert!(entry.signature.is_none());
1660 assert!(entry.doc.is_none());
1661 assert!(entry.qualified_name.is_none());
1662 assert!(entry.visibility.is_none());
1663 assert!(!entry.is_async);
1664 assert!(!entry.is_static);
1665 assert!(!entry.is_unsafe);
1666 assert!(entry.body_hash.is_none());
1667 }
1668
1669 #[test]
1670 fn test_arena_default_impl() {
1671 let arena = NodeArena::default();
1672 assert_eq!(arena.len(), 0);
1673 assert!(arena.is_empty());
1674 }
1675
1676 #[test]
1677 fn test_get_invalid_id() {
1678 let mut arena = NodeArena::new();
1679 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1680 assert!(arena.get(NodeId::INVALID).is_none());
1682 }
1683
1684 #[test]
1685 fn test_get_mut_invalid_id() {
1686 let mut arena = NodeArena::new();
1687 arena.alloc(test_entry(NodeKind::Function)).unwrap();
1688 assert!(arena.get_mut(NodeId::INVALID).is_none());
1689 }
1690
1691 #[test]
1692 fn test_get_mut_wrong_generation() {
1693 let mut arena = NodeArena::new();
1694 let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1695 let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
1696 assert!(arena.get_mut(wrong_gen_id).is_none());
1697 }
1698
1699 #[test]
1700 fn test_remove_invalid_id() {
1701 let mut arena = NodeArena::new();
1702 assert!(arena.remove(NodeId::INVALID).is_none());
1703 }
1704
1705 #[test]
1706 fn test_slot_get_mut_occupied() {
1707 let mut slot: Slot<i32> = Slot::new_occupied(1, 42);
1708 let val = slot.get_mut().unwrap();
1709 *val = 99;
1710 assert_eq!(slot.get(), Some(&99));
1711 }
1712
1713 #[test]
1714 fn test_slot_get_mut_vacant() {
1715 let mut slot: Slot<i32> = Slot::new_vacant(1, None);
1716 assert!(slot.get_mut().is_none());
1717 }
1718
1719 #[test]
1720 fn test_truncate_to_zero_occupied_dropped() {
1721 let mut arena = NodeArena::new();
1722 let placeholder = test_entry(NodeKind::Function);
1723 arena.alloc_range(5, &placeholder).unwrap();
1724 arena.truncate_to(0);
1726 assert_eq!(arena.len(), 0);
1727 assert_eq!(arena.slot_count(), 0);
1728 }
1729
1730 #[test]
1731 fn test_slot_count_vs_len() {
1732 let mut arena = NodeArena::new();
1733 let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
1734 arena.alloc(test_entry(NodeKind::Class)).unwrap();
1735 arena.remove(id0);
1736 assert_eq!(arena.slot_count(), 2);
1738 assert_eq!(arena.len(), 1);
1739 }
1740}