#![cfg_attr(coverage_nightly, coverage(off))]
use super::*;
use rustc_hash::FxHashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct SymbolTable {
symbols: FxHashMap<String, Vec<SymbolEntry>>,
file_symbols: FxHashMap<PathBuf, Vec<String>>,
module_map: FxHashMap<String, PathBuf>,
}
#[derive(Debug, Clone)]
pub struct SymbolEntry {
pub symbol: Symbol,
pub file_path: PathBuf,
pub module_path: String,
pub usage_count: usize,
pub is_exported: bool,
}
impl SymbolTable {
pub fn new() -> Self {
SymbolTable {
symbols: FxHashMap::default(),
file_symbols: FxHashMap::default(),
module_map: FxHashMap::default(),
}
}
pub fn insert(&mut self, name: String, entry: SymbolEntry) {
self.symbols
.entry(name.clone())
.or_default()
.push(entry.clone());
self.file_symbols
.entry(entry.file_path.clone())
.or_default()
.push(name);
self.module_map
.insert(entry.module_path.clone(), entry.file_path);
}
pub fn resolve(&self, name: &str, from_module: &str) -> Option<&SymbolEntry> {
self.symbols.get(name).and_then(|entries| {
entries.iter().find(|entry| {
self.is_visible(entry, from_module)
})
})
}
fn is_visible(&self, entry: &SymbolEntry, from_module: &str) -> bool {
match entry.symbol.visibility {
Visibility::Public => true,
Visibility::Private => {
entry.module_path == from_module
}
Visibility::Protected => {
from_module.starts_with(&entry.module_path)
}
}
}
pub fn get_file_symbols(&self, path: &Path) -> Vec<&SymbolEntry> {
self.file_symbols
.get(path)
.map(|names| {
names
.iter()
.filter_map(|name| {
self.symbols
.get(name)
.and_then(|entries| entries.iter().find(|e| e.file_path == path))
})
.collect()
})
.unwrap_or_default()
}
pub fn increment_usage(&mut self, name: &str, from_module: &str) {
if let Some(entries) = self.symbols.get_mut(name) {
for entry in entries.iter_mut() {
let is_visible = match entry.symbol.visibility {
Visibility::Public => true,
Visibility::Private => entry.module_path == from_module,
Visibility::Protected => from_module.starts_with(&entry.module_path),
};
if is_visible {
entry.usage_count += 1;
break;
}
}
}
}
pub fn len(&self) -> usize {
self.symbols.len()
}
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_table_insertion() {
let mut table = SymbolTable::new();
let entry = SymbolEntry {
symbol: Symbol {
name: "test_func".to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
line: 10,
},
file_path: PathBuf::from("src/lib.rs"),
module_path: "lib".to_string(),
usage_count: 0,
is_exported: true,
};
table.insert("test_func".to_string(), entry);
assert_eq!(table.len(), 1);
assert!(!table.is_empty());
}
#[test]
fn test_symbol_resolution_cross_module() {
let mut table = SymbolTable::new();
let public_entry = SymbolEntry {
symbol: Symbol {
name: "public_func".to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
line: 10,
},
file_path: PathBuf::from("src/mod_a.rs"),
module_path: "mod_a".to_string(),
usage_count: 0,
is_exported: true,
};
let private_entry = SymbolEntry {
symbol: Symbol {
name: "private_func".to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Private,
line: 20,
},
file_path: PathBuf::from("src/mod_a.rs"),
module_path: "mod_a".to_string(),
usage_count: 0,
is_exported: false,
};
table.insert("public_func".to_string(), public_entry);
table.insert("private_func".to_string(), private_entry);
let resolved = table.resolve("public_func", "mod_b");
assert!(resolved.is_some());
assert_eq!(resolved.unwrap().symbol.name, "public_func");
let resolved = table.resolve("private_func", "mod_b");
assert!(resolved.is_none());
let resolved = table.resolve("private_func", "mod_a");
assert!(resolved.is_some());
}
#[test]
fn test_visibility_rules() {
let mut table = SymbolTable::new();
let protected_entry = SymbolEntry {
symbol: Symbol {
name: "protected_func".to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Protected,
line: 30,
},
file_path: PathBuf::from("src/parent.rs"),
module_path: "parent".to_string(),
usage_count: 0,
is_exported: true,
};
table.insert("protected_func".to_string(), protected_entry);
let resolved = table.resolve("protected_func", "parent::child");
assert!(resolved.is_some());
let resolved = table.resolve("protected_func", "sibling");
assert!(resolved.is_none());
}
#[test]
fn test_usage_count_tracking() {
let mut table = SymbolTable::new();
let entry = SymbolEntry {
symbol: Symbol {
name: "tracked_func".to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
line: 10,
},
file_path: PathBuf::from("src/lib.rs"),
module_path: "lib".to_string(),
usage_count: 0,
is_exported: true,
};
table.insert("tracked_func".to_string(), entry);
table.increment_usage("tracked_func", "other_mod");
table.increment_usage("tracked_func", "another_mod");
let resolved = table.resolve("tracked_func", "lib").unwrap();
assert_eq!(resolved.usage_count, 2);
}
#[test]
fn test_file_symbols_retrieval() {
let mut table = SymbolTable::new();
let path = PathBuf::from("src/test.rs");
for i in 0..3 {
let entry = SymbolEntry {
symbol: Symbol {
name: format!("func_{}", i),
kind: SymbolKind::Function,
visibility: Visibility::Public,
line: i * 10,
},
file_path: path.clone(),
module_path: "test".to_string(),
usage_count: 0,
is_exported: true,
};
table.insert(format!("func_{}", i), entry);
}
let file_symbols = table.get_file_symbols(&path);
assert_eq!(file_symbols.len(), 3);
}
}