use std::collections::{HashMap, HashSet};
use super::file_table::FileId;
#[derive(Debug, Default, Clone)]
pub struct DepGraph {
forward: HashMap<FileId, HashSet<FileId>>,
reverse: HashMap<FileId, HashSet<FileId>>,
unresolved: HashMap<FileId, Vec<String>>,
}
impl DepGraph {
pub fn new() -> Self {
Self::default()
}
pub fn set_edges(&mut self, file: FileId, resolved: HashSet<FileId>, unresolved: Vec<String>) {
if let Some(old) = self.forward.remove(&file) {
for target in &old {
if *target == file {
continue;
}
if let Some(set) = self.reverse.get_mut(target) {
set.remove(&file);
if set.is_empty() {
self.reverse.remove(target);
}
}
}
}
for target in &resolved {
if *target == file {
continue;
}
self.reverse.entry(*target).or_default().insert(file);
}
self.forward.insert(file, resolved);
if unresolved.is_empty() {
self.unresolved.remove(&file);
} else {
self.unresolved.insert(file, unresolved);
}
}
pub fn remove_file(&mut self, file: FileId) {
if let Some(old) = self.forward.remove(&file) {
for target in old {
if target == file {
continue;
}
if let Some(set) = self.reverse.get_mut(&target) {
set.remove(&file);
if set.is_empty() {
self.reverse.remove(&target);
}
}
}
}
self.unresolved.remove(&file);
if let Some(rev) = self.reverse.remove(&file) {
for importer in rev {
if let Some(forward) = self.forward.get_mut(&importer) {
forward.remove(&file);
}
}
}
}
pub fn imports_of(&self, file: FileId) -> Vec<FileId> {
self.forward
.get(&file)
.map(|set| set.iter().copied().collect())
.unwrap_or_default()
}
pub fn importers_of(&self, file: FileId) -> Vec<FileId> {
self.reverse
.get(&file)
.map(|set| set.iter().copied().collect())
.unwrap_or_default()
}
pub fn unresolved_imports(&self, file: FileId) -> &[String] {
self.unresolved.get(&file).map(Vec::as_slice).unwrap_or(&[])
}
pub fn snapshot_rows(&self) -> Vec<super::snapshot::DepRow> {
let mut keys: HashSet<FileId> = HashSet::new();
keys.extend(self.forward.keys().copied());
keys.extend(self.unresolved.keys().copied());
let mut keys: Vec<FileId> = keys.into_iter().collect();
keys.sort_unstable();
keys.into_iter()
.map(|from| {
let mut to: Vec<FileId> = self
.forward
.get(&from)
.map(|s| s.iter().copied().collect())
.unwrap_or_default();
to.sort_unstable();
let unresolved = self.unresolved.get(&from).cloned().unwrap_or_default();
super::snapshot::DepRow {
from,
to,
unresolved,
}
})
.collect()
}
pub fn from_rows(rows: Vec<super::snapshot::DepRow>) -> Self {
let mut g = Self::new();
for row in rows {
let resolved: HashSet<FileId> = row.to.into_iter().collect();
g.set_edges(row.from, resolved, row.unresolved);
}
g
}
}
#[cfg(test)]
mod tests {
use super::*;
fn set_of(ids: &[FileId]) -> HashSet<FileId> {
ids.iter().copied().collect()
}
#[test]
fn set_edges_populates_both_sides() {
let mut g = DepGraph::new();
g.set_edges(1, set_of(&[2, 3]), vec![]);
let mut imports = g.imports_of(1);
imports.sort();
assert_eq!(imports, vec![2, 3]);
assert_eq!(g.importers_of(2), vec![1]);
assert_eq!(g.importers_of(3), vec![1]);
}
#[test]
fn re_setting_edges_drops_stale_reverse_edges() {
let mut g = DepGraph::new();
g.set_edges(1, set_of(&[2]), vec![]);
g.set_edges(1, set_of(&[3]), vec![]);
assert!(g.importers_of(2).is_empty());
assert_eq!(g.importers_of(3), vec![1]);
}
#[test]
fn remove_file_cleans_up_both_directions() {
let mut g = DepGraph::new();
g.set_edges(1, set_of(&[2]), vec!["unmatched".into()]);
g.set_edges(3, set_of(&[1]), vec![]);
g.remove_file(1);
assert!(g.imports_of(1).is_empty());
assert!(g.importers_of(2).is_empty());
assert!(g.imports_of(3).is_empty());
}
#[test]
fn unresolved_imports_round_trip() {
let mut g = DepGraph::new();
g.set_edges(1, HashSet::new(), vec!["weird::path".into()]);
assert_eq!(g.unresolved_imports(1), &["weird::path".to_string()]);
g.set_edges(1, HashSet::new(), vec![]);
assert!(g.unresolved_imports(1).is_empty());
}
}