harn_hostlib/code_index/
graph.rs1use std::collections::{HashMap, HashSet};
10
11use super::file_table::FileId;
12
13#[derive(Debug, Default, Clone)]
16pub struct DepGraph {
17 forward: HashMap<FileId, HashSet<FileId>>,
18 reverse: HashMap<FileId, HashSet<FileId>>,
19 unresolved: HashMap<FileId, Vec<String>>,
20}
21
22impl DepGraph {
23 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn set_edges(&mut self, file: FileId, resolved: HashSet<FileId>, unresolved: Vec<String>) {
32 if let Some(old) = self.forward.remove(&file) {
33 for target in &old {
34 if *target == file {
35 continue;
36 }
37 if let Some(set) = self.reverse.get_mut(target) {
38 set.remove(&file);
39 if set.is_empty() {
40 self.reverse.remove(target);
41 }
42 }
43 }
44 }
45 for target in &resolved {
46 if *target == file {
47 continue;
48 }
49 self.reverse.entry(*target).or_default().insert(file);
50 }
51 self.forward.insert(file, resolved);
52 if unresolved.is_empty() {
53 self.unresolved.remove(&file);
54 } else {
55 self.unresolved.insert(file, unresolved);
56 }
57 }
58
59 pub fn remove_file(&mut self, file: FileId) {
62 if let Some(old) = self.forward.remove(&file) {
63 for target in old {
64 if target == file {
65 continue;
66 }
67 if let Some(set) = self.reverse.get_mut(&target) {
68 set.remove(&file);
69 if set.is_empty() {
70 self.reverse.remove(&target);
71 }
72 }
73 }
74 }
75 self.unresolved.remove(&file);
76 if let Some(rev) = self.reverse.remove(&file) {
79 for importer in rev {
80 if let Some(forward) = self.forward.get_mut(&importer) {
81 forward.remove(&file);
82 }
83 }
84 }
85 }
86
87 pub fn imports_of(&self, file: FileId) -> Vec<FileId> {
89 self.forward
90 .get(&file)
91 .map(|set| set.iter().copied().collect())
92 .unwrap_or_default()
93 }
94
95 pub fn importers_of(&self, file: FileId) -> Vec<FileId> {
97 self.reverse
98 .get(&file)
99 .map(|set| set.iter().copied().collect())
100 .unwrap_or_default()
101 }
102
103 pub fn unresolved_imports(&self, file: FileId) -> &[String] {
105 self.unresolved.get(&file).map(Vec::as_slice).unwrap_or(&[])
106 }
107
108 pub fn snapshot_rows(&self) -> Vec<super::snapshot::DepRow> {
111 let mut keys: HashSet<FileId> = HashSet::new();
112 keys.extend(self.forward.keys().copied());
113 keys.extend(self.unresolved.keys().copied());
114 let mut keys: Vec<FileId> = keys.into_iter().collect();
115 keys.sort_unstable();
116 keys.into_iter()
117 .map(|from| {
118 let mut to: Vec<FileId> = self
119 .forward
120 .get(&from)
121 .map(|s| s.iter().copied().collect())
122 .unwrap_or_default();
123 to.sort_unstable();
124 let unresolved = self.unresolved.get(&from).cloned().unwrap_or_default();
125 super::snapshot::DepRow {
126 from,
127 to,
128 unresolved,
129 }
130 })
131 .collect()
132 }
133
134 pub fn from_rows(rows: Vec<super::snapshot::DepRow>) -> Self {
137 let mut g = Self::new();
138 for row in rows {
139 let resolved: HashSet<FileId> = row.to.into_iter().collect();
140 g.set_edges(row.from, resolved, row.unresolved);
141 }
142 g
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn set_of(ids: &[FileId]) -> HashSet<FileId> {
151 ids.iter().copied().collect()
152 }
153
154 #[test]
155 fn set_edges_populates_both_sides() {
156 let mut g = DepGraph::new();
157 g.set_edges(1, set_of(&[2, 3]), vec![]);
158 let mut imports = g.imports_of(1);
159 imports.sort();
160 assert_eq!(imports, vec![2, 3]);
161 assert_eq!(g.importers_of(2), vec![1]);
162 assert_eq!(g.importers_of(3), vec![1]);
163 }
164
165 #[test]
166 fn re_setting_edges_drops_stale_reverse_edges() {
167 let mut g = DepGraph::new();
168 g.set_edges(1, set_of(&[2]), vec![]);
169 g.set_edges(1, set_of(&[3]), vec![]);
170 assert!(g.importers_of(2).is_empty());
171 assert_eq!(g.importers_of(3), vec![1]);
172 }
173
174 #[test]
175 fn remove_file_cleans_up_both_directions() {
176 let mut g = DepGraph::new();
177 g.set_edges(1, set_of(&[2]), vec!["unmatched".into()]);
178 g.set_edges(3, set_of(&[1]), vec![]);
179 g.remove_file(1);
180 assert!(g.imports_of(1).is_empty());
181 assert!(g.importers_of(2).is_empty());
182 assert!(g.imports_of(3).is_empty());
184 }
185
186 #[test]
187 fn unresolved_imports_round_trip() {
188 let mut g = DepGraph::new();
189 g.set_edges(1, HashSet::new(), vec!["weird::path".into()]);
190 assert_eq!(g.unresolved_imports(1), &["weird::path".to_string()]);
191 g.set_edges(1, HashSet::new(), vec![]);
192 assert!(g.unresolved_imports(1).is_empty());
193 }
194}