Skip to main content

jj_lib/
graph_dominators.rs

1// Copyright 2026 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Generic implementation of the "closest common dominator" algorithm for
16//! directed graphs.
17//!
18//! Generic implementation of the Common Dominator algorithm for directed
19//! graphs, using the Cooper-Harvey-Kennedy iterative algorithm. Loosely
20//! speaking the algorithm finds the "choke point" for a set of nodes S in a
21//! directed graph (going from the "entry" node to nodes in S), closest to S.
22//!
23//! Dominance:
24//!
25//! * A flow graph is a directed graph with a designated entry node.
26//! * A node z is said to dominate a node n if all paths from the entry node to
27//!   n must go through z. Every node dominates itself, and the entry node
28//!   dominates all nodes.
29//! * A node can have one or more dominators.
30//! * A node z strictly dominates n if z dominates n and z != n.
31//! * The immediate dominator of a node n is the dominator of n that doesn't
32//!   strictly dominate any other strict dominators of n. Informally it is the
33//!   "closest" choke point on all paths from the entry node to n.
34//! * Let S be a subset of the nodes in the graph. The intersection of the
35//!   dominators of each node in S is the set of common dominators of S.
36//! * The closest common dominator of S is the common dominator of S that
37//!   doesn't strictly dominate any other common dominator of S. Informally, it
38//!   is the choke point closest to S such that all paths from the entry node to
39//!   S must go through it.
40//!
41//! Dominator Tree:
42//!
43//! For any flow graph G there is a corresponding dominator tree defined as
44//! follows:
45//! * The nodes of the dominator tree are the same as the nodes of G
46//! * The root of the dominator tree is the entry node of G
47//! * In the dominator tree, the children of a node are the nodes it immediately
48//!   dominates
49//!
50//! The closest common dominator of S is the Lowest Common Ancestor (LCA)
51//! of S in the graph's dominator tree.
52//!
53//! This implementation constructs the Dominator Tree by first determining
54//! the Immediate Dominator for every node (using the standard iterative
55//! algorithm), and then calculating the LCA for the set S. See:
56//!
57//! * <http://www.hipersoft.rice.edu/grads/publications/dom14.pdf>
58//! * <https://en.wikipedia.org/wiki/Dominator_(graph_theory)>
59//!
60//! The running time is O(V+E+|S|*V)in the worst case, the space complexity is
61//! O(V+E), where V is the number of nodes and E is the number of edges. In
62//! practice the algorithm is fast and efficient for typical use cases because
63//! the number of nodes that dominate any given node is typically small, and
64//! the dominator tree is typically shallow.
65
66use std::collections::HashMap;
67use std::collections::HashSet;
68use std::hash::Hash;
69use std::iter;
70
71use indexmap::IndexMap;
72use indexmap::IndexSet;
73use itertools::Itertools as _;
74use thiserror::Error;
75
76/// An immutable directed graph with nodes of type N and a minimal interface for
77/// iterating over nodes and their adjacent nodes.
78#[derive(Clone, Eq, PartialEq, Debug)]
79pub struct SimpleDirectedGraph<N>
80where
81    N: Clone + Eq + Hash,
82{
83    /// The adjacency map of the graph. Each key is a node, and the
84    /// corresponding value is the set of adjacent nodes (i.e., the children of
85    /// the key node). The adjacency map is in canonical form: for every
86    /// u->v edge, there is an entry in adj with key v (even if v has no
87    /// outgoing edges).
88    adj: IndexMap<N, IndexSet<N>>,
89}
90
91impl<N> SimpleDirectedGraph<N>
92where
93    N: Clone + Eq + Hash,
94{
95    /// Constructs a new SimpleDirectedGraph from a list of edges.
96    pub fn new<EI>(edges: EI) -> Self
97    where
98        EI: IntoIterator<Item = (N, N)>,
99    {
100        let mut adj: IndexMap<N, IndexSet<N>> = IndexMap::new();
101        for (parent, child) in edges {
102            adj.entry(parent).or_default().insert(child.clone());
103            adj.entry(child).or_default();
104        }
105        Self { adj }
106    }
107
108    /// Returns the nodes in this graph.
109    pub fn nodes(&self) -> impl Iterator<Item = &N> {
110        self.adj.keys()
111    }
112
113    /// Returns the nodes in this graph.
114    pub fn num_nodes(&self) -> usize {
115        self.adj.len()
116    }
117
118    /// Returns the edges in this graph.
119    pub fn edges(&self) -> impl Iterator<Item = (&N, &N)> {
120        self.adj
121            .iter()
122            .flat_map(|(parent, adj_set)| adj_set.iter().map(move |child| (parent, child)))
123    }
124
125    /// Returns the adjacent nodes for the given node, or None if the node is
126    /// not in the graph.
127    pub fn adjacent_nodes(&self, node: &N) -> Option<impl DoubleEndedIterator<Item = &N>> {
128        self.adj.get(node).map(|adj_set| adj_set.iter())
129    }
130
131    /// Returns true if this graph contains the given node.
132    pub fn contains_node(&self, node: &N) -> bool {
133        self.adj.contains_key(node)
134    }
135
136    /// Returns a postorder traversal of the nodes in this graph starting from
137    /// the given node.
138    pub fn get_postorder<'a>(&'a self, start_node: &'a N) -> Vec<&'a N> {
139        post_order(start_node, |&u| self.adjacent_nodes(u).unwrap()).collect_vec()
140    }
141}
142
143/// A FlowGraph is a directed graph with a designated start node.
144///
145/// Any node in the graph can be the start node. There are no reachability
146/// requirements whatsoever: some nodes may be unreachable from the start node,
147/// the start node could have incoming edges, the graph could be disconnected,
148/// etc.
149#[derive(Clone, Eq, PartialEq, Debug)]
150pub struct FlowGraph<N>
151where
152    N: Clone + Eq + Hash,
153{
154    /// The graph.
155    pub graph: SimpleDirectedGraph<N>,
156    /// The start node.
157    pub start_node: N,
158}
159
160impl<N> FlowGraph<N>
161where
162    N: Clone + Eq + Hash,
163{
164    /// Constructs a new FlowGraph.
165    pub fn new(graph: SimpleDirectedGraph<N>, start_node: N) -> Self {
166        Self { graph, start_node }
167    }
168}
169
170/// Calculates the dominators in a flow graph. Also has a method for finding the
171/// closest common dominator of a set of nodes.
172pub struct DominatorFinder<'a, N> {
173    /// Map from nodes to integers in [0, N-1] range, in postorder (the start
174    /// node has index N-1).
175    node_to_id: HashMap<&'a N, InternalId>,
176    /// The inverse of node_to_id.
177    id_to_node: Vec<&'a N>,
178    /// The immediate dominator for each node (by index). NOTE: the immediate
179    /// dominator of the start node is itself.
180    immediate_dominators: Vec<InternalId>,
181}
182
183/// Errors that can occur while finding dominators.
184#[derive(Debug, Error, PartialEq)]
185pub enum DominatorFinderError {
186    /// The flow graph is invalid.
187    #[error("The flow graph is invalid: some nodes are unreachable from the start node")]
188    UnreachableNodesInFlowGraph,
189    /// The target set is empty.
190    #[error("Target set must not be empty")]
191    EmptyTargetSet,
192    /// The target set is invalid.
193    #[error("Target set contains a node which is not in the flow graph")]
194    UnknownNodeInTargetSet,
195}
196
197/// The dominator algorithm assigns consecutive numeric IDs to nodes, for
198/// efficiency reasons. We use this type alias for clarity.
199type InternalId = usize;
200
201impl<'a, N> DominatorFinder<'a, N>
202where
203    N: Clone + Eq + Hash,
204{
205    /// Constructs a new DominatorFinder. Returns an error if the flow graph is
206    /// invalid: e.g. if some node is unreachable from the start node.
207    pub fn calculate(flow_graph: &'a FlowGraph<N>) -> Result<Self, DominatorFinderError> {
208        // Get postorder traversal of the graph starting from the start node.
209        let postorder = flow_graph.graph.get_postorder(&flow_graph.start_node);
210        if postorder.len() != flow_graph.graph.num_nodes() {
211            return Err(DominatorFinderError::UnreachableNodesInFlowGraph);
212        }
213
214        // Map generic types to integer IDs
215        let mut node_to_id = HashMap::new();
216        let mut id_to_node = Vec::new();
217        for (index, &node) in postorder.iter().enumerate() {
218            id_to_node.push(node);
219            node_to_id.insert(node, index);
220        }
221
222        // Build graph using internal IDs.
223        let num_nodes = node_to_id.len();
224        let mut rev_adj = vec![vec![]; num_nodes];
225        for (u, v) in flow_graph.graph.edges() {
226            rev_adj[node_to_id[v]].push(node_to_id[u]);
227        }
228
229        // Find the immediate dominators for each node using the Cooper-Harvey-Kennedy
230        // iterative algorithm.
231        let immediate_dominators = Self::calculate_immediate_dominators(&rev_adj);
232
233        Ok(Self {
234            node_to_id,
235            id_to_node,
236            immediate_dominators,
237        })
238    }
239
240    /// Returns a map from each node to its immediate dominator. NOTE: the
241    /// immediate dominator of the start node is itself.
242    pub fn get_immediate_dominators(&self) -> HashMap<N, N> {
243        self.immediate_dominators
244            .iter()
245            .enumerate()
246            .map(|(index, &idom)| {
247                (
248                    self.id_to_node[index].clone(),
249                    self.id_to_node[idom].clone(),
250                )
251            })
252            .collect()
253    }
254
255    /// Finds the closest common dominator for the given flow graph and set of
256    /// nodes S (target_set).
257    pub fn find_closest_common_dominator<NI>(
258        &self,
259        target_set: NI,
260    ) -> Result<N, DominatorFinderError>
261    where
262        NI: IntoIterator<Item = N>,
263    {
264        // Convert generic target_set to internal IDs
265        let target_ids: Vec<InternalId> = target_set
266            .into_iter()
267            .map(|node| match self.node_to_id.get(&node) {
268                Some(id) => Ok(*id),
269                None => Err(DominatorFinderError::UnknownNodeInTargetSet),
270            })
271            .try_collect()?;
272        if target_ids.is_empty() {
273            return Err(DominatorFinderError::EmptyTargetSet);
274        }
275
276        // The closest common dominator of a set of nodes is the lowest common ancestor
277        // of those nodes in the dominator tree.
278        let closest_common_dominator =
279            Self::find_lowest_common_ancestor(&target_ids, &self.immediate_dominators);
280
281        // Map the internal ID back to generic type N.
282        Ok(self.id_to_node[closest_common_dominator].clone())
283    }
284
285    // Applies the Cooper-Harvey-Kennedy iterative algorithm to find the immediate
286    // dominators for each node in the graph.
287    // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works.
288    fn calculate_immediate_dominators(rev_adj: &[Vec<InternalId>]) -> Vec<InternalId> {
289        // Step 1: Compute Dominators on Reverse Graph
290        let num_nodes = rev_adj.len();
291        let start_node_id = num_nodes - 1;
292
293        // We hold the immediate dominator for each node in the following vector, in
294        // index position (the kth entry is the immediate dominator of the node with ID
295        // k). We initialize the immediate dominator of every node to usize::MAX to
296        // represent that those nodes are not processed yet. Once a node is
297        // processed, its immediate dominator is guaranteed to be a valid node
298        // index.
299        let mut immediate_dominators: Vec<InternalId> = vec![usize::MAX; num_nodes];
300        // NOTE: technically speaking the immediate dominator is NOT defined for the
301        // start node, but it is convenient for the algorithm to set it to itself; this
302        // is consistent with the literature and specifically with
303        // Cooper-Harvey-Kennedy.
304        immediate_dominators[start_node_id] = start_node_id;
305
306        loop {
307            // Each iteration of the loop processes all nodes in reverse postorder, trying
308            // to improve the immediate dominator for each node. The loop continues until we
309            // have an iteration where no immediate dominator is changed. Note that the
310            // entries in immediate_dominators are only guaranteed to be correct when the
311            // loop terminates.
312            let mut changed = false;
313
314            // Iterate in reverse postorder, skipping the start node.
315            for u in (0..start_node_id).rev() {
316                let mut new_idom = usize::MAX;
317                // Process predecessors (nodes that flow INTO u).
318                let preds = &rev_adj[u];
319                for &p in preds {
320                    if immediate_dominators[p] == usize::MAX {
321                        // Skip predecessors that have not been processed yet.
322                        continue;
323                    }
324                    if new_idom == usize::MAX {
325                        // This is the first predecessor of u that has been processed so far. We use
326                        // it as the starting point for finding the new "improved" immediate
327                        // dominator for u.
328                        new_idom = p;
329                    } else {
330                        // "Intersect" the current new_idom with p's idom.
331                        new_idom = Self::intersect(new_idom, p, &immediate_dominators);
332                    }
333                }
334                if new_idom == usize::MAX {
335                    // None of the predecessors of u have been processed yet. That's fine, we will
336                    // try again of the next iteration of the outer loop.
337                    continue;
338                }
339                if immediate_dominators[u] != new_idom {
340                    // We "improved" the immediate dominator for u!
341                    immediate_dominators[u] = new_idom;
342                    changed = true;
343                }
344            }
345
346            if !changed {
347                // We reached the fixed point. We are done.
348                break;
349            }
350        }
351
352        // At this point we know the immediate dominator of every node, but we keep the
353        // Option wrapper so that we can use the intersect function during
354        // find_lowest_common_ancestor.
355        immediate_dominators
356    }
357
358    // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works.
359    fn intersect(
360        mut b1: InternalId,
361        mut b2: InternalId,
362        immediate_dominators: &[InternalId],
363    ) -> InternalId {
364        while b1 != b2 {
365            while b1 < b2 {
366                b1 = immediate_dominators[b1];
367            }
368            while b2 < b1 {
369                b2 = immediate_dominators[b2];
370            }
371        }
372        b1
373    }
374
375    // See http://www.hipersoft.rice.edu/grads/publications/dom14.pdf for details on how this function works.
376    fn find_lowest_common_ancestor(
377        targets: &[InternalId],
378        immediate_dominators: &[InternalId],
379    ) -> InternalId {
380        targets
381            .iter()
382            .copied()
383            .reduce(|a, b| Self::intersect(a, b, immediate_dominators))
384            .expect("targets must not be empty")
385    }
386}
387
388/// Traverses nodes from `start_node` in post-order.
389fn post_order<T, NI>(
390    start_node: T,
391    mut neighbors_fn: impl FnMut(&T) -> NI,
392) -> impl Iterator<Item = T>
393where
394    T: Clone + Hash + Eq,
395    NI: DoubleEndedIterator<Item = T>,
396{
397    let mut stack = vec![(start_node, false)];
398    let mut visited: HashSet<T> = HashSet::new();
399    iter::from_fn(move || {
400        while let Some((node, processed)) = stack.pop() {
401            if processed {
402                // If we marked it as processed, it means its children
403                // were already added to the stack and processed.
404                return Some(node);
405            }
406            // Mark as visited so we don't start a new DFS from here
407            if !visited.insert(node.clone()) {
408                // The node is already visited, continue.
409                continue;
410            }
411            let neighbors = neighbors_fn(&node);
412            // Push the node back onto the stack with processed = true.
413            // It will be popped and yielded AFTER its children.
414            stack.push((node, true));
415            // Push the neighbors onto the stack with processed = false. The neighbors are
416            // added in reverse order, so they are processed in the
417            // original order.
418            for neighbor in neighbors.rev() {
419                if !visited.contains(&neighbor) {
420                    stack.push((neighbor, false));
421                }
422            }
423        }
424        None
425    })
426}
427
428#[cfg(test)]
429mod tests {
430    use maplit::hashmap;
431
432    use super::*;
433
434    #[test]
435    fn test_closest_common_dominator_split() -> Result<(), DominatorFinderError> {
436        //   /-> B \
437        // A        -> D
438        //   \-> C /
439        let flow_graph = FlowGraph::new(
440            SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D"), ("C", "D")]),
441            "A",
442        );
443        let df = DominatorFinder::calculate(&flow_graph)?;
444        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
445        assert_eq!(df.find_closest_common_dominator(["B"])?, "B");
446        assert_eq!(df.find_closest_common_dominator(["C"])?, "C");
447        assert_eq!(df.find_closest_common_dominator(["D"])?, "D");
448        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "A");
449        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "A");
450        assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "A");
451        Ok(())
452    }
453
454    #[test]
455    fn test_closest_common_dominator_linear_chain() -> Result<(), DominatorFinderError> {
456        // A -> B -> C -> D
457        let flow_graph = FlowGraph::new(
458            SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D")]),
459            "A",
460        );
461        let df = DominatorFinder::calculate(&flow_graph)?;
462        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
463        assert_eq!(df.find_closest_common_dominator(["B"])?, "B");
464        assert_eq!(df.find_closest_common_dominator(["C"])?, "C");
465        assert_eq!(df.find_closest_common_dominator(["D"])?, "D");
466        assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A");
467        assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A");
468        assert_eq!(df.find_closest_common_dominator(["A", "D"])?, "A");
469        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B");
470        assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C");
471        assert_eq!(df.find_closest_common_dominator(["A", "B", "C", "D"])?, "A");
472        Ok(())
473    }
474
475    #[test]
476    fn test_closest_common_dominator_classic_diamond() -> Result<(), DominatorFinderError> {
477        //      /-> B -\
478        //    A          -> D -> E
479        //      \-> C -/
480        let flow_graph = FlowGraph::new(
481            SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D"), ("C", "D"), ("D", "E")]),
482            "A",
483        );
484        let df = DominatorFinder::calculate(&flow_graph)?;
485        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "A");
486        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A");
487        assert_eq!(df.find_closest_common_dominator(["D"])?, "D");
488        assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D");
489        assert_eq!(df.find_closest_common_dominator(["A", "D"])?, "A");
490        Ok(())
491    }
492
493    #[test]
494    fn test_closest_common_dominator_single_node() -> Result<(), DominatorFinderError> {
495        // A
496        let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "A")]), "A");
497        let df = DominatorFinder::calculate(&flow_graph)?;
498        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
499        Ok(())
500    }
501
502    #[test]
503    fn test_invalid_flowgraph() {
504        //       /-> E
505        // A -> B
506        //       \-> F
507        //           ^
508        //           |
509        // C --> D --/
510        let flow_graph = FlowGraph::new(
511            SimpleDirectedGraph::new([("A", "B"), ("B", "E"), ("B", "F"), ("C", "D"), ("D", "F")]),
512            "A",
513        );
514        assert_eq!(
515            DominatorFinder::calculate(&flow_graph).err(),
516            Some(DominatorFinderError::UnreachableNodesInFlowGraph)
517        );
518    }
519
520    #[test]
521    fn test_closest_common_dominator_simple_cycle_with_entry() -> Result<(), DominatorFinderError> {
522        //
523        // A -> B -> C -> D
524        //      ^         |
525        //      |         |
526        //      \--------/
527        let flow_graph = FlowGraph::new(
528            SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("D", "B")]),
529            "A",
530        );
531        let df = DominatorFinder::calculate(&flow_graph)?;
532        assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A");
533        assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A");
534        assert_eq!(df.find_closest_common_dominator(["A", "B", "C"])?, "A");
535        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
536        assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B");
537        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
538        assert_eq!(df.find_closest_common_dominator(["B"])?, "B");
539        assert_eq!(df.find_closest_common_dominator(["C"])?, "C");
540        assert_eq!(df.find_closest_common_dominator(["D"])?, "D");
541        Ok(())
542    }
543
544    #[test]
545    fn test_closest_common_dominator_figure_eight_with_bridge() -> Result<(), DominatorFinderError>
546    {
547        //
548        //  A -> B -> C -> D -> E -> F -> G
549        //       ^         |    ^         |
550        //       |         |    |         |
551        //        \_______/      \_______/
552        let flow_graph = FlowGraph::new(
553            SimpleDirectedGraph::new([
554                ("A", "B"), // entry
555                ("B", "C"),
556                ("C", "D"),
557                ("D", "B"), // Loop 1
558                ("D", "E"), // Bridge
559                ("E", "F"),
560                ("F", "G"),
561                ("G", "E"), // Loop 2
562            ]),
563            "A",
564        );
565        let df = DominatorFinder::calculate(&flow_graph)?;
566        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
567        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B");
568        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B");
569        assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C");
570        assert_eq!(df.find_closest_common_dominator(["C", "F"])?, "C");
571        assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D");
572        assert_eq!(df.find_closest_common_dominator(["D", "F"])?, "D");
573        assert_eq!(df.find_closest_common_dominator(["E", "G"])?, "E");
574        assert_eq!(df.find_closest_common_dominator(["F", "G"])?, "F");
575        Ok(())
576    }
577
578    #[test]
579    fn test_closest_common_dominator_figure_eight() -> Result<(), DominatorFinderError> {
580        //
581        //  A -> B -> C --> D   -> E -> F
582        //       ^         | ^          |
583        //       |         | |          |
584        //        \_______/  \_________/
585        let flow_graph = FlowGraph::new(
586            SimpleDirectedGraph::new([
587                ("A", "B"), // entry
588                ("B", "C"),
589                ("C", "D"),
590                ("D", "B"), // Loop 1
591                ("D", "E"),
592                ("E", "F"),
593                ("F", "D"), // Loop 2
594            ]),
595            "A",
596        );
597        let df = DominatorFinder::calculate(&flow_graph)?;
598        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
599        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B");
600        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B");
601        assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C");
602        assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C");
603        assert_eq!(df.find_closest_common_dominator(["C", "F"])?, "C");
604        assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "D");
605        assert_eq!(df.find_closest_common_dominator(["D", "F"])?, "D");
606        assert_eq!(df.find_closest_common_dominator(["E", "F"])?, "E");
607        Ok(())
608    }
609
610    #[test]
611    fn test_closest_common_dominator_entry_cycle_dominance() -> Result<(), DominatorFinderError> {
612        // A -> B -> C
613        //      ^    |
614        //      |----/
615        let flow_graph = FlowGraph::new(
616            SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "B")]),
617            "A",
618        );
619        let df = DominatorFinder::calculate(&flow_graph)?;
620        assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A");
621        assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A");
622        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
623        assert_eq!(df.find_closest_common_dominator(["A", "B", "C"])?, "A");
624        Ok(())
625    }
626
627    #[test]
628    fn test_closest_common_dominator_nested_loops() -> Result<(), DominatorFinderError> {
629        //           /---> E
630        //           |     |
631        //           |     |
632        // A -> B -> C <--/
633        //      ^    |
634        //      |    V
635        //      \----D
636        let flow_graph = FlowGraph::new(
637            SimpleDirectedGraph::new([
638                ("A", "B"),
639                ("B", "C"),
640                ("C", "D"),
641                ("C", "E"),
642                ("E", "C"),
643                ("D", "B"),
644            ]),
645            "A",
646        );
647        let df = DominatorFinder::calculate(&flow_graph)?;
648        assert_eq!(df.find_closest_common_dominator(["A", "B"])?, "A");
649        assert_eq!(df.find_closest_common_dominator(["A", "C"])?, "A");
650        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
651        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "B");
652        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "B");
653        assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "C");
654        assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "C");
655        assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "C");
656        assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B");
657        assert_eq!(df.find_closest_common_dominator(["B", "C", "E"])?, "B");
658        assert_eq!(df.find_closest_common_dominator(["B", "D", "E"])?, "B");
659        assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "C");
660        assert_eq!(df.find_closest_common_dominator(["B", "C", "D", "E"])?, "B");
661        Ok(())
662    }
663
664    #[test]
665    fn test_irreducible_graph_cooper_harvey_kennedy_fig2() -> Result<(), DominatorFinderError> {
666        //        5
667        //     /    \
668        //    |      |
669        //    V      V
670        //    4      3
671        //    |      |
672        //    V      V
673        //    1 <==> 2
674        let graph = SimpleDirectedGraph::new([(1, 2), (2, 1), (3, 2), (4, 1), (5, 4), (5, 3)]);
675        let flow_graph = FlowGraph::new(graph, 5);
676        let df = DominatorFinder::calculate(&flow_graph)?;
677        assert_eq!(
678            df.get_immediate_dominators(),
679            HashMap::from([(1, 5), (2, 5), (3, 5), (4, 5), (5, 5),])
680        );
681        Ok(())
682    }
683
684    #[test]
685    fn test_irreducible_graph_cooper_harvey_kennedy_fig3() -> Result<(), DominatorFinderError> {
686        //     6
687        //   /   \
688        //  |     |
689        //  v     v
690        //  5     4 --
691        //  |     |    \
692        //  v     v     v
693        //  1 <=> 2 <=> 3
694        let graph = SimpleDirectedGraph::new([
695            (1, 2),
696            (2, 1),
697            (2, 3),
698            (3, 2),
699            (5, 1),
700            (4, 2),
701            (4, 3),
702            (6, 5),
703            (6, 4),
704        ]);
705        let flow_graph = FlowGraph::new(graph, 6);
706        let df = DominatorFinder::calculate(&flow_graph)?;
707        assert_eq!(
708            df.get_immediate_dominators(),
709            HashMap::from([(1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6),])
710        );
711        assert_eq!(df.find_closest_common_dominator([2, 3])?, 6);
712        Ok(())
713    }
714
715    #[test]
716    fn test_dominator_tree_with_three_levels() -> Result<(), DominatorFinderError> {
717        // Graph taken from https://en.wikipedia.org/wiki/Dominator_(graph_theory)
718        //     1
719        //     |  /---\
720        //     v /     \
721        //     2 <--\    \
722        //    / \    \    \
723        //   /   \    \    \
724        //  |     |    |   |
725        //  v     v    |   |
726        //  3     4    |   |
727        //  |     |    |   |
728        //   \    v    |   v
729        //    --> 5 --/    6
730        //
731        let graph =
732            SimpleDirectedGraph::new([(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]);
733        let flow_graph = FlowGraph::new(graph, 1);
734        let df = DominatorFinder::calculate(&flow_graph)?;
735        assert_eq!(
736            df.get_immediate_dominators(),
737            HashMap::from([(1, 1), (2, 1), (3, 2), (4, 2), (5, 2), (6, 2),])
738        );
739        assert_eq!(df.find_closest_common_dominator([1, 6])?, 1);
740        assert_eq!(df.find_closest_common_dominator([2, 3])?, 2);
741        assert_eq!(df.find_closest_common_dominator([2, 4])?, 2);
742        assert_eq!(df.find_closest_common_dominator([2, 5])?, 2);
743        assert_eq!(df.find_closest_common_dominator([2, 6])?, 2);
744        assert_eq!(df.find_closest_common_dominator([3, 4])?, 2);
745        assert_eq!(df.find_closest_common_dominator([3, 5])?, 2);
746        assert_eq!(df.find_closest_common_dominator([3, 6])?, 2);
747        assert_eq!(df.find_closest_common_dominator([4, 5])?, 2);
748        assert_eq!(df.find_closest_common_dominator([4, 6])?, 2);
749        assert_eq!(df.find_closest_common_dominator([5, 6])?, 2);
750        assert_eq!(df.find_closest_common_dominator([2, 3, 5])?, 2);
751        assert_eq!(df.find_closest_common_dominator([3, 4, 5])?, 2);
752        assert_eq!(df.find_closest_common_dominator([3, 4, 5, 6])?, 2);
753        Ok(())
754    }
755
756    #[test]
757    fn test_big_graph_fig_18_3() -> Result<(), DominatorFinderError> {
758        // Graph taken from Modern Compiler Implementation in Java,
759        // by Appel and Palsberg, 2004
760        //
761        //           1
762        //           |
763        //           v
764        //      /--> 2 <--\
765        //      |   / \   |
766        //      |  v   v  |
767        //      \- 3   4 -/
768        //             /\
769        //            /  \
770        //           v    v
771        //     /-->  5    6
772        //    /    /   \  /
773        //   /    |     ||
774        //  /     v     vv
775        // |  /-> 8      7
776        // |  |   |      |
777        // |  |   v      v
778        // |  \-- 9      11
779        // |      |      |
780        // |      v      v
781        //  \--- 10 --> 12
782        //
783        let graph = SimpleDirectedGraph::new([
784            (1, 2),
785            (2, 3),
786            (2, 4),
787            (3, 2),
788            (4, 2),
789            (4, 5),
790            (4, 6),
791            (5, 7),
792            (5, 8),
793            (6, 7),
794            (7, 11),
795            (8, 9),
796            (9, 8),
797            (9, 10),
798            (10, 5),
799            (10, 12),
800            (11, 12),
801        ]);
802        let flow_graph = FlowGraph::new(graph, 1);
803        let df = DominatorFinder::calculate(&flow_graph)?;
804        assert_eq!(
805            df.get_immediate_dominators(),
806            HashMap::from([
807                (1, 1),
808                (2, 1),
809                (3, 2),
810                (4, 2),
811                (5, 4),
812                (6, 4),
813                (7, 4),
814                (8, 5),
815                (9, 8),
816                (10, 9),
817                (11, 7),
818                (12, 4)
819            ])
820        );
821        assert_eq!(df.find_closest_common_dominator([6, 3])?, 2);
822        assert_eq!(df.find_closest_common_dominator([11, 9, 12])?, 4);
823        assert_eq!(df.find_closest_common_dominator([11, 9, 5])?, 4);
824        assert_eq!(df.find_closest_common_dominator([11, 10])?, 4);
825        assert_eq!(df.find_closest_common_dominator([10, 11, 12, 3, 6])?, 2);
826        Ok(())
827    }
828
829    #[test]
830    fn test_big_graph_fig_19_8() -> Result<(), DominatorFinderError> {
831        // Graph taken from Modern Compiler Implementation in Java,
832        // by Appel and Palsberg, 2004
833        //
834        //          /----- A ----\
835        //         |              |
836        //         v              v
837        //  /----> B ---\     /-> C --\
838        //  |      |    |     |   |   |
839        //  |      v    |     |   v   |
840        //  |  /-- D    |     \-- E   |
841        //  |  |   |    |         |   |
842        //  |  |   |    |         |   |
843        //  |  v   |    v         v   v
844        //  |  F   \--> G           H
845        //  |  |\       |          /
846        //  |  | \      v         /
847        //  |  |  \ /---J        /
848        //  |  |   X            /
849        //  |  | /  \          /
850        //  |  vv    v         |
851        //  |  I     K         |
852        //  |  \    /          |
853        //  |   \  /           |
854        //  |    vv            v
855        //  \---- L ---------> M
856        //
857        let graph = SimpleDirectedGraph::new([
858            ("A", "B"),
859            ("A", "C"),
860            ("B", "D"),
861            ("B", "G"),
862            ("C", "E"),
863            ("C", "H"),
864            ("D", "F"),
865            ("D", "G"),
866            ("E", "C"),
867            ("E", "H"),
868            ("F", "I"),
869            ("F", "K"),
870            ("G", "J"),
871            ("H", "M"),
872            ("I", "L"),
873            ("J", "I"),
874            ("K", "L"),
875            ("L", "B"),
876            ("L", "M"),
877        ]);
878        let flow_graph = FlowGraph::new(graph, "A");
879        let df = DominatorFinder::calculate(&flow_graph)?;
880        assert_eq!(
881            df.get_immediate_dominators(),
882            HashMap::from([
883                ("A", "A"),
884                ("B", "A"),
885                ("C", "A"),
886                ("D", "B"),
887                ("E", "C"),
888                ("F", "D"),
889                ("G", "B"),
890                ("H", "C"),
891                ("I", "B"),
892                ("J", "G"),
893                ("K", "F"),
894                ("L", "B"),
895                ("M", "A"),
896            ])
897        );
898        assert_eq!(df.find_closest_common_dominator(["K", "L"])?, "B");
899        assert_eq!(df.find_closest_common_dominator(["K", "C"])?, "A");
900        assert_eq!(df.find_closest_common_dominator(["B", "G", "J"])?, "B");
901        Ok(())
902    }
903
904    #[test]
905    fn test_closest_common_dominator_tree() -> Result<(), DominatorFinderError> {
906        // A -> B -> C
907        // \     \-> D
908        //  \------> E
909        let flow_graph = FlowGraph::new(
910            SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("B", "D"), ("A", "E")]),
911            "A",
912        );
913        let df = DominatorFinder::calculate(&flow_graph)?;
914        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
915        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A");
916        assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "B");
917        assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "A");
918        assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "B");
919        assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "A");
920        Ok(())
921    }
922
923    #[test]
924    fn test_closest_common_dominator_bypassing_path() -> Result<(), DominatorFinderError> {
925        // A -> B -> C -> D
926        // |              ^
927        // v              |
928        // E -------------/
929        let flow_graph = FlowGraph::new(
930            SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("C", "D"), ("A", "E"), ("E", "D")]),
931            "A",
932        );
933        let df = DominatorFinder::calculate(&flow_graph)?;
934        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
935        assert_eq!(df.find_closest_common_dominator(["B", "D"])?, "A");
936        assert_eq!(df.find_closest_common_dominator(["B", "E"])?, "A");
937        assert_eq!(df.find_closest_common_dominator(["C", "D"])?, "A");
938        assert_eq!(df.find_closest_common_dominator(["C", "E"])?, "A");
939        assert_eq!(df.find_closest_common_dominator(["D", "E"])?, "A");
940        assert_eq!(df.find_closest_common_dominator(["B", "C", "D"])?, "A");
941        assert_eq!(df.find_closest_common_dominator(["C", "D", "E"])?, "A");
942        Ok(())
943    }
944
945    #[test]
946    fn test_closest_common_dominator_self_loop_handling() -> Result<(), DominatorFinderError> {
947        // A->A (Self loop), A->B
948        let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "A"), ("A", "B")]), "A");
949        let df = DominatorFinder::calculate(&flow_graph)?;
950        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
951        Ok(())
952    }
953
954    #[test]
955    fn test_closest_common_dominator_multi_edge() -> Result<(), DominatorFinderError> {
956        // Shape: A->B (x2), B->C.
957        let flow_graph = FlowGraph::new(
958            SimpleDirectedGraph::new([
959                ("A", "B"),
960                ("A", "B"), // Duplicate edge
961                ("B", "C"),
962            ]),
963            "A",
964        );
965        let df = DominatorFinder::calculate(&flow_graph)?;
966        assert_eq!(df.find_closest_common_dominator(["A"])?, "A");
967        assert_eq!(df.find_closest_common_dominator(["B", "C"])?, "B");
968        Ok(())
969    }
970
971    #[test]
972    fn test_closest_common_dominator_invalid_target_set() -> Result<(), DominatorFinderError> {
973        // A -> B
974        let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "B")]), "A");
975        let df = DominatorFinder::calculate(&flow_graph)?;
976        assert_eq!(
977            df.find_closest_common_dominator([]),
978            Err(DominatorFinderError::EmptyTargetSet)
979        );
980        Ok(())
981    }
982
983    #[test]
984    fn test_closest_common_dominator_repeated_node() -> Result<(), DominatorFinderError> {
985        // A -> B
986        let flow_graph = FlowGraph::new(SimpleDirectedGraph::new([("A", "B")]), "A");
987        let df = DominatorFinder::calculate(&flow_graph)?;
988        assert_eq!(df.find_closest_common_dominator(["A", "B", "A", "B"])?, "A");
989        Ok(())
990    }
991
992    #[test]
993    fn test_simple_directed_graph_nodes() {
994        let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C")]);
995        let nodes = graph.nodes().copied().collect_vec();
996        assert_eq!(nodes, ["A", "B", "C"]);
997
998        let graph = SimpleDirectedGraph::<String>::new([]);
999        let nodes = graph.nodes().cloned().collect_vec();
1000        assert!(nodes.is_empty());
1001    }
1002
1003    #[test]
1004    fn test_simple_directed_graph_edges() {
1005        let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C"), ("A", "C")]);
1006        let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec();
1007        assert_eq!(edges, [("A", "B"), ("A", "C"), ("B", "C")]);
1008
1009        let graph = SimpleDirectedGraph::<String>::new([]);
1010        let edges = graph.edges().collect_vec();
1011        assert!(edges.is_empty());
1012    }
1013
1014    #[test]
1015    fn test_simple_directed_graph_adjacent_nodes() {
1016        let graph = SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "D")]);
1017        assert_eq!(
1018            graph.adjacent_nodes(&"A").unwrap().copied().collect_vec(),
1019            ["B", "C"]
1020        );
1021        assert_eq!(
1022            graph.adjacent_nodes(&"B").unwrap().copied().collect_vec(),
1023            ["D"]
1024        );
1025        assert!(graph.adjacent_nodes(&"C").unwrap().next().is_none());
1026        assert!(graph.adjacent_nodes(&"Z").is_none());
1027    }
1028
1029    #[test]
1030    fn test_simple_directed_graph_contains_node() {
1031        let graph = SimpleDirectedGraph::new([("A", "B"), ("B", "C")]);
1032        assert!(graph.contains_node(&"A"));
1033        assert!(graph.contains_node(&"B"));
1034        assert!(graph.contains_node(&"C"));
1035        assert!(!graph.contains_node(&"D"));
1036    }
1037
1038    #[test]
1039    fn test_simple_directed_graph_new() {
1040        let graph = SimpleDirectedGraph::new([("A", "B"), ("A", "C"), ("B", "C"), ("A", "B")]);
1041        let nodes = graph.nodes().copied().collect_vec();
1042        assert_eq!(nodes, ["A", "B", "C"]);
1043        let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec();
1044        assert_eq!(edges, [("A", "B"), ("A", "C"), ("B", "C")]);
1045
1046        let graph = SimpleDirectedGraph::new([("B", "C"), ("A", "B")]);
1047        let nodes = graph.nodes().copied().collect_vec();
1048        assert_eq!(nodes, ["B", "C", "A"]);
1049        let edges = graph.edges().map(|(&u, &v)| (u, v)).collect_vec();
1050        assert_eq!(edges, [("B", "C"), ("A", "B")]);
1051    }
1052
1053    #[test]
1054    fn test_flow_graph_new() {
1055        let graph = SimpleDirectedGraph::new([("A", "B")]);
1056        let flow_graph = FlowGraph::new(graph.clone(), "A");
1057        assert_eq!(flow_graph.graph, graph);
1058        assert_eq!(flow_graph.start_node, "A");
1059        let flow_graph = FlowGraph::new(graph.clone(), "C");
1060        assert_eq!(flow_graph.graph, graph);
1061        assert_eq!(flow_graph.start_node, "C");
1062    }
1063
1064    #[test]
1065    fn test_post_order() {
1066        // This graph:
1067        //  o F
1068        //  |\
1069        //  o | E
1070        //  | o D
1071        //  | o C
1072        //  | o B
1073        //  |/
1074        //  o A
1075
1076        let neighbors = hashmap! {
1077            'A' => vec![],
1078            'B' => vec!['A'],
1079            'C' => vec!['B'],
1080            'D' => vec!['C'],
1081            'E' => vec!['A'],
1082            'F' => vec!['E', 'D'],
1083        };
1084        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1085        assert_eq!(
1086            post_order('F', neighbors_fn).collect_vec(),
1087            ['A', 'E', 'B', 'C', 'D', 'F']
1088        );
1089        assert_eq!(post_order('E', neighbors_fn).collect_vec(), ['A', 'E']);
1090        assert_eq!(
1091            post_order('D', neighbors_fn).collect_vec(),
1092            ['A', 'B', 'C', 'D']
1093        );
1094        assert_eq!(post_order('A', neighbors_fn).collect_vec(), ['A']);
1095
1096        // This graph:
1097        //  o I
1098        //  |\
1099        //  | o H
1100        //  | |\
1101        //  | | o G
1102        //  | o | F
1103        //  | | o E
1104        //  o |/ D
1105        //  | o C
1106        //  o | B
1107        //  |/
1108        //  o A
1109
1110        let neighbors = hashmap! {
1111            'A' => vec![],
1112            'B' => vec!['A'],
1113            'C' => vec!['A'],
1114            'D' => vec!['B'],
1115            'E' => vec!['C'],
1116            'F' => vec!['C'],
1117            'G' => vec!['E'],
1118            'H' => vec!['F', 'G'],
1119            'I' => vec!['D', 'H'],
1120        };
1121        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1122        assert_eq!(
1123            post_order('I', neighbors_fn).collect_vec(),
1124            ['A', 'B', 'D', 'C', 'F', 'E', 'G', 'H', 'I']
1125        );
1126
1127        // This graph:
1128        //  o I
1129        //  |\
1130        //  | |\
1131        //  | | |\
1132        //  | | | o h (h > I)
1133        //  | | |/|
1134        //  | | o | G
1135        //  | |/| o f
1136        //  | o |/ e (e > I, G)
1137        //  |/| o D
1138        //  o |/ C
1139        //  | o b (b > D)
1140        //  |/
1141        //  o A
1142
1143        let neighbors = hashmap! {
1144            'A' => vec![],
1145            'b' => vec!['A'],
1146            'C' => vec!['A'],
1147            'D' => vec!['b'],
1148            'e' => vec!['C', 'b'],
1149            'f' => vec!['D'],
1150            'G' => vec!['e', 'D'],
1151            'h' => vec!['G', 'f'],
1152            'I' => vec!['C', 'e', 'G', 'h'],
1153        };
1154        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1155        assert_eq!(
1156            post_order('I', neighbors_fn).collect_vec(),
1157            ['A', 'C', 'b', 'e', 'D', 'G', 'f', 'h', 'I']
1158        );
1159
1160        // This graph:
1161        //  o G
1162        //  |\
1163        //  | o F
1164        //  o | E
1165        //  | o D
1166        //  |/
1167        //  o C
1168        //  o B
1169        //  o A
1170
1171        let neighbors = hashmap! {
1172            'A' => vec![],
1173            'B' => vec!['A'],
1174            'C' => vec!['B'],
1175            'D' => vec!['C'],
1176            'E' => vec!['C'],
1177            'F' => vec!['D'],
1178            'G' => vec!['E', 'F'],
1179        };
1180        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1181        assert_eq!(
1182            post_order('G', neighbors_fn).collect_vec(),
1183            ['A', 'B', 'C', 'E', 'D', 'F', 'G']
1184        );
1185
1186        // This graph:
1187        //  o G
1188        //  |\
1189        //  o | F
1190        //  o | E
1191        //  | o D
1192        //  |/
1193        //  o c (c > E, D)
1194        //  o B
1195        //  o A
1196
1197        let neighbors = hashmap! {
1198            'A' => vec![],
1199            'B' => vec!['A'],
1200            'c' => vec!['B'],
1201            'D' => vec!['c'],
1202            'E' => vec!['c'],
1203            'F' => vec!['E'],
1204            'G' => vec!['F', 'D'],
1205        };
1206        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1207        assert_eq!(
1208            post_order('G', neighbors_fn).collect_vec(),
1209            ['A', 'B', 'c', 'E', 'F', 'D', 'G']
1210        );
1211
1212        // This graph:
1213        //  o F
1214        //  |\
1215        //  o | E
1216        //  | o D
1217        //  | | o C
1218        //  | | |
1219        //  | | o B
1220        //  | |/
1221        //  |/
1222        //  o A
1223
1224        let neighbors = hashmap! {
1225            'A' => vec![],
1226            'B' => vec!['A'],
1227            'C' => vec!['B'],
1228            'D' => vec!['A'],
1229            'E' => vec!['A'],
1230            'F' => vec!['E', 'D'],
1231        };
1232        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1233        assert_eq!(
1234            post_order('F', neighbors_fn).collect_vec(),
1235            ['A', 'E', 'D', 'F']
1236        );
1237        assert_eq!(post_order('C', neighbors_fn).collect_vec(), ['A', 'B', 'C']);
1238
1239        // This graph:
1240        //  o D
1241        //  | \
1242        //  o | C
1243        //    o B
1244        //    o A
1245
1246        let neighbors = hashmap! {
1247            'A' => vec![],
1248            'B' => vec!['A'],
1249            'C' => vec![],
1250            'D' => vec!['C', 'B'],
1251        };
1252        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1253        assert_eq!(
1254            post_order('D', neighbors_fn).collect_vec(),
1255            ['C', 'A', 'B', 'D']
1256        );
1257
1258        // This graph:
1259        //  o C
1260        //  o B
1261        //  o A (to C)
1262
1263        let neighbors = hashmap! {
1264            'A' => vec!['C'],
1265            'B' => vec!['A'],
1266            'C' => vec!['B'],
1267        };
1268        let neighbors_fn = |node: &char| neighbors[node].iter().copied();
1269        assert_eq!(post_order('C', neighbors_fn).collect_vec(), ['A', 'B', 'C']);
1270        assert_eq!(post_order('B', neighbors_fn).collect_vec(), ['C', 'A', 'B']);
1271        assert_eq!(post_order('A', neighbors_fn).collect_vec(), ['B', 'C', 'A']);
1272    }
1273}