use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use super::super::file::FileId;
use super::super::node::NodeId;
use super::super::string::StringId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdentityKey {
pub language: StringId,
pub file: FileId,
pub qualified_name: StringId,
}
impl IdentityKey {
#[must_use]
pub fn new(language: StringId, file: FileId, qualified_name: StringId) -> Self {
Self {
language,
file,
qualified_name,
}
}
}
impl Hash for IdentityKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.language.hash(state);
self.file.hash(state);
self.qualified_name.hash(state);
}
}
#[derive(Debug, Default)]
pub struct IdentityIndex {
index: HashMap<IdentityKey, NodeId>,
by_file: HashMap<FileId, Vec<IdentityKey>>,
by_node: HashMap<NodeId, IdentityKey>,
}
impl IdentityIndex {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
index: HashMap::with_capacity(capacity),
by_file: HashMap::new(),
by_node: HashMap::new(),
}
}
pub fn insert(&mut self, key: IdentityKey, node_id: NodeId) -> Option<NodeId> {
let file = key.file;
let old = self.index.insert(key.clone(), node_id);
let entry = self.by_file.entry(file).or_default();
if !entry.contains(&key) {
entry.push(key.clone());
}
if let Some(prev) = old {
self.by_node.remove(&prev);
}
self.by_node.insert(node_id, key);
old
}
#[must_use]
pub fn get(&self, key: &IdentityKey) -> Option<NodeId> {
self.index.get(key).copied()
}
#[must_use]
pub fn contains(&self, key: &IdentityKey) -> bool {
self.index.contains_key(key)
}
pub fn remove(&mut self, key: &IdentityKey) -> Option<NodeId> {
let result = self.index.remove(key);
if let Some(node_id) = result {
self.by_node.remove(&node_id);
if let Some(keys) = self.by_file.get_mut(&key.file) {
keys.retain(|existing| existing != key);
if keys.is_empty() {
self.by_file.remove(&key.file);
}
}
}
result
}
pub fn remove_file(&mut self, file: FileId) -> Vec<(IdentityKey, NodeId)> {
let keys = self.by_file.remove(&file).unwrap_or_default();
let mut removed = Vec::with_capacity(keys.len());
for key in keys {
if let Some(node_id) = self.index.remove(&key) {
self.by_node.remove(&node_id);
removed.push((key, node_id));
}
}
removed
}
#[must_use]
pub fn len(&self) -> usize {
self.index.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
#[must_use]
pub fn file_count(&self) -> usize {
self.by_file.len()
}
#[must_use]
pub fn nodes_in_file(&self, file: FileId) -> Vec<NodeId> {
self.by_file
.get(&file)
.map(|keys| {
keys.iter()
.filter_map(|key| self.index.get(key).copied())
.collect()
})
.unwrap_or_default()
}
pub fn iter(&self) -> impl Iterator<Item = (&IdentityKey, &NodeId)> {
self.index.iter()
}
pub fn keys_in_file(&self, file: FileId) -> &[IdentityKey] {
self.by_file.get(&file).map_or(&[], Vec::as_slice)
}
pub fn clear(&mut self) {
self.index.clear();
self.by_file.clear();
self.by_node.clear();
}
pub fn remove_node_id(&mut self, node_id: NodeId) -> Option<(IdentityKey, NodeId)> {
let key = self.by_node.remove(&node_id)?;
let removed = self.index.remove(&key);
if let Some(keys) = self.by_file.get_mut(&key.file) {
keys.retain(|existing| existing != &key);
if keys.is_empty() {
self.by_file.remove(&key.file);
}
}
removed.map(|id| (key, id))
}
}
pub struct IdentityKeyBuilder<'a> {
strings: &'a mut super::super::storage::StringInterner,
}
impl<'a> IdentityKeyBuilder<'a> {
pub fn new(strings: &'a mut super::super::storage::StringInterner) -> Self {
Self { strings }
}
pub fn build(
&mut self,
language: &str,
file: FileId,
qualified_name: &str,
) -> Result<IdentityKey, super::super::storage::InternError> {
Ok(IdentityKey {
language: self.strings.intern(language)?,
file,
qualified_name: self.strings.intern(qualified_name)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::unified::storage::StringInterner;
fn make_key(lang: u32, file: u32, name: u32) -> IdentityKey {
IdentityKey::new(StringId::new(lang), FileId::new(file), StringId::new(name))
}
#[test]
fn test_identity_key_equality() {
let key1 = make_key(1, 2, 3);
let key2 = make_key(1, 2, 3);
let key3 = make_key(1, 2, 4);
assert_eq!(key1, key2);
assert_ne!(key1, key3);
}
#[test]
fn test_identity_index_insert_and_get() {
let mut index = IdentityIndex::new();
let key = make_key(1, 2, 3);
let node_id = NodeId::new(10, 1);
assert!(index.insert(key.clone(), node_id).is_none());
assert_eq!(index.get(&key), Some(node_id));
assert!(index.contains(&key));
}
#[test]
fn test_identity_index_replace() {
let mut index = IdentityIndex::new();
let key = make_key(1, 2, 3);
let old_id = NodeId::new(10, 1);
let new_id = NodeId::new(20, 2);
index.insert(key.clone(), old_id);
let replaced = index.insert(key.clone(), new_id);
assert_eq!(replaced, Some(old_id));
assert_eq!(index.get(&key), Some(new_id));
}
#[test]
fn test_identity_index_remove() {
let mut index = IdentityIndex::new();
let key = make_key(1, 2, 3);
let node_id = NodeId::new(10, 1);
index.insert(key.clone(), node_id);
let removed = index.remove(&key);
assert_eq!(removed, Some(node_id));
assert!(!index.contains(&key));
assert!(!index.by_node.contains_key(&node_id));
}
#[test]
fn test_identity_index_remove_file() {
let mut index = IdentityIndex::new();
let file_id = FileId::new(5);
let key1 = IdentityKey::new(StringId::new(1), file_id, StringId::new(10));
let key2 = IdentityKey::new(StringId::new(1), file_id, StringId::new(11));
let key3 = IdentityKey::new(StringId::new(1), FileId::new(6), StringId::new(12));
index.insert(key1.clone(), NodeId::new(1, 1));
index.insert(key2.clone(), NodeId::new(2, 1));
index.insert(key3.clone(), NodeId::new(3, 1));
assert_eq!(index.len(), 3);
let removed = index.remove_file(file_id);
assert_eq!(removed.len(), 2);
assert_eq!(index.len(), 1);
assert!(!index.contains(&key1));
assert!(!index.contains(&key2));
assert!(index.contains(&key3));
}
#[test]
fn test_identity_index_nodes_in_file() {
let mut index = IdentityIndex::new();
let file_id = FileId::new(5);
let key1 = IdentityKey::new(StringId::new(1), file_id, StringId::new(10));
let key2 = IdentityKey::new(StringId::new(1), file_id, StringId::new(11));
let node1 = NodeId::new(1, 1);
let node2 = NodeId::new(2, 1);
index.insert(key1, node1);
index.insert(key2, node2);
let file_nodes = index.nodes_in_file(file_id);
assert_eq!(file_nodes.len(), 2);
assert!(file_nodes.contains(&node1));
assert!(file_nodes.contains(&node2));
}
#[test]
fn test_identity_key_builder() {
let mut strings = StringInterner::new();
let mut builder = IdentityKeyBuilder::new(&mut strings);
let key = builder
.build("rust", FileId::new(0), "my_module::MyClass")
.expect("build should succeed");
assert_eq!(key.file, FileId::new(0));
assert_eq!(strings.resolve(key.language).as_deref(), Some("rust"));
assert_eq!(
strings.resolve(key.qualified_name).as_deref(),
Some("my_module::MyClass")
);
}
#[test]
fn test_identity_index_with_capacity() {
let index = IdentityIndex::with_capacity(100);
assert!(index.is_empty());
}
#[test]
fn test_identity_index_clear() {
let mut index = IdentityIndex::new();
index.insert(make_key(1, 2, 3), NodeId::new(1, 1));
index.insert(make_key(1, 2, 4), NodeId::new(2, 1));
assert_eq!(index.len(), 2);
index.clear();
assert!(index.is_empty());
assert_eq!(index.file_count(), 0);
assert!(index.by_node.is_empty());
}
#[test]
fn test_identity_index_remove_node_id() {
let mut index = IdentityIndex::new();
let key = make_key(1, 2, 3);
let node_id = NodeId::new(10, 1);
index.insert(key.clone(), node_id);
let removed = index.remove_node_id(node_id);
assert!(removed.is_some());
assert!(!index.contains(&key));
assert!(!index.by_node.contains_key(&node_id));
assert!(!index.by_file.contains_key(&key.file));
}
}