use std::collections::BTreeMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use super::super::file::id::FileId;
use super::super::node::id::NodeId;
use super::super::node::kind::NodeKind;
use super::super::string::id::StringId;
use super::arena::NodeArena;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuxiliaryIndices {
kind_index: BTreeMap<NodeKind, Vec<NodeId>>,
name_index: BTreeMap<StringId, Vec<NodeId>>,
qualified_name_index: BTreeMap<StringId, Vec<NodeId>>,
file_index: BTreeMap<FileId, Vec<NodeId>>,
node_count: usize,
}
impl AuxiliaryIndices {
#[must_use]
pub fn new() -> Self {
Self {
kind_index: BTreeMap::new(),
name_index: BTreeMap::new(),
qualified_name_index: BTreeMap::new(),
file_index: BTreeMap::new(),
node_count: 0,
}
}
#[must_use]
pub fn with_capacity(_capacity: usize) -> Self {
Self::new()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.node_count
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.node_count == 0
}
pub fn add(
&mut self,
id: NodeId,
kind: NodeKind,
name: StringId,
qualified_name: Option<StringId>,
file: FileId,
) -> bool {
let kind_ids = self.kind_index.entry(kind).or_default();
if kind_ids.contains(&id) {
return false;
}
kind_ids.push(id);
self.name_index.entry(name).or_default().push(id);
if let Some(qn) = qualified_name {
self.qualified_name_index.entry(qn).or_default().push(id);
}
self.file_index.entry(file).or_default().push(id);
self.node_count += 1;
true
}
fn remove_id_from_index<K: Eq + Ord + Copy>(
index: &mut BTreeMap<K, Vec<NodeId>>,
key: K,
id: NodeId,
) -> bool {
let Some(ids) = index.get_mut(&key) else {
return false;
};
let Some(pos) = ids.iter().position(|&x| x == id) else {
return false;
};
ids.swap_remove(pos);
if ids.is_empty() {
index.remove(&key);
}
true
}
fn remove_id_from_all_buckets<K: Eq + Ord>(index: &mut BTreeMap<K, Vec<NodeId>>, id: NodeId) {
for ids in index.values_mut() {
if let Some(pos) = ids.iter().position(|&x| x == id) {
ids.swap_remove(pos);
break;
}
}
}
fn retain_non_empty<K: Eq + Ord>(index: &mut BTreeMap<K, Vec<NodeId>>) {
index.retain(|_, v| !v.is_empty());
}
pub fn remove(
&mut self,
id: NodeId,
kind: NodeKind,
name: StringId,
qualified_name: Option<StringId>,
file: FileId,
) -> bool {
let removed = Self::remove_id_from_index(&mut self.kind_index, kind, id);
Self::remove_id_from_index(&mut self.name_index, name, id);
if let Some(qn) = qualified_name {
Self::remove_id_from_index(&mut self.qualified_name_index, qn, id);
}
Self::remove_id_from_index(&mut self.file_index, file, id);
if removed {
self.node_count -= 1;
}
removed
}
#[must_use]
pub fn by_kind(&self, kind: NodeKind) -> &[NodeId] {
self.kind_index.get(&kind).map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn by_name(&self, name: StringId) -> &[NodeId] {
self.name_index.get(&name).map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn by_qualified_name(&self, name: StringId) -> &[NodeId] {
self.qualified_name_index
.get(&name)
.map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn by_file(&self, file: FileId) -> &[NodeId] {
self.file_index.get(&file).map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn kind_count(&self) -> usize {
self.kind_index.len()
}
#[must_use]
pub fn name_count(&self) -> usize {
self.name_index.len()
}
#[must_use]
pub fn file_count(&self) -> usize {
self.file_index.len()
}
#[must_use]
pub fn has_kind(&self, kind: NodeKind) -> bool {
self.kind_index.contains_key(&kind)
}
#[must_use]
pub fn has_name(&self, name: StringId) -> bool {
self.name_index.contains_key(&name)
}
#[must_use]
pub fn has_file(&self, file: FileId) -> bool {
self.file_index.contains_key(&file)
}
pub fn iter_kinds(&self) -> impl Iterator<Item = (NodeKind, usize)> + '_ {
self.kind_index.iter().map(|(&k, v)| (k, v.len()))
}
pub fn iter_names(&self) -> impl Iterator<Item = (StringId, usize)> + '_ {
self.name_index.iter().map(|(&n, v)| (n, v.len()))
}
pub fn iter_files(&self) -> impl Iterator<Item = (FileId, usize)> + '_ {
self.file_index.iter().map(|(&f, v)| (f, v.len()))
}
pub fn remove_file_with_info(
&mut self,
file: FileId,
nodes: impl IntoIterator<Item = (NodeId, NodeKind, StringId, Option<StringId>)>,
) -> usize {
self.file_index.remove(&file);
let removed_count = self.remove_nodes_with_info(nodes);
self.node_count -= removed_count;
removed_count
}
#[deprecated(
since = "0.1.0",
note = "use remove_file_with_info for O(N) performance when node metadata is available"
)]
pub fn remove_file(&mut self, file: FileId) -> Vec<NodeId> {
let node_ids = self.file_index.remove(&file).unwrap_or_default();
let removed_count = node_ids.len();
self.remove_nodes_without_info(&node_ids);
self.node_count -= removed_count;
node_ids
}
fn remove_nodes_with_info(
&mut self,
nodes: impl IntoIterator<Item = (NodeId, NodeKind, StringId, Option<StringId>)>,
) -> usize {
let mut removed_count = 0;
for (id, kind, name, qualified_name) in nodes {
if Self::remove_id_from_index(&mut self.kind_index, kind, id) {
removed_count += 1;
}
Self::remove_id_from_index(&mut self.name_index, name, id);
if let Some(qn) = qualified_name {
Self::remove_id_from_index(&mut self.qualified_name_index, qn, id);
}
}
removed_count
}
fn remove_nodes_without_info(&mut self, node_ids: &[NodeId]) {
for &id in node_ids {
Self::remove_id_from_all_buckets(&mut self.kind_index, id);
Self::remove_id_from_all_buckets(&mut self.name_index, id);
Self::remove_id_from_all_buckets(&mut self.qualified_name_index, id);
}
Self::retain_non_empty(&mut self.kind_index);
Self::retain_non_empty(&mut self.name_index);
Self::retain_non_empty(&mut self.qualified_name_index);
}
pub fn clear(&mut self) {
self.kind_index.clear();
self.name_index.clear();
self.qualified_name_index.clear();
self.file_index.clear();
self.node_count = 0;
}
pub fn build_from_arena(&mut self, arena: &NodeArena) {
self.clear();
for (id, entry) in arena.iter() {
self.kind_index.entry(entry.kind).or_default().push(id);
self.name_index.entry(entry.name).or_default().push(id);
if let Some(qn) = entry.qualified_name {
self.qualified_name_index.entry(qn).or_default().push(id);
}
self.file_index.entry(entry.file).or_default().push(id);
self.node_count += 1;
}
}
pub fn reserve(&mut self, _additional: usize) {
}
#[must_use]
pub fn stats(&self) -> IndicesStats {
IndicesStats {
node_count: self.node_count,
kind_count: self.kind_index.len(),
name_count: self.name_index.len(),
qualified_name_count: self.qualified_name_index.len(),
file_count: self.file_index.len(),
}
}
}
impl Default for AuxiliaryIndices {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for AuxiliaryIndices {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"AuxiliaryIndices(nodes={}, kinds={}, names={}, qnames={}, files={})",
self.node_count,
self.kind_index.len(),
self.name_index.len(),
self.qualified_name_index.len(),
self.file_index.len()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndicesStats {
pub node_count: usize,
pub kind_count: usize,
pub name_count: usize,
pub qualified_name_count: usize,
pub file_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
fn test_id(index: u32) -> NodeId {
NodeId::new(index, 1)
}
fn test_name(index: u32) -> StringId {
StringId::new(index)
}
fn test_file(index: u32) -> FileId {
FileId::new(index)
}
#[test]
fn test_new() {
let indices = AuxiliaryIndices::new();
assert_eq!(indices.len(), 0);
assert!(indices.is_empty());
}
#[test]
fn test_with_capacity() {
let indices = AuxiliaryIndices::with_capacity(1000);
assert_eq!(indices.len(), 0);
}
#[test]
fn test_add_single() {
let mut indices = AuxiliaryIndices::new();
let id = test_id(1);
let name = test_name(1);
let file = test_file(1);
assert!(indices.add(id, NodeKind::Function, name, None, file));
assert_eq!(indices.len(), 1);
assert_eq!(indices.by_kind(NodeKind::Function), &[id]);
assert_eq!(indices.by_name(name), &[id]);
assert_eq!(indices.by_file(file), &[id]);
}
#[test]
fn test_add_duplicate_prevented() {
let mut indices = AuxiliaryIndices::new();
let id = test_id(1);
let name = test_name(1);
let file = test_file(1);
assert!(indices.add(id, NodeKind::Function, name, None, file));
assert_eq!(indices.len(), 1);
assert!(!indices.add(id, NodeKind::Function, name, None, file));
assert_eq!(indices.len(), 1);
assert_eq!(indices.by_kind(NodeKind::Function).len(), 1);
assert_eq!(indices.by_name(name).len(), 1);
assert_eq!(indices.by_file(file).len(), 1);
}
#[test]
fn test_add_multiple_same_kind() {
let mut indices = AuxiliaryIndices::new();
let id1 = test_id(1);
let id2 = test_id(2);
let id3 = test_id(3);
indices.add(id1, NodeKind::Function, test_name(1), None, test_file(1));
indices.add(id2, NodeKind::Function, test_name(2), None, test_file(1));
indices.add(id3, NodeKind::Function, test_name(3), None, test_file(2));
assert_eq!(indices.len(), 3);
let by_kind = indices.by_kind(NodeKind::Function);
assert_eq!(by_kind.len(), 3);
assert!(by_kind.contains(&id1));
assert!(by_kind.contains(&id2));
assert!(by_kind.contains(&id3));
}
#[test]
fn test_add_multiple_same_name() {
let mut indices = AuxiliaryIndices::new();
let id1 = test_id(1);
let id2 = test_id(2);
let shared_name = test_name(100);
indices.add(id1, NodeKind::Function, shared_name, None, test_file(1));
indices.add(id2, NodeKind::Variable, shared_name, None, test_file(2));
let by_name = indices.by_name(shared_name);
assert_eq!(by_name.len(), 2);
assert!(by_name.contains(&id1));
assert!(by_name.contains(&id2));
}
#[test]
fn test_add_multiple_same_file() {
let mut indices = AuxiliaryIndices::new();
let id1 = test_id(1);
let id2 = test_id(2);
let id3 = test_id(3);
let shared_file = test_file(100);
indices.add(id1, NodeKind::Function, test_name(1), None, shared_file);
indices.add(id2, NodeKind::Class, test_name(2), None, shared_file);
indices.add(id3, NodeKind::Method, test_name(3), None, shared_file);
let by_file = indices.by_file(shared_file);
assert_eq!(by_file.len(), 3);
assert!(by_file.contains(&id1));
assert!(by_file.contains(&id2));
assert!(by_file.contains(&id3));
}
#[test]
fn test_remove() {
let mut indices = AuxiliaryIndices::new();
let id = test_id(1);
let name = test_name(1);
let file = test_file(1);
indices.add(id, NodeKind::Function, name, None, file);
assert_eq!(indices.len(), 1);
let removed = indices.remove(id, NodeKind::Function, name, None, file);
assert!(removed);
assert_eq!(indices.len(), 0);
assert!(indices.by_kind(NodeKind::Function).is_empty());
assert!(indices.by_name(name).is_empty());
assert!(indices.by_file(file).is_empty());
}
#[test]
fn test_remove_nonexistent() {
let mut indices = AuxiliaryIndices::new();
let id = test_id(1);
let name = test_name(1);
let file = test_file(1);
let removed = indices.remove(id, NodeKind::Function, name, None, file);
assert!(!removed);
}
#[test]
fn test_remove_one_of_many() {
let mut indices = AuxiliaryIndices::new();
let id1 = test_id(1);
let id2 = test_id(2);
let name = test_name(1);
let file = test_file(1);
indices.add(id1, NodeKind::Function, name, None, file);
indices.add(id2, NodeKind::Function, name, None, file);
assert_eq!(indices.len(), 2);
indices.remove(id1, NodeKind::Function, name, None, file);
assert_eq!(indices.len(), 1);
assert_eq!(indices.by_kind(NodeKind::Function), &[id2]);
}
#[test]
fn test_by_kind_empty() {
let indices = AuxiliaryIndices::new();
assert!(indices.by_kind(NodeKind::Function).is_empty());
}
#[test]
fn test_by_name_empty() {
let indices = AuxiliaryIndices::new();
assert!(indices.by_name(test_name(1)).is_empty());
}
#[test]
fn test_by_file_empty() {
let indices = AuxiliaryIndices::new();
assert!(indices.by_file(test_file(1)).is_empty());
}
#[test]
fn test_counts() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
indices.add(
test_id(2),
NodeKind::Class,
test_name(2),
None,
test_file(1),
);
indices.add(
test_id(3),
NodeKind::Function,
test_name(3),
None,
test_file(2),
);
assert_eq!(indices.kind_count(), 2); assert_eq!(indices.name_count(), 3); assert_eq!(indices.file_count(), 2); }
#[test]
fn test_has_methods() {
let mut indices = AuxiliaryIndices::new();
let name = test_name(1);
let file = test_file(1);
indices.add(test_id(1), NodeKind::Function, name, None, file);
assert!(indices.has_kind(NodeKind::Function));
assert!(!indices.has_kind(NodeKind::Class));
assert!(indices.has_name(name));
assert!(!indices.has_name(test_name(999)));
assert!(indices.has_file(file));
assert!(!indices.has_file(test_file(999)));
}
#[test]
fn test_iter_kinds() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
indices.add(
test_id(2),
NodeKind::Function,
test_name(2),
None,
test_file(1),
);
indices.add(
test_id(3),
NodeKind::Class,
test_name(3),
None,
test_file(1),
);
let kinds: Vec<_> = indices.iter_kinds().collect();
assert_eq!(kinds.len(), 2);
let functions = kinds.iter().find(|(k, _)| *k == NodeKind::Function);
assert_eq!(functions, Some(&(NodeKind::Function, 2)));
let classes = kinds.iter().find(|(k, _)| *k == NodeKind::Class);
assert_eq!(classes, Some(&(NodeKind::Class, 1)));
}
#[test]
fn test_iter_files() {
let mut indices = AuxiliaryIndices::new();
let file1 = test_file(1);
let file2 = test_file(2);
indices.add(test_id(1), NodeKind::Function, test_name(1), None, file1);
indices.add(test_id(2), NodeKind::Function, test_name(2), None, file1);
indices.add(test_id(3), NodeKind::Class, test_name(3), None, file2);
let file_entries: Vec<_> = indices.iter_files().collect();
assert_eq!(file_entries.len(), 2);
}
#[test]
fn test_remove_file_with_info() {
let mut indices = AuxiliaryIndices::new();
let file1 = test_file(1);
let file2 = test_file(2);
let id1 = test_id(1);
let id2 = test_id(2);
let id3 = test_id(3);
let name1 = test_name(1);
let name2 = test_name(2);
let name3 = test_name(3);
indices.add(id1, NodeKind::Function, name1, None, file1);
indices.add(id2, NodeKind::Class, name2, None, file1);
indices.add(id3, NodeKind::Method, name3, None, file2);
assert_eq!(indices.len(), 3);
let nodes = vec![
(id1, NodeKind::Function, name1, None),
(id2, NodeKind::Class, name2, None),
];
let removed_count = indices.remove_file_with_info(file1, nodes);
assert_eq!(removed_count, 2);
assert_eq!(indices.len(), 1);
assert!(indices.by_file(file1).is_empty());
assert_eq!(indices.by_file(file2), &[id3]);
assert!(indices.by_kind(NodeKind::Function).is_empty());
assert!(indices.by_kind(NodeKind::Class).is_empty());
assert_eq!(indices.by_kind(NodeKind::Method), &[id3]);
}
#[test]
fn test_remove_file_with_info_empty() {
let mut indices = AuxiliaryIndices::new();
let removed = indices.remove_file_with_info(test_file(1), std::iter::empty());
assert_eq!(removed, 0);
}
#[test]
fn test_remove_file_with_info_partial_mismatch() {
let mut indices = AuxiliaryIndices::new();
let file1 = test_file(1);
let id1 = test_id(1);
let name1 = test_name(1);
indices.add(id1, NodeKind::Function, name1, None, file1);
assert_eq!(indices.len(), 1);
let nodes = vec![
(id1, NodeKind::Function, name1, None),
(test_id(99), NodeKind::Class, test_name(99), None), ];
let removed = indices.remove_file_with_info(file1, nodes);
assert_eq!(removed, 1);
assert_eq!(indices.len(), 0);
}
#[test]
#[allow(deprecated)]
fn test_remove_file_deprecated() {
let mut indices = AuxiliaryIndices::new();
let file1 = test_file(1);
let file2 = test_file(2);
let id1 = test_id(1);
let id2 = test_id(2);
let id3 = test_id(3);
indices.add(id1, NodeKind::Function, test_name(1), None, file1);
indices.add(id2, NodeKind::Class, test_name(2), None, file1);
indices.add(id3, NodeKind::Method, test_name(3), None, file2);
assert_eq!(indices.len(), 3);
let removed = indices.remove_file(file1);
assert_eq!(removed.len(), 2);
assert!(removed.contains(&id1));
assert!(removed.contains(&id2));
assert_eq!(indices.len(), 1);
assert!(indices.by_file(file1).is_empty());
assert_eq!(indices.by_file(file2), &[id3]);
}
#[test]
#[allow(deprecated)]
fn test_remove_file_empty_deprecated() {
let mut indices = AuxiliaryIndices::new();
let removed = indices.remove_file(test_file(1));
assert!(removed.is_empty());
}
#[test]
fn test_clear() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
indices.add(
test_id(2),
NodeKind::Class,
test_name(2),
None,
test_file(2),
);
assert_eq!(indices.len(), 2);
indices.clear();
assert_eq!(indices.len(), 0);
assert!(indices.is_empty());
}
#[test]
fn test_reserve() {
let mut indices = AuxiliaryIndices::new();
indices.reserve(1000);
}
#[test]
fn test_display() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
let display = format!("{indices}");
assert!(display.contains("AuxiliaryIndices"));
assert!(display.contains("nodes=1"));
}
#[test]
fn test_stats() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
indices.add(
test_id(2),
NodeKind::Class,
test_name(2),
None,
test_file(1),
);
let stats = indices.stats();
assert_eq!(stats.node_count, 2);
assert_eq!(stats.kind_count, 2);
assert_eq!(stats.name_count, 2);
assert_eq!(stats.file_count, 1);
}
#[test]
fn test_default() {
let indices: AuxiliaryIndices = AuxiliaryIndices::default();
assert_eq!(indices.len(), 0);
}
#[test]
fn test_clone() {
let mut indices = AuxiliaryIndices::new();
indices.add(
test_id(1),
NodeKind::Function,
test_name(1),
None,
test_file(1),
);
let cloned = indices.clone();
assert_eq!(cloned.len(), 1);
assert_eq!(cloned.by_kind(NodeKind::Function).len(), 1);
}
#[test]
fn test_build_from_arena() {
use super::super::arena::{NodeArena, NodeEntry};
let mut arena = NodeArena::new();
let name1 = test_name(1);
let name2 = test_name(2);
let file1 = test_file(1);
let entry1 = NodeEntry::new(NodeKind::Function, name1, file1);
let entry2 = NodeEntry::new(NodeKind::Class, name2, file1);
let id1 = arena.alloc(entry1).unwrap();
let id2 = arena.alloc(entry2).unwrap();
let mut indices = AuxiliaryIndices::new();
indices.build_from_arena(&arena);
assert_eq!(indices.len(), 2);
assert_eq!(indices.by_kind(NodeKind::Function), &[id1]);
assert_eq!(indices.by_kind(NodeKind::Class), &[id2]);
assert_eq!(indices.by_name(name1), &[id1]);
assert_eq!(indices.by_name(name2), &[id2]);
assert_eq!(indices.by_file(file1).len(), 2);
assert!(indices.by_file(file1).contains(&id1));
assert!(indices.by_file(file1).contains(&id2));
}
#[test]
fn test_build_from_arena_with_qualified_names() {
use super::super::arena::{NodeArena, NodeEntry};
let mut arena = NodeArena::new();
let name1 = test_name(1);
let qname1 = test_name(10);
let file1 = test_file(1);
let entry = NodeEntry::new(NodeKind::Method, name1, file1).with_qualified_name(qname1);
let id = arena.alloc(entry).unwrap();
let mut indices = AuxiliaryIndices::new();
indices.build_from_arena(&arena);
assert_eq!(indices.len(), 1);
assert_eq!(indices.by_qualified_name(qname1), &[id]);
}
#[test]
fn test_deterministic_serialization() {
use super::super::arena::{NodeArena, NodeEntry};
let mut arena = NodeArena::new();
let names = [test_name(5), test_name(3), test_name(1)];
let files = [test_file(2), test_file(1), test_file(3)];
let kinds = [NodeKind::Class, NodeKind::Function, NodeKind::Method];
for i in 0..3 {
let entry = NodeEntry::new(kinds[i], names[i], files[i])
.with_qualified_name(test_name(100 + i as u32));
arena.alloc(entry).unwrap();
}
let mut indices1 = AuxiliaryIndices::new();
indices1.build_from_arena(&arena);
let mut indices2 = AuxiliaryIndices::new();
indices2.build_from_arena(&arena);
let bytes1 = postcard::to_stdvec(&indices1).expect("serialize indices1");
let bytes2 = postcard::to_stdvec(&indices2).expect("serialize indices2");
assert_eq!(
bytes1, bytes2,
"Two AuxiliaryIndices built from the same arena must produce identical serialized bytes"
);
}
}