1use std::collections::{HashMap, HashSet, VecDeque};
8
9use petgraph::stable_graph::{NodeIndex, StableDiGraph};
10use petgraph::visit::{EdgeRef, IntoEdgeReferences};
11
12use super::{HierarchyDirection, HierarchyKind, LoadState, NodeId, SourceLocation, SymbolIdentity};
13
14#[derive(Clone, Debug, Eq, Hash, PartialEq)]
15struct ResolvedSymbolKey {
16 kind: HierarchyKind,
17 location: SourceLocation,
18}
19
20impl ResolvedSymbolKey {
21 fn from_identity(identity: &SymbolIdentity) -> Option<Self> {
22 Some(Self {
23 kind: identity.kind,
24 location: identity.location.clone()?,
25 })
26 }
27}
28
29#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
30pub struct BranchKey {
31 pub node_id: NodeId,
32 pub direction: HierarchyDirection,
33}
34
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct GraphBranch {
37 pub expanded: bool,
38 pub load_state: LoadState,
39 pub neighbors: Vec<NodeId>,
40 pub failure: Option<String>,
41 pub(crate) active_request_id: Option<u64>,
42}
43
44impl GraphBranch {
45 pub fn can_toggle(&self) -> bool {
46 !self.neighbors.is_empty()
47 }
48
49 pub fn toggle(&mut self) -> bool {
50 if !self.can_toggle() {
51 return false;
52 }
53 self.expanded = !self.expanded;
54 true
55 }
56
57 pub fn failure(&self) -> Option<&str> {
58 self.failure.as_deref()
59 }
60}
61
62impl Default for GraphBranch {
63 fn default() -> Self {
64 Self {
65 expanded: false,
66 load_state: LoadState::NotLoaded,
67 neighbors: Vec::new(),
68 failure: None,
69 active_request_id: None,
70 }
71 }
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct GraphNode {
76 pub id: NodeId,
77 pub symbol: String,
78 pub kind: HierarchyKind,
79 pub location: Option<SourceLocation>,
80 pub incoming: GraphBranch,
81 pub outgoing: GraphBranch,
82}
83
84impl GraphNode {
85 fn new(identity: SymbolIdentity) -> Self {
86 Self {
87 id: NodeId::next(),
88 symbol: identity.symbol,
89 kind: identity.kind,
90 location: identity.location,
91 incoming: GraphBranch::default(),
92 outgoing: GraphBranch::default(),
93 }
94 }
95
96 pub fn identity(&self) -> SymbolIdentity {
97 SymbolIdentity {
98 symbol: self.symbol.clone(),
99 kind: self.kind,
100 location: self.location.clone(),
101 }
102 }
103
104 pub fn branch(&self, direction: HierarchyDirection) -> &GraphBranch {
105 match direction {
106 HierarchyDirection::Incoming => &self.incoming,
107 HierarchyDirection::Outgoing => &self.outgoing,
108 }
109 }
110
111 pub fn branch_mut(&mut self, direction: HierarchyDirection) -> &mut GraphBranch {
112 match direction {
113 HierarchyDirection::Incoming => &mut self.incoming,
114 HierarchyDirection::Outgoing => &mut self.outgoing,
115 }
116 }
117}
118
119#[derive(Clone, Debug, Eq, PartialEq)]
120pub struct RelationEdge {
121 pub observed_by: Vec<BranchKey>,
122}
123
124#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
125pub struct VisibleEdge {
126 pub source: NodeId,
127 pub target: NodeId,
128}
129
130#[derive(Clone, Debug, Default, Eq, PartialEq)]
131pub struct VisibleGraph {
132 pub nodes: Vec<NodeId>,
133 pub edges: Vec<VisibleEdge>,
134}
135
136#[derive(Debug, Default)]
137pub struct RelationGraph {
138 graph: StableDiGraph<GraphNode, RelationEdge>,
139 by_id: HashMap<NodeId, NodeIndex>,
140 by_identity: HashMap<ResolvedSymbolKey, NodeId>,
141 redirects: HashMap<NodeId, NodeId>,
142 anchors: Vec<NodeId>,
143}
144
145impl RelationGraph {
146 pub fn insert_symbol(&mut self, identity: SymbolIdentity) -> NodeId {
147 if let Some(key) = ResolvedSymbolKey::from_identity(&identity)
148 && let Some(existing) = self.by_identity.get(&key)
149 {
150 return *existing;
151 }
152
153 let node = GraphNode::new(identity);
154 let node_id = node.id;
155 let key = ResolvedSymbolKey::from_identity(&node.identity());
156 let index = self.graph.add_node(node);
157 self.by_id.insert(node_id, index);
158 if let Some(key) = key {
159 self.by_identity.insert(key, node_id);
160 }
161 node_id
162 }
163
164 pub fn pin_symbol(&mut self, identity: SymbolIdentity) -> NodeId {
165 let node_id = self.insert_symbol(identity);
166 self.pin(node_id);
167 node_id
168 }
169
170 pub fn pin(&mut self, node_id: NodeId) -> bool {
171 let Some(node_id) = self.resolve_id(node_id) else {
172 return false;
173 };
174 if !self.anchors.contains(&node_id) {
175 self.anchors.push(node_id);
176 }
177 true
178 }
179
180 pub fn unpin(&mut self, node_id: NodeId) -> bool {
181 let Some(node_id) = self.resolve_id(node_id) else {
182 return false;
183 };
184 let Some(index) = self.anchors.iter().position(|anchor| *anchor == node_id) else {
185 return false;
186 };
187 self.anchors.remove(index);
188 true
189 }
190
191 pub fn anchors(&self) -> &[NodeId] {
192 &self.anchors
193 }
194
195 pub fn is_anchor(&self, node_id: NodeId) -> bool {
196 self.resolve_id(node_id)
197 .is_some_and(|node_id| self.anchors.contains(&node_id))
198 }
199
200 pub fn contains(&self, node_id: NodeId) -> bool {
201 self.resolve_id(node_id).is_some()
202 }
203
204 pub fn node(&self, node_id: NodeId) -> Option<&GraphNode> {
205 let node_id = self.resolve_id(node_id)?;
206 self.by_id
207 .get(&node_id)
208 .and_then(|index| self.graph.node_weight(*index))
209 }
210
211 pub fn node_mut(&mut self, node_id: NodeId) -> Option<&mut GraphNode> {
212 let node_id = self.resolve_id(node_id)?;
213 let index = *self.by_id.get(&node_id)?;
214 self.graph.node_weight_mut(index)
215 }
216
217 pub fn nodes_named(&self, symbol: &str, kind: HierarchyKind) -> Vec<NodeId> {
218 self.graph
219 .node_weights()
220 .filter(|node| node.symbol == symbol && node.kind == kind)
221 .map(|node| node.id)
222 .collect()
223 }
224
225 pub fn resolve_id(&self, mut node_id: NodeId) -> Option<NodeId> {
226 let mut visited = HashSet::new();
227 while let Some(next) = self.redirects.get(&node_id) {
228 if !visited.insert(node_id) {
229 return None;
230 }
231 node_id = *next;
232 }
233 self.by_id.contains_key(&node_id).then_some(node_id)
234 }
235
236 pub fn resolve_symbol(&mut self, node_id: NodeId, identity: SymbolIdentity) -> Option<NodeId> {
237 let node_id = self.resolve_id(node_id)?;
238 let new_key = ResolvedSymbolKey::from_identity(&identity);
239 if let Some(existing) = new_key
240 .as_ref()
241 .and_then(|key| self.by_identity.get(key))
242 .copied()
243 && existing != node_id
244 {
245 return Some(self.merge_nodes(node_id, existing, identity));
246 }
247
248 if let Some(old_key) = self
249 .node(node_id)
250 .and_then(|node| ResolvedSymbolKey::from_identity(&node.identity()))
251 {
252 self.by_identity.remove(&old_key);
253 }
254 let node = self.node_mut(node_id)?;
255 node.symbol = identity.symbol;
256 node.kind = identity.kind;
257 node.location = identity.location;
258 if let Some(key) = new_key {
259 self.by_identity.insert(key, node_id);
260 }
261 Some(node_id)
262 }
263
264 pub fn replace_branch_neighbors(
265 &mut self,
266 node_id: NodeId,
267 direction: HierarchyDirection,
268 children: Vec<SymbolIdentity>,
269 ) -> Option<Vec<NodeId>> {
270 let node_id = self.resolve_id(node_id)?;
271 let owner = BranchKey { node_id, direction };
272 let old_neighbors = self.node(node_id)?.branch(direction).neighbors.clone();
273 for neighbor in old_neighbors {
274 self.remove_observation(node_id, neighbor, owner);
275 }
276
277 let mut seen = HashSet::new();
278 let mut neighbors = Vec::new();
279 for child in children {
280 let child_id = self.insert_symbol(child);
281 if !seen.insert(child_id) {
282 continue;
283 }
284 self.observe_relation(node_id, child_id, owner);
285 neighbors.push(child_id);
286 }
287 self.node_mut(node_id)?.branch_mut(direction).neighbors = neighbors.clone();
288 Some(neighbors)
289 }
290
291 pub fn clear_branch(&mut self, node_id: NodeId, direction: HierarchyDirection) -> bool {
292 let Some(node_id) = self.resolve_id(node_id) else {
293 return false;
294 };
295 let owner = BranchKey { node_id, direction };
296 let neighbors = self
297 .node(node_id)
298 .map(|node| node.branch(direction).neighbors.clone())
299 .unwrap_or_default();
300 for neighbor in neighbors {
301 self.remove_observation(node_id, neighbor, owner);
302 }
303 let Some(node) = self.node_mut(node_id) else {
304 return false;
305 };
306 *node.branch_mut(direction) = GraphBranch::default();
307 true
308 }
309
310 pub fn visible_graph(&self) -> VisibleGraph {
311 self.project_graph(true)
312 }
313
314 pub fn known_graph(&self) -> VisibleGraph {
318 self.project_graph(false)
319 }
320
321 fn project_graph(&self, expanded_only: bool) -> VisibleGraph {
322 let mut visible = VisibleGraph::default();
323 let mut seen_nodes = HashSet::new();
324 let mut seen_edges = HashSet::new();
325 let mut queue = VecDeque::new();
326
327 for anchor in &self.anchors {
328 if self.contains(*anchor) && seen_nodes.insert(*anchor) {
329 visible.nodes.push(*anchor);
330 queue.push_back(*anchor);
331 }
332 }
333
334 while let Some(node_id) = queue.pop_front() {
335 let Some(node) = self.node(node_id) else {
336 continue;
337 };
338 for direction in [HierarchyDirection::Incoming, HierarchyDirection::Outgoing] {
339 let branch = node.branch(direction);
340 if expanded_only && !branch.expanded {
341 continue;
342 }
343 for neighbor in &branch.neighbors {
344 let Some(neighbor) = self.resolve_id(*neighbor) else {
345 continue;
346 };
347 let edge = canonical_edge(node_id, neighbor, direction);
348 if self.has_edge(edge.source, edge.target) && seen_edges.insert(edge) {
349 visible.edges.push(edge);
350 }
351 if seen_nodes.insert(neighbor) {
352 visible.nodes.push(neighbor);
353 queue.push_back(neighbor);
354 }
355 }
356 }
357 }
358 visible
359 }
360
361 pub fn node_count(&self) -> usize {
362 self.graph.node_count()
363 }
364
365 pub fn edge_count(&self) -> usize {
366 self.graph.edge_count()
367 }
368
369 fn observe_relation(&mut self, node_id: NodeId, neighbor: NodeId, owner: BranchKey) {
370 let edge = canonical_edge(node_id, neighbor, owner.direction);
371 let Some(source) = self.resolve_id(edge.source) else {
372 return;
373 };
374 let Some(target) = self.resolve_id(edge.target) else {
375 return;
376 };
377 let source_index = self.by_id[&source];
378 let target_index = self.by_id[&target];
379 if let Some(edge_index) = self.graph.find_edge(source_index, target_index) {
380 let relation = self
381 .graph
382 .edge_weight_mut(edge_index)
383 .expect("found edges have weights");
384 if !relation.observed_by.contains(&owner) {
385 relation.observed_by.push(owner);
386 }
387 } else {
388 self.graph.add_edge(
389 source_index,
390 target_index,
391 RelationEdge {
392 observed_by: vec![owner],
393 },
394 );
395 }
396 }
397
398 fn remove_observation(&mut self, node_id: NodeId, neighbor: NodeId, owner: BranchKey) {
399 let edge = canonical_edge(node_id, neighbor, owner.direction);
400 let Some(source) = self.resolve_id(edge.source) else {
401 return;
402 };
403 let Some(target) = self.resolve_id(edge.target) else {
404 return;
405 };
406 let Some(edge_index) = self
407 .graph
408 .find_edge(self.by_id[&source], self.by_id[&target])
409 else {
410 return;
411 };
412 let relation = self
413 .graph
414 .edge_weight_mut(edge_index)
415 .expect("found edges have weights");
416 relation.observed_by.retain(|candidate| *candidate != owner);
417 if relation.observed_by.is_empty() {
418 self.graph.remove_edge(edge_index);
419 }
420 }
421
422 fn has_edge(&self, source: NodeId, target: NodeId) -> bool {
423 let (Some(source), Some(target)) = (self.resolve_id(source), self.resolve_id(target))
424 else {
425 return false;
426 };
427 self.graph
428 .find_edge(self.by_id[&source], self.by_id[&target])
429 .is_some()
430 }
431
432 fn merge_nodes(
433 &mut self,
434 source_id: NodeId,
435 target_id: NodeId,
436 identity: SymbolIdentity,
437 ) -> NodeId {
438 let identity_key = ResolvedSymbolKey::from_identity(&identity);
439 let source_index = self.by_id[&source_id];
440 let target_index = self.by_id[&target_id];
441 let incident = self
442 .graph
443 .edge_references()
444 .filter(|edge| edge.source() == source_index || edge.target() == source_index)
445 .map(|edge| {
446 let source = self.graph[edge.source()].id;
447 let target = self.graph[edge.target()].id;
448 (source, target, edge.weight().clone())
449 })
450 .collect::<Vec<_>>();
451 let mut source_node = self
452 .graph
453 .remove_node(source_index)
454 .expect("source node exists while merging");
455 replace_neighbor(&mut source_node.incoming.neighbors, source_id, target_id);
456 replace_neighbor(&mut source_node.outgoing.neighbors, source_id, target_id);
457 self.by_id.remove(&source_id);
458 self.redirects.insert(source_id, target_id);
459 self.by_identity.retain(|_, value| *value != source_id);
460
461 for node_index in self.graph.node_indices().collect::<Vec<_>>() {
462 let node = &mut self.graph[node_index];
463 replace_neighbor(&mut node.incoming.neighbors, source_id, target_id);
464 replace_neighbor(&mut node.outgoing.neighbors, source_id, target_id);
465 }
466 for relation in self.graph.edge_weights_mut() {
467 for owner in &mut relation.observed_by {
468 if owner.node_id == source_id {
469 owner.node_id = target_id;
470 }
471 }
472 relation.observed_by.sort_by_key(|owner| {
473 (
474 owner.node_id.0,
475 match owner.direction {
476 HierarchyDirection::Incoming => 0,
477 HierarchyDirection::Outgoing => 1,
478 },
479 )
480 });
481 relation.observed_by.dedup();
482 }
483
484 {
485 let target = self
486 .graph
487 .node_weight_mut(target_index)
488 .expect("target node remains while merging");
489 merge_branch(&mut target.incoming, source_node.incoming);
490 merge_branch(&mut target.outgoing, source_node.outgoing);
491 target.symbol = identity.symbol;
492 target.kind = identity.kind;
493 target.location = identity.location.clone();
494 }
495
496 for (source, target, mut relation) in incident {
497 let source = if source == source_id {
498 target_id
499 } else {
500 source
501 };
502 let target = if target == source_id {
503 target_id
504 } else {
505 target
506 };
507 for owner in &mut relation.observed_by {
508 if owner.node_id == source_id {
509 owner.node_id = target_id;
510 }
511 }
512 let source_index = self.by_id[&source];
513 let target_index = self.by_id[&target];
514 if let Some(edge_index) = self.graph.find_edge(source_index, target_index) {
515 let existing = self
516 .graph
517 .edge_weight_mut(edge_index)
518 .expect("found edges have weights");
519 for owner in relation.observed_by {
520 if !existing.observed_by.contains(&owner) {
521 existing.observed_by.push(owner);
522 }
523 }
524 } else {
525 self.graph.add_edge(source_index, target_index, relation);
526 }
527 }
528
529 for anchor in &mut self.anchors {
530 if *anchor == source_id {
531 *anchor = target_id;
532 }
533 }
534 let mut seen = HashSet::new();
535 self.anchors.retain(|anchor| seen.insert(*anchor));
536 if let Some(key) = identity_key {
537 self.by_identity.insert(key, target_id);
538 }
539 target_id
540 }
541}
542
543fn canonical_edge(node_id: NodeId, neighbor: NodeId, direction: HierarchyDirection) -> VisibleEdge {
544 match direction {
545 HierarchyDirection::Incoming => VisibleEdge {
546 source: neighbor,
547 target: node_id,
548 },
549 HierarchyDirection::Outgoing => VisibleEdge {
550 source: node_id,
551 target: neighbor,
552 },
553 }
554}
555
556fn replace_neighbor(neighbors: &mut Vec<NodeId>, source: NodeId, target: NodeId) {
557 for neighbor in neighbors.iter_mut() {
558 if *neighbor == source {
559 *neighbor = target;
560 }
561 }
562 let mut seen = HashSet::new();
563 neighbors.retain(|neighbor| seen.insert(*neighbor));
564}
565
566fn merge_branch(target: &mut GraphBranch, source: GraphBranch) {
567 target.expanded |= source.expanded;
568 for neighbor in source.neighbors {
569 if !target.neighbors.contains(&neighbor) {
570 target.neighbors.push(neighbor);
571 }
572 }
573 if load_state_priority(source.load_state) > load_state_priority(target.load_state) {
574 target.load_state = source.load_state;
575 target.failure = source.failure;
576 } else if target.failure.is_none() {
577 target.failure = source.failure;
578 }
579 if target.active_request_id.is_none() {
580 target.active_request_id = source.active_request_id;
581 }
582}
583
584fn load_state_priority(state: LoadState) -> u8 {
585 match state {
586 LoadState::NotLoaded => 0,
587 LoadState::Failed => 1,
588 LoadState::Loading => 2,
589 LoadState::Loaded => 3,
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::{RelationGraph, VisibleEdge};
596 use crate::state::{
597 HierarchyDirection, HierarchyKind, LoadState, SourceLocation, SymbolIdentity,
598 };
599
600 fn symbol(name: &str, line: u32) -> SymbolIdentity {
601 SymbolIdentity {
602 symbol: name.to_owned(),
603 kind: HierarchyKind::Call,
604 location: Some(SourceLocation {
605 uri: "file:///workspace/src/lib.rs".to_owned(),
606 line: Some(line),
607 character: Some(0),
608 }),
609 }
610 }
611
612 #[test]
613 fn globally_deduplicates_diamond_nodes_without_losing_edges() {
614 let mut graph = RelationGraph::default();
615 let root = graph.pin_symbol(symbol("root", 0));
616 graph
617 .replace_branch_neighbors(
618 root,
619 HierarchyDirection::Outgoing,
620 vec![symbol("left", 1), symbol("right", 2)],
621 )
622 .unwrap();
623 graph.node_mut(root).unwrap().outgoing.expanded = true;
624 let left = graph.node(root).unwrap().outgoing.neighbors[0];
625 let right = graph.node(root).unwrap().outgoing.neighbors[1];
626 let shared_from_left = graph
627 .replace_branch_neighbors(
628 left,
629 HierarchyDirection::Outgoing,
630 vec![symbol("shared", 3)],
631 )
632 .unwrap()[0];
633 let shared_from_right = graph
634 .replace_branch_neighbors(
635 right,
636 HierarchyDirection::Outgoing,
637 vec![symbol("shared", 3)],
638 )
639 .unwrap()[0];
640 graph.node_mut(left).unwrap().outgoing.expanded = true;
641 graph.node_mut(right).unwrap().outgoing.expanded = true;
642
643 assert_eq!(shared_from_left, shared_from_right);
644 assert_eq!(graph.node_count(), 4);
645 assert_eq!(graph.edge_count(), 4);
646 let visible = graph.visible_graph();
647 assert_eq!(visible.nodes.len(), 4);
648 assert_eq!(visible.edges.len(), 4);
649 }
650
651 #[test]
652 fn merges_the_same_edge_observed_from_both_directions() {
653 let mut graph = RelationGraph::default();
654 let caller = graph.pin_symbol(symbol("caller", 0));
655 let callee = graph.insert_symbol(symbol("callee", 1));
656 graph
657 .replace_branch_neighbors(
658 caller,
659 HierarchyDirection::Outgoing,
660 vec![symbol("callee", 1)],
661 )
662 .unwrap();
663 graph
664 .replace_branch_neighbors(
665 callee,
666 HierarchyDirection::Incoming,
667 vec![symbol("caller", 0)],
668 )
669 .unwrap();
670
671 assert_eq!(graph.edge_count(), 1);
672 }
673
674 #[test]
675 fn traverses_cycles_once_and_preserves_the_closing_edge() {
676 let mut graph = RelationGraph::default();
677 let first = graph.pin_symbol(symbol("first", 0));
678 let second = graph
679 .replace_branch_neighbors(
680 first,
681 HierarchyDirection::Outgoing,
682 vec![symbol("second", 1)],
683 )
684 .unwrap()[0];
685 let third = graph
686 .replace_branch_neighbors(
687 second,
688 HierarchyDirection::Outgoing,
689 vec![symbol("third", 2)],
690 )
691 .unwrap()[0];
692 graph
693 .replace_branch_neighbors(
694 third,
695 HierarchyDirection::Outgoing,
696 vec![symbol("first", 0)],
697 )
698 .unwrap();
699 for node_id in [first, second, third] {
700 graph.node_mut(node_id).unwrap().outgoing.expanded = true;
701 }
702
703 let visible = graph.visible_graph();
704 assert_eq!(visible.nodes.len(), 3);
705 assert_eq!(visible.edges.len(), 3);
706 assert!(visible.edges.contains(&VisibleEdge {
707 source: third,
708 target: first,
709 }));
710 }
711
712 #[test]
713 fn represents_a_self_loop_without_creating_an_extra_node() {
714 let mut graph = RelationGraph::default();
715 let node = graph.pin_symbol(symbol("recursive", 0));
716 graph
717 .replace_branch_neighbors(
718 node,
719 HierarchyDirection::Outgoing,
720 vec![symbol("recursive", 0)],
721 )
722 .unwrap();
723 graph.node_mut(node).unwrap().outgoing.expanded = true;
724
725 assert_eq!(graph.node_count(), 1);
726 assert_eq!(graph.edge_count(), 1);
727 assert_eq!(
728 graph.visible_graph().edges,
729 [VisibleEdge {
730 source: node,
731 target: node,
732 }]
733 );
734 }
735
736 #[test]
737 fn keeps_same_named_symbols_at_different_locations_separate() {
738 let mut graph = RelationGraph::default();
739 let first = graph.insert_symbol(symbol("same", 1));
740 let second = graph.insert_symbol(symbol("same", 2));
741
742 assert_ne!(first, second);
743 assert_eq!(graph.node_count(), 2);
744 }
745
746 #[test]
747 fn resolves_a_provisional_anchor_into_an_existing_semantic_node() {
748 let mut graph = RelationGraph::default();
749 let provisional = graph.pin_symbol(SymbolIdentity {
750 symbol: "target".to_owned(),
751 kind: HierarchyKind::Call,
752 location: None,
753 });
754 let existing = graph.insert_symbol(symbol("Module::target", 4));
755
756 let resolved = graph
757 .resolve_symbol(provisional, symbol("Module::target", 4))
758 .unwrap();
759
760 assert_eq!(resolved, existing);
761 assert_eq!(graph.resolve_id(provisional), Some(existing));
762 assert_eq!(graph.anchors(), [existing]);
763 assert_eq!(graph.node_count(), 1);
764 }
765
766 #[test]
767 fn retargets_existing_edges_when_resolving_a_provisional_anchor() {
768 let mut graph = RelationGraph::default();
769 let provisional = graph.pin_symbol(SymbolIdentity {
770 symbol: "target".to_owned(),
771 kind: HierarchyKind::Call,
772 location: None,
773 });
774 let child = graph
775 .replace_branch_neighbors(
776 provisional,
777 HierarchyDirection::Outgoing,
778 vec![symbol("child", 5)],
779 )
780 .unwrap()[0];
781 graph.node_mut(provisional).unwrap().outgoing.expanded = true;
782 let existing = graph.insert_symbol(symbol("Module::target", 4));
783
784 let resolved = graph
785 .resolve_symbol(provisional, symbol("Module::target", 4))
786 .unwrap();
787 let visible = graph.visible_graph();
788
789 assert_eq!(resolved, existing);
790 assert_eq!(graph.edge_count(), 1);
791 assert_eq!(visible.nodes, [existing, child]);
792 assert_eq!(
793 visible.edges,
794 [VisibleEdge {
795 source: existing,
796 target: child,
797 }]
798 );
799 }
800
801 #[test]
802 fn clearing_one_branch_keeps_an_edge_observed_by_the_other_endpoint() {
803 let mut graph = RelationGraph::default();
804 let caller = graph.pin_symbol(symbol("caller", 0));
805 let callee = graph.insert_symbol(symbol("callee", 1));
806 graph
807 .replace_branch_neighbors(
808 caller,
809 HierarchyDirection::Outgoing,
810 vec![symbol("callee", 1)],
811 )
812 .unwrap();
813 graph
814 .replace_branch_neighbors(
815 callee,
816 HierarchyDirection::Incoming,
817 vec![symbol("caller", 0)],
818 )
819 .unwrap();
820
821 assert!(graph.clear_branch(caller, HierarchyDirection::Outgoing));
822 assert_eq!(graph.edge_count(), 1);
823 assert_eq!(
824 graph.node(caller).unwrap().outgoing.load_state,
825 LoadState::NotLoaded
826 );
827 assert!(graph.clear_branch(callee, HierarchyDirection::Incoming));
828 assert_eq!(graph.edge_count(), 0);
829 }
830
831 #[test]
832 fn known_graph_includes_collapsed_cache_and_excludes_cleared_orphans() {
833 let mut graph = RelationGraph::default();
834 let root = graph.pin_symbol(symbol("root", 0));
835 let child = graph
836 .replace_branch_neighbors(root, HierarchyDirection::Outgoing, vec![symbol("child", 1)])
837 .unwrap()[0];
838
839 assert_eq!(graph.visible_graph().nodes, [root]);
840 assert_eq!(graph.known_graph().nodes, [root, child]);
841 assert_eq!(
842 graph.known_graph().edges,
843 [VisibleEdge {
844 source: root,
845 target: child,
846 }]
847 );
848
849 assert!(graph.clear_branch(root, HierarchyDirection::Outgoing));
850 assert_eq!(graph.known_graph().nodes, [root]);
851 assert!(graph.known_graph().edges.is_empty());
852 }
853}