use std::fmt;
use serde::{Deserialize, Serialize};
use super::super::file::id::FileId;
use super::super::node::id::{GenerationOverflowError, NodeId};
use super::super::node::kind::NodeKind;
use super::super::string::id::StringId;
use crate::graph::body_hash::BodyHash128;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArenaError {
GenerationOverflow(GenerationOverflowError),
FreeListCorruption {
index: u32,
},
CapacityExceeded,
}
impl fmt::Display for ArenaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArenaError::GenerationOverflow(e) => write!(f, "{e}"),
ArenaError::FreeListCorruption { index } => {
write!(
f,
"free list corruption: occupied slot found at index {index}"
)
}
ArenaError::CapacityExceeded => {
write!(
f,
"arena capacity exceeded: cannot allocate more than {} nodes",
u32::MAX
)
}
}
}
}
impl std::error::Error for ArenaError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ArenaError::GenerationOverflow(e) => Some(e),
_ => None,
}
}
}
impl From<GenerationOverflowError> for ArenaError {
fn from(e: GenerationOverflowError) -> Self {
ArenaError::GenerationOverflow(e)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SlotState<T> {
Occupied(T),
Vacant {
next_free: Option<u32>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Slot<T> {
generation: u64,
state: SlotState<T>,
}
impl<T> Slot<T> {
#[allow(dead_code)] fn new_vacant(generation: u64, next_free: Option<u32>) -> Self {
Self {
generation,
state: SlotState::Vacant { next_free },
}
}
pub(crate) fn new_occupied(generation: u64, data: T) -> Self {
Self {
generation,
state: SlotState::Occupied(data),
}
}
#[inline]
pub fn is_occupied(&self) -> bool {
matches!(self.state, SlotState::Occupied(_))
}
#[inline]
pub fn is_vacant(&self) -> bool {
matches!(self.state, SlotState::Vacant { .. })
}
#[inline]
pub fn generation(&self) -> u64 {
self.generation
}
pub fn get(&self) -> Option<&T> {
match &self.state {
SlotState::Occupied(data) => Some(data),
SlotState::Vacant { .. } => None,
}
}
#[inline]
pub fn state(&self) -> &SlotState<T> {
&self.state
}
pub fn get_mut(&mut self) -> Option<&mut T> {
match &mut self.state {
SlotState::Occupied(data) => Some(data),
SlotState::Vacant { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodeEntry {
pub kind: NodeKind,
pub name: StringId,
pub file: FileId,
pub start_byte: u32,
pub end_byte: u32,
pub start_line: u32,
pub start_column: u32,
pub end_line: u32,
pub end_column: u32,
pub signature: Option<StringId>,
pub doc: Option<StringId>,
pub qualified_name: Option<StringId>,
pub visibility: Option<StringId>,
pub is_async: bool,
pub is_static: bool,
#[serde(default)]
pub body_hash: Option<BodyHash128>,
#[serde(default)]
pub is_unsafe: bool,
}
impl NodeEntry {
#[must_use]
pub fn new(kind: NodeKind, name: StringId, file: FileId) -> Self {
Self {
kind,
name,
file,
start_byte: 0,
end_byte: 0,
start_line: 0,
start_column: 0,
end_line: 0,
end_column: 0,
signature: None,
doc: None,
qualified_name: None,
visibility: None,
is_async: false,
is_static: false,
is_unsafe: false,
body_hash: None,
}
}
#[must_use]
pub fn with_byte_range(mut self, start: u32, end: u32) -> Self {
self.start_byte = start;
self.end_byte = end;
self
}
#[must_use]
pub fn with_location(
mut self,
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
) -> Self {
self.start_line = start_line;
self.start_column = start_column;
self.end_line = end_line;
self.end_column = end_column;
self
}
#[must_use]
pub fn with_signature(mut self, signature: StringId) -> Self {
self.signature = Some(signature);
self
}
#[must_use]
pub fn with_doc(mut self, doc: StringId) -> Self {
self.doc = Some(doc);
self
}
#[must_use]
pub fn with_qualified_name(mut self, qualified_name: StringId) -> Self {
self.qualified_name = Some(qualified_name);
self
}
#[must_use]
pub fn with_visibility(mut self, visibility: StringId) -> Self {
self.visibility = Some(visibility);
self
}
#[must_use]
pub fn with_async(mut self, is_async: bool) -> Self {
self.is_async = is_async;
self
}
#[must_use]
pub fn with_static(mut self, is_static: bool) -> Self {
self.is_static = is_static;
self
}
#[must_use]
pub fn with_unsafe(mut self, is_unsafe: bool) -> Self {
self.is_unsafe = is_unsafe;
self
}
#[must_use]
pub fn with_body_hash(mut self, hash: BodyHash128) -> Self {
self.body_hash = Some(hash);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeArena {
slots: Vec<Slot<NodeEntry>>,
free_head: Option<u32>,
len: usize,
}
impl NodeArena {
#[must_use]
pub fn new() -> Self {
Self {
slots: Vec::new(),
free_head: None,
len: 0,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
slots: Vec::with_capacity(capacity),
free_head: None,
len: 0,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
self.slots.capacity()
}
#[inline]
#[must_use]
pub fn slot_count(&self) -> usize {
self.slots.len()
}
pub fn alloc(&mut self, entry: NodeEntry) -> Result<NodeId, ArenaError> {
self.len += 1;
if let Some(free_idx) = self.free_head {
let slot_index = free_idx as usize;
if slot_index >= self.slots.len() {
self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
}
let slot = &mut self.slots[slot_index];
self.free_head = match &slot.state {
SlotState::Vacant { next_free } => *next_free,
SlotState::Occupied(_) => {
self.len -= 1; return Err(ArenaError::FreeListCorruption { index: free_idx });
}
};
let temp_id = NodeId::new(free_idx, slot.generation);
let new_generation = temp_id.try_increment_generation().map_err(|mut e| {
self.len -= 1; e.index = free_idx; ArenaError::GenerationOverflow(e)
})?;
slot.generation = new_generation;
slot.state = SlotState::Occupied(entry);
Ok(NodeId::new(free_idx, new_generation))
} else {
let index = self.slots.len();
let Ok(index_u32) = u32::try_from(index) else {
self.len -= 1; return Err(ArenaError::CapacityExceeded);
};
let slot = Slot::new_occupied(1, entry);
self.slots.push(slot);
Ok(NodeId::new(index_u32, 1))
}
}
#[must_use]
pub fn get(&self, id: NodeId) -> Option<&NodeEntry> {
if id.is_invalid() {
return None;
}
let index = id.index() as usize;
let slot = self.slots.get(index)?;
if slot.generation != id.generation() {
return None;
}
slot.get()
}
#[must_use]
pub fn get_mut(&mut self, id: NodeId) -> Option<&mut NodeEntry> {
if id.is_invalid() {
return None;
}
let index = id.index() as usize;
let slot = self.slots.get_mut(index)?;
if slot.generation != id.generation() {
return None;
}
slot.get_mut()
}
#[must_use]
pub fn contains(&self, id: NodeId) -> bool {
self.get(id).is_some()
}
pub fn remove(&mut self, id: NodeId) -> Option<NodeEntry> {
if id.is_invalid() {
return None;
}
let index = id.index() as usize;
let slot = self.slots.get_mut(index)?;
if slot.generation != id.generation() {
return None;
}
if let SlotState::Occupied(_) = &slot.state {
let old_state = std::mem::replace(
&mut slot.state,
SlotState::Vacant {
next_free: self.free_head,
},
);
self.free_head = Some(id.index());
self.len -= 1;
if let SlotState::Occupied(entry) = old_state {
return Some(entry);
}
}
None
}
pub fn iter(&self) -> impl Iterator<Item = (NodeId, &NodeEntry)> {
self.slots.iter().enumerate().filter_map(|(index, slot)| {
if let SlotState::Occupied(entry) = &slot.state {
let index_u32 = u32::try_from(index).ok()?;
Some((NodeId::new(index_u32, slot.generation), entry))
} else {
None
}
})
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut NodeEntry)> {
self.slots
.iter_mut()
.enumerate()
.filter_map(|(index, slot)| {
let generation = slot.generation;
if let SlotState::Occupied(entry) = &mut slot.state {
let index_u32 = u32::try_from(index).ok()?;
Some((NodeId::new(index_u32, generation), entry))
} else {
None
}
})
}
pub fn clear(&mut self) {
self.slots.clear();
self.free_head = None;
self.len = 0;
}
pub fn reserve(&mut self, additional: usize) {
self.slots.reserve(additional);
}
#[must_use]
pub fn slot(&self, index: u32) -> Option<&Slot<NodeEntry>> {
self.slots.get(index as usize)
}
pub fn alloc_range(&mut self, count: u32, placeholder: &NodeEntry) -> Result<u32, ArenaError> {
if count == 0 {
return Ok(u32::try_from(self.slots.len())
.unwrap_or_else(|_| unreachable!("slot_count invariant violated")));
}
let start = self.slots.len();
let new_total = start
.checked_add(count as usize)
.ok_or(ArenaError::CapacityExceeded)?;
if new_total > u32::MAX as usize + 1 {
return Err(ArenaError::CapacityExceeded);
}
let start_u32 = u32::try_from(start).map_err(|_| ArenaError::CapacityExceeded)?;
self.slots.reserve(count as usize);
for _ in 0..count {
self.slots.push(Slot::new_occupied(1, placeholder.clone()));
}
self.len += count as usize;
Ok(start_u32)
}
#[must_use]
pub fn bulk_slice_mut(&mut self, start: u32, count: u32) -> &mut [Slot<NodeEntry>] {
let begin = start as usize;
let end = begin + count as usize;
&mut self.slots[begin..end]
}
pub fn truncate_to(&mut self, saved_slot_count: usize) {
assert!(
saved_slot_count <= self.slots.len(),
"truncate_to({saved_slot_count}) exceeds current slot count ({})",
self.slots.len(),
);
let dropped_occupied = self.slots[saved_slot_count..]
.iter()
.filter(|s| s.is_occupied())
.count();
self.slots.truncate(saved_slot_count);
self.len -= dropped_occupied;
self.rebuild_free_list_after_truncate(saved_slot_count);
}
fn rebuild_free_list_after_truncate(&mut self, cutoff: usize) {
let mut new_head: Option<u32> = None;
for i in (0..cutoff).rev() {
if self.slots[i].is_vacant() {
let i_u32 = u32::try_from(i)
.unwrap_or_else(|_| unreachable!("free-list index exceeds u32 invariant"));
self.slots[i].state = SlotState::Vacant {
next_free: new_head,
};
new_head = Some(i_u32);
}
}
self.free_head = new_head;
}
#[must_use]
pub fn slots(&self) -> &[Slot<NodeEntry>] {
&self.slots
}
}
impl Default for NodeArena {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for NodeArena {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"NodeArena(len={}, capacity={})",
self.len,
self.capacity()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_file() -> FileId {
FileId::new(1)
}
fn test_name() -> StringId {
StringId::new(1)
}
fn test_entry(kind: NodeKind) -> NodeEntry {
NodeEntry::new(kind, test_name(), test_file())
}
#[test]
fn test_arena_new() {
let arena = NodeArena::new();
assert_eq!(arena.len(), 0);
assert!(arena.is_empty());
assert_eq!(arena.capacity(), 0);
}
#[test]
fn test_arena_with_capacity() {
let arena = NodeArena::with_capacity(100);
assert_eq!(arena.len(), 0);
assert!(arena.capacity() >= 100);
}
#[test]
fn test_alloc_and_get() {
let mut arena = NodeArena::new();
let entry = test_entry(NodeKind::Function);
let id = arena.alloc(entry).unwrap();
assert!(!id.is_invalid());
assert_eq!(arena.len(), 1);
let node = arena.get(id).unwrap();
assert_eq!(node.kind, NodeKind::Function);
}
#[test]
fn test_alloc_multiple() {
let mut arena = NodeArena::new();
let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
assert_eq!(arena.len(), 3);
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Function);
assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
assert_eq!(arena.get(id3).unwrap().kind, NodeKind::Method);
}
#[test]
fn test_get_mut() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
{
let node = arena.get_mut(id).unwrap();
node.start_line = 42;
}
assert_eq!(arena.get(id).unwrap().start_line, 42);
}
#[test]
fn test_contains() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
assert!(arena.contains(id));
assert!(!arena.contains(NodeId::INVALID));
}
#[test]
fn test_remove() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
assert_eq!(arena.len(), 1);
assert!(arena.contains(id));
let removed = arena.remove(id).unwrap();
assert_eq!(removed.kind, NodeKind::Function);
assert_eq!(arena.len(), 0);
assert!(!arena.contains(id));
}
#[test]
fn test_stale_id_after_remove() {
let mut arena = NodeArena::new();
let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.remove(id1);
let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
assert!(arena.get(id1).is_none());
assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Class);
assert_eq!(id1.index(), id2.index());
assert_ne!(id1.generation(), id2.generation());
}
#[test]
fn test_remove_idempotent() {
let mut arena = NodeArena::new();
let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
assert!(arena.remove(id1).is_some());
assert_eq!(arena.len(), 1);
assert!(arena.remove(id1).is_none());
assert_eq!(arena.len(), 1);
assert!(arena.remove(id1).is_none());
assert_eq!(arena.len(), 1);
assert!(arena.contains(id2));
let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
assert_eq!(id3.index(), id1.index()); assert_eq!(arena.len(), 2);
}
#[test]
fn test_free_list_reuse() {
let mut arena = NodeArena::new();
let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
arena.remove(id2);
assert_eq!(arena.len(), 2);
assert_eq!(arena.slot_count(), 3);
let id4 = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
assert_eq!(id4.index(), id2.index());
assert_eq!(arena.len(), 3);
assert_eq!(arena.slot_count(), 3);
assert!(arena.get(id2).is_none());
assert_eq!(arena.get(id4).unwrap().kind, NodeKind::Variable);
assert!(arena.contains(id1));
assert!(arena.contains(id3));
}
#[test]
fn test_invalid_id() {
let arena = NodeArena::new();
assert!(arena.get(NodeId::INVALID).is_none());
}
#[test]
fn test_out_of_bounds_id() {
let arena = NodeArena::new();
let fake_id = NodeId::new(999, 1);
assert!(arena.get(fake_id).is_none());
}
#[test]
fn test_wrong_generation() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
assert!(arena.get(wrong_gen_id).is_none());
}
#[test]
fn test_iter() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.alloc(test_entry(NodeKind::Class)).unwrap();
arena.alloc(test_entry(NodeKind::Method)).unwrap();
let entries: Vec<_> = arena.iter().collect();
assert_eq!(entries.len(), 3);
let kinds: Vec<_> = entries.iter().map(|(_, e)| e.kind).collect();
assert!(kinds.contains(&NodeKind::Function));
assert!(kinds.contains(&NodeKind::Class));
assert!(kinds.contains(&NodeKind::Method));
}
#[test]
fn test_iter_with_removed() {
let mut arena = NodeArena::new();
let id1 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let id2 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
let id3 = arena.alloc(test_entry(NodeKind::Method)).unwrap();
arena.remove(id2);
let entries: Vec<_> = arena.iter().collect();
assert_eq!(entries.len(), 2);
let collected_ids: Vec<_> = entries.iter().map(|(id, _)| *id).collect();
assert!(collected_ids.contains(&id1));
assert!(!collected_ids.contains(&id2));
assert!(collected_ids.contains(&id3));
}
#[test]
fn test_iter_mut() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.alloc(test_entry(NodeKind::Class)).unwrap();
for (_, entry) in arena.iter_mut() {
entry.start_line = 100;
}
for (_, entry) in arena.iter() {
assert_eq!(entry.start_line, 100);
}
}
#[test]
fn test_clear() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.alloc(test_entry(NodeKind::Class)).unwrap();
assert_eq!(arena.len(), 2);
arena.clear();
assert_eq!(arena.len(), 0);
assert!(arena.is_empty());
}
#[test]
fn test_reserve() {
let mut arena = NodeArena::new();
arena.reserve(1000);
assert!(arena.capacity() >= 1000);
}
#[test]
fn test_display() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
let display = format!("{arena}");
assert!(display.contains("NodeArena"));
assert!(display.contains("len=1"));
}
#[test]
fn test_node_entry_builder() {
let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
.with_byte_range(10, 100)
.with_location(1, 0, 5, 10)
.with_signature(StringId::new(2))
.with_doc(StringId::new(3))
.with_qualified_name(StringId::new(4));
assert_eq!(entry.kind, NodeKind::Function);
assert_eq!(entry.start_byte, 10);
assert_eq!(entry.end_byte, 100);
assert_eq!(entry.start_line, 1);
assert_eq!(entry.start_column, 0);
assert_eq!(entry.end_line, 5);
assert_eq!(entry.end_column, 10);
assert_eq!(entry.signature, Some(StringId::new(2)));
assert_eq!(entry.doc, Some(StringId::new(3)));
assert_eq!(entry.qualified_name, Some(StringId::new(4)));
}
#[test]
fn test_slot_state() {
let occupied: Slot<i32> = Slot::new_occupied(1, 42);
assert!(occupied.is_occupied());
assert!(!occupied.is_vacant());
assert_eq!(occupied.get(), Some(&42));
let vacant: Slot<i32> = Slot::new_vacant(2, Some(5));
assert!(!vacant.is_occupied());
assert!(vacant.is_vacant());
assert_eq!(vacant.get(), None);
}
#[test]
fn test_slot_generation() {
let slot: Slot<i32> = Slot::new_occupied(42, 100);
assert_eq!(slot.generation(), 42);
}
#[test]
fn test_slot_state_accessor() {
let occupied: Slot<i32> = Slot::new_occupied(1, 42);
assert!(matches!(occupied.state(), SlotState::Occupied(42)));
let vacant: Slot<i32> = Slot::new_vacant(1, Some(3));
assert!(matches!(
vacant.state(),
SlotState::Vacant { next_free: Some(3) }
));
}
#[test]
fn test_alloc_range_basic() {
let mut arena = NodeArena::new();
let placeholder = test_entry(NodeKind::Function);
let start = arena.alloc_range(5, &placeholder).unwrap();
assert_eq!(start, 0);
assert_eq!(arena.len(), 5);
assert_eq!(arena.slot_count(), 5);
for i in 0..5u32 {
let id = NodeId::new(i, 1);
let entry = arena.get(id).expect("slot should be occupied");
assert_eq!(entry.kind, NodeKind::Function);
}
}
#[test]
fn test_alloc_range_after_existing() {
let mut arena = NodeArena::new();
let id0 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
assert_eq!(id0.index(), 0);
assert_eq!(arena.len(), 1);
let placeholder = test_entry(NodeKind::Method);
let start = arena.alloc_range(3, &placeholder).unwrap();
assert_eq!(start, 1);
assert_eq!(arena.len(), 4);
assert_eq!(arena.slot_count(), 4);
assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Class);
for i in 1..4u32 {
let id = NodeId::new(i, 1);
assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
}
}
#[test]
fn test_alloc_range_zero_is_noop() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
let len_before = arena.len();
let slot_count_before = arena.slot_count();
let start = arena.alloc_range(0, &test_entry(NodeKind::Class)).unwrap();
assert_eq!(start, slot_count_before as u32);
assert_eq!(arena.len(), len_before);
assert_eq!(arena.slot_count(), slot_count_before);
}
#[test]
fn test_bulk_slice_mut() {
let mut arena = NodeArena::new();
let placeholder = test_entry(NodeKind::Function);
let start = arena.alloc_range(3, &placeholder).unwrap();
{
let slice = arena.bulk_slice_mut(start, 3);
assert_eq!(slice.len(), 3);
slice[1] = Slot::new_occupied(1, test_entry(NodeKind::Class));
}
let id1 = NodeId::new(1, 1);
assert_eq!(arena.get(id1).unwrap().kind, NodeKind::Class);
let id0 = NodeId::new(0, 1);
assert_eq!(arena.get(id0).unwrap().kind, NodeKind::Function);
let id2 = NodeId::new(2, 1);
assert_eq!(arena.get(id2).unwrap().kind, NodeKind::Function);
}
#[test]
fn test_truncate_to() {
let mut arena = NodeArena::new();
let placeholder = test_entry(NodeKind::Function);
arena.alloc_range(3, &placeholder).unwrap();
assert_eq!(arena.len(), 3);
assert_eq!(arena.slot_count(), 3);
let saved = arena.slot_count();
arena.alloc_range(4, &placeholder).unwrap();
assert_eq!(arena.len(), 7);
assert_eq!(arena.slot_count(), 7);
arena.truncate_to(saved);
assert_eq!(arena.len(), 3);
assert_eq!(arena.slot_count(), 3);
for i in 0..3u32 {
let id = NodeId::new(i, 1);
assert!(arena.get(id).is_some());
}
}
#[test]
fn test_alloc_range_with_free_list() {
let mut arena = NodeArena::new();
let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let _id1 = arena.alloc(test_entry(NodeKind::Class)).unwrap();
arena.remove(id0);
assert_eq!(arena.len(), 1);
assert_eq!(arena.slot_count(), 2);
let start = arena.alloc_range(3, &test_entry(NodeKind::Method)).unwrap();
assert_eq!(start, 2); assert_eq!(arena.len(), 4); assert_eq!(arena.slot_count(), 5);
let slot0 = arena.slot(0).unwrap();
assert!(slot0.is_vacant());
for i in 2..5u32 {
let id = NodeId::new(i, 1);
assert_eq!(arena.get(id).unwrap().kind, NodeKind::Method);
}
}
#[test]
fn test_truncate_to_clamps_free_head() {
let mut arena = NodeArena::new();
let mut ids = Vec::new();
for _ in 0..5 {
ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
}
let saved = arena.slot_count();
let extra_ids: Vec<_> = (0..3)
.map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
.collect();
arena.remove(extra_ids[1]);
arena.truncate_to(saved);
assert_eq!(arena.slot_count(), 5);
assert_eq!(arena.len(), 5);
let new_id = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
assert_eq!(new_id.index(), 5); assert_eq!(arena.get(new_id).unwrap().kind, NodeKind::Variable);
}
#[test]
fn test_truncate_to_preserves_retained_free_list() {
let mut arena = NodeArena::new();
let mut ids = Vec::new();
for _ in 0..8 {
ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
}
assert_eq!(arena.len(), 8);
arena.remove(ids[2]);
arena.remove(ids[6]);
assert_eq!(arena.len(), 6);
arena.truncate_to(5);
assert_eq!(arena.slot_count(), 5);
assert_eq!(arena.len(), 4);
let reused = arena.alloc(test_entry(NodeKind::Variable)).unwrap();
assert_eq!(reused.index(), 2);
assert_eq!(arena.get(reused).unwrap().kind, NodeKind::Variable);
assert_eq!(arena.len(), 5);
let appended = arena.alloc(test_entry(NodeKind::Class)).unwrap();
assert_eq!(appended.index(), 5);
assert_eq!(arena.len(), 6);
}
#[test]
fn test_slots_read_access() {
let mut arena = NodeArena::new();
let placeholder = test_entry(NodeKind::Variable);
arena.alloc_range(4, &placeholder).unwrap();
let slots = arena.slots();
assert_eq!(slots.len(), 4);
for slot in slots {
assert!(slot.is_occupied());
assert_eq!(slot.generation(), 1);
assert_eq!(slot.get().unwrap().kind, NodeKind::Variable);
}
}
#[test]
fn test_multiple_remove_reuse_cycle() {
let mut arena = NodeArena::new();
let mut ids = Vec::new();
for _ in 0..5 {
ids.push(arena.alloc(test_entry(NodeKind::Function)).unwrap());
}
for &id in ids.iter().rev() {
arena.remove(id);
}
let new_ids: Vec<_> = (0..5)
.map(|_| arena.alloc(test_entry(NodeKind::Class)).unwrap())
.collect();
for &old_id in &ids {
assert!(arena.get(old_id).is_none());
}
for &new_id in &new_ids {
assert!(arena.get(new_id).is_some());
}
}
#[test]
fn test_generation_overflow_at_max_generation() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
assert_eq!(id.index(), 0);
arena.remove(id);
arena.slots[0].generation = NodeId::MAX_GENERATION;
let result = arena.alloc(test_entry(NodeKind::Class));
assert!(result.is_err());
let err = result.unwrap_err();
match err {
ArenaError::GenerationOverflow(inner) => {
assert_eq!(inner.index, 0);
assert_eq!(inner.generation, NodeId::MAX_GENERATION);
}
_ => panic!("expected GenerationOverflow, got {err:?}"),
}
}
#[test]
fn test_free_list_corruption_returns_error() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.remove(id);
arena.slots[0].state = SlotState::Occupied(test_entry(NodeKind::Class));
let result = arena.alloc(test_entry(NodeKind::Method));
assert!(result.is_err());
let err = result.unwrap_err();
match err {
ArenaError::FreeListCorruption { index } => {
assert_eq!(index, 0);
}
_ => panic!("expected FreeListCorruption, got {err:?}"),
}
assert_eq!(arena.len(), 0);
}
#[test]
fn test_arena_error_display() {
let gen_err = ArenaError::GenerationOverflow(GenerationOverflowError {
index: 42,
generation: 100,
});
let display = format!("{gen_err}");
assert!(display.contains("42"));
assert!(display.contains("100"));
let corruption_err = ArenaError::FreeListCorruption { index: 5 };
let display = format!("{corruption_err}");
assert!(display.contains("free list corruption"));
assert!(display.contains("5"));
let capacity_err = ArenaError::CapacityExceeded;
let display = format!("{capacity_err}");
assert!(display.contains("capacity exceeded"));
}
#[test]
fn test_arena_error_source_generation_overflow() {
use std::error::Error;
let inner = GenerationOverflowError {
index: 0,
generation: NodeId::MAX_GENERATION,
};
let err = ArenaError::GenerationOverflow(inner);
assert!(err.source().is_some());
}
#[test]
fn test_arena_error_source_none_for_other_variants() {
use std::error::Error;
let corruption = ArenaError::FreeListCorruption { index: 0 };
assert!(corruption.source().is_none());
let capacity = ArenaError::CapacityExceeded;
assert!(capacity.source().is_none());
}
#[test]
fn test_arena_from_generation_overflow_error() {
let inner = GenerationOverflowError {
index: 10,
generation: 99,
};
let err: ArenaError = inner.into();
assert!(matches!(err, ArenaError::GenerationOverflow(_)));
}
#[test]
fn test_arena_error_clone_and_eq() {
let err = ArenaError::CapacityExceeded;
let cloned = err.clone();
assert_eq!(err, cloned);
let err2 = ArenaError::FreeListCorruption { index: 3 };
let cloned2 = err2.clone();
assert_eq!(err2, cloned2);
}
#[test]
fn test_node_entry_with_visibility() {
let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file())
.with_visibility(StringId::new(5));
assert_eq!(entry.visibility, Some(StringId::new(5)));
}
#[test]
fn test_node_entry_with_async_and_static() {
let entry = NodeEntry::new(NodeKind::Method, test_name(), test_file())
.with_async(true)
.with_static(true);
assert!(entry.is_async);
assert!(entry.is_static);
}
#[test]
fn test_node_entry_with_unsafe_flag() {
let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_unsafe(true);
assert!(entry.is_unsafe);
}
#[test]
fn test_node_entry_with_body_hash() {
use crate::graph::body_hash::BodyHash128;
let hash = BodyHash128::from_u128(0u128);
let entry =
NodeEntry::new(NodeKind::Function, test_name(), test_file()).with_body_hash(hash);
assert!(entry.body_hash.is_some());
}
#[test]
fn test_node_entry_defaults() {
let entry = NodeEntry::new(NodeKind::Function, test_name(), test_file());
assert_eq!(entry.start_byte, 0);
assert_eq!(entry.end_byte, 0);
assert_eq!(entry.start_line, 0);
assert_eq!(entry.start_column, 0);
assert_eq!(entry.end_line, 0);
assert_eq!(entry.end_column, 0);
assert!(entry.signature.is_none());
assert!(entry.doc.is_none());
assert!(entry.qualified_name.is_none());
assert!(entry.visibility.is_none());
assert!(!entry.is_async);
assert!(!entry.is_static);
assert!(!entry.is_unsafe);
assert!(entry.body_hash.is_none());
}
#[test]
fn test_arena_default_impl() {
let arena = NodeArena::default();
assert_eq!(arena.len(), 0);
assert!(arena.is_empty());
}
#[test]
fn test_get_invalid_id() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
assert!(arena.get(NodeId::INVALID).is_none());
}
#[test]
fn test_get_mut_invalid_id() {
let mut arena = NodeArena::new();
arena.alloc(test_entry(NodeKind::Function)).unwrap();
assert!(arena.get_mut(NodeId::INVALID).is_none());
}
#[test]
fn test_get_mut_wrong_generation() {
let mut arena = NodeArena::new();
let id = arena.alloc(test_entry(NodeKind::Function)).unwrap();
let wrong_gen_id = NodeId::new(id.index(), id.generation() + 1);
assert!(arena.get_mut(wrong_gen_id).is_none());
}
#[test]
fn test_remove_invalid_id() {
let mut arena = NodeArena::new();
assert!(arena.remove(NodeId::INVALID).is_none());
}
#[test]
fn test_slot_get_mut_occupied() {
let mut slot: Slot<i32> = Slot::new_occupied(1, 42);
let val = slot.get_mut().unwrap();
*val = 99;
assert_eq!(slot.get(), Some(&99));
}
#[test]
fn test_slot_get_mut_vacant() {
let mut slot: Slot<i32> = Slot::new_vacant(1, None);
assert!(slot.get_mut().is_none());
}
#[test]
fn test_truncate_to_zero_occupied_dropped() {
let mut arena = NodeArena::new();
let placeholder = test_entry(NodeKind::Function);
arena.alloc_range(5, &placeholder).unwrap();
arena.truncate_to(0);
assert_eq!(arena.len(), 0);
assert_eq!(arena.slot_count(), 0);
}
#[test]
fn test_slot_count_vs_len() {
let mut arena = NodeArena::new();
let id0 = arena.alloc(test_entry(NodeKind::Function)).unwrap();
arena.alloc(test_entry(NodeKind::Class)).unwrap();
arena.remove(id0);
assert_eq!(arena.slot_count(), 2);
assert_eq!(arena.len(), 1);
}
}