use crate::core::{CallGraph, SymbolId, TrackedSymbol};
use std::collections::{HashMap, HashSet};
pub fn find_transitive_dead(
unreachable: &[TrackedSymbol],
call_graph: &CallGraph,
) -> (
Vec<TrackedSymbol>,
Vec<(TrackedSymbol, Vec<SymbolId>, SymbolId)>,
) {
let unreachable_ids: HashSet<SymbolId> = unreachable.iter().map(|s| s.id).collect();
let mut referenced_by: HashMap<SymbolId, Vec<SymbolId>> = HashMap::new();
for symbol in unreachable {
referenced_by.insert(symbol.id, Vec::new());
}
for symbol in unreachable {
for &ref_id in call_graph.get_incoming_refs(symbol.id) {
if unreachable_ids.contains(&ref_id) {
referenced_by.entry(symbol.id).or_default().push(ref_id);
}
}
}
let mut root_dead: Vec<SymbolId> = Vec::new();
let mut has_unreachable_refs: Vec<SymbolId> = Vec::new();
for symbol in unreachable {
let refs = referenced_by.get(&symbol.id).map_or(0, |v| v.len());
if refs == 0 {
root_dead.push(symbol.id);
} else {
has_unreachable_refs.push(symbol.id);
}
}
let symbol_map: HashMap<SymbolId, &TrackedSymbol> =
unreachable.iter().map(|s| (s.id, s)).collect();
let mut directly_dead = Vec::new();
let mut transitively_dead = Vec::new();
for id in root_dead {
if let Some(symbol) = symbol_map.get(&id) {
directly_dead.push((*symbol).clone());
}
}
for id in has_unreachable_refs {
if let Some(symbol) = symbol_map.get(&id) {
let chain: Vec<SymbolId> = referenced_by
.get(&id)
.cloned()
.unwrap_or_default();
if let Some(&killed_by) = chain.first() {
transitively_dead.push(((*symbol).clone(), chain, killed_by));
} else {
directly_dead.push((*symbol).clone());
}
}
}
(directly_dead, transitively_dead)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{FileId, Location, ReferenceKind, SymbolKind, SymbolReference};
use std::path::PathBuf;
fn make_symbol(id: u32, name: &str) -> TrackedSymbol {
TrackedSymbol::new(
SymbolId::new(id),
name.to_string(),
SymbolKind::Function,
Location::new(PathBuf::from("test.ts"), 0, 10, 1, 1),
FileId::new(0),
)
}
#[test]
fn test_isolated_symbols_are_directly_dead() {
let graph = CallGraph::new();
let unreachable = vec![make_symbol(0, "foo"), make_symbol(1, "bar")];
let (directly_dead, transitively_dead) = find_transitive_dead(&unreachable, &graph);
assert_eq!(directly_dead.len(), 2);
assert!(transitively_dead.is_empty());
}
#[test]
fn test_transitive_dead_detection() {
let mut graph = CallGraph::new();
graph.add_reference(SymbolReference::new(
SymbolId::new(0),
SymbolId::new(1),
ReferenceKind::Call,
Location::new(PathBuf::from("test.ts"), 0, 10, 1, 1),
));
let unreachable = vec![make_symbol(0, "foo"), make_symbol(1, "bar")];
let (directly_dead, transitively_dead) = find_transitive_dead(&unreachable, &graph);
assert_eq!(directly_dead.len(), 1);
assert_eq!(directly_dead[0].name, "foo");
assert_eq!(transitively_dead.len(), 1);
assert_eq!(transitively_dead[0].0.name, "bar");
}
}