use rustc_hash::FxHashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct TypePosition {
pub file_uri: String,
pub line: u32,
pub character: u32,
}
impl TypePosition {
pub fn new(file_uri: impl Into<String>, line: u32, character: u32) -> Self {
Self {
file_uri: file_uri.into(),
line,
character,
}
}
}
#[derive(Debug, Default)]
pub struct TypeCache {
entries: FxHashMap<TypePosition, String>,
hits: AtomicUsize,
misses: AtomicUsize,
}
#[allow(dead_code)]
impl TypeCache {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, pos: &TypePosition) -> Option<&String> {
let result = self.entries.get(pos);
if result.is_some() {
self.hits.fetch_add(1, Ordering::Relaxed);
} else {
self.misses.fetch_add(1, Ordering::Relaxed);
}
result
}
pub fn contains(&self, pos: &TypePosition) -> bool {
self.entries.contains_key(pos)
}
pub fn insert(&mut self, pos: TypePosition, type_str: String) {
self.entries.insert(pos, type_str);
}
pub fn stats(&self) -> (usize, usize) {
(
self.hits.load(Ordering::Relaxed),
self.misses.load(Ordering::Relaxed),
)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
}
pub fn clear_for_file(&mut self, file_uri: &str) {
self.entries.retain(|pos, _| pos.file_uri != file_uri);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_position_equality() {
let pos1 = TypePosition::new("file:///test.ts", 10, 5);
let pos2 = TypePosition::new("file:///test.ts", 10, 5);
let pos3 = TypePosition::new("file:///test.ts", 10, 6);
assert_eq!(pos1, pos2);
assert_ne!(pos1, pos3);
}
#[test]
fn test_cache_insert_and_get() {
let mut cache = TypeCache::new();
let pos = TypePosition::new("file:///test.ts", 10, 5);
assert!(cache.get(&pos).is_none());
assert_eq!(cache.stats(), (0, 1));
cache.insert(pos.clone(), "string".to_string());
assert_eq!(cache.get(&pos), Some(&"string".to_string()));
assert_eq!(cache.stats(), (1, 1)); }
#[test]
fn test_cache_contains() {
let mut cache = TypeCache::new();
let pos = TypePosition::new("file:///test.ts", 10, 5);
assert!(!cache.contains(&pos));
cache.insert(pos.clone(), "number".to_string());
assert!(cache.contains(&pos));
assert_eq!(cache.stats(), (0, 0));
}
#[test]
fn test_cache_clear() {
let mut cache = TypeCache::new();
let pos = TypePosition::new("file:///test.ts", 10, 5);
cache.insert(pos.clone(), "boolean".to_string());
assert_eq!(cache.len(), 1);
cache.get(&pos);
cache.get(&TypePosition::new("other.ts", 0, 0));
assert_eq!(cache.stats(), (1, 1));
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.stats(), (0, 0));
}
#[test]
fn test_cache_multiple_entries() {
let mut cache = TypeCache::new();
let positions = vec![
(TypePosition::new("file:///a.ts", 0, 0), "string"),
(TypePosition::new("file:///a.ts", 0, 10), "number"),
(TypePosition::new("file:///b.ts", 5, 3), "boolean"),
];
for (pos, type_str) in &positions {
cache.insert(pos.clone(), type_str.to_string());
}
assert_eq!(cache.len(), 3);
for (pos, expected) in &positions {
assert_eq!(cache.get(pos), Some(&expected.to_string()));
}
assert_eq!(cache.stats(), (3, 0));
}
}