rustc_data_structures/graph/scc/
mod.rs

1//! Routine to compute the strongly connected components (SCCs) of a graph.
2//!
3//! Also computes as the resulting DAG if each SCC is replaced with a
4//! node in the graph. This uses [Tarjan's algorithm](
5//! https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm)
6//! that completes in *O*(*n*) time.
7//! Optionally, also annotate the SCC nodes with some commutative data.
8//! Typical examples would include: minimum element in SCC, maximum element
9//! reachable from it, etc.
10
11use std::assert_matches::debug_assert_matches;
12use std::fmt::Debug;
13use std::marker::PhantomData;
14use std::ops::Range;
15
16use rustc_index::{Idx, IndexSlice, IndexVec};
17use tracing::{debug, instrument, trace};
18
19use crate::fx::FxHashSet;
20use crate::graph::vec_graph::VecGraph;
21use crate::graph::{DirectedGraph, NumEdges, Successors};
22
23#[cfg(test)]
24mod tests;
25
26/// An annotation for an SCC. This can be a representative,
27/// the max/min element of the SCC, or all of the above.
28///
29/// Concretely, the both merge operations must commute, e.g. where `merge`
30/// is `merge_scc` and `merge_reached`: `a.merge(b) == b.merge(a)`
31///
32/// In general, what you want is probably always min/max according
33/// to some ordering, potentially with side constraints (min x such
34/// that P holds).
35pub trait Annotation: Debug + Copy {
36    /// Merge two existing annotations into one during
37    /// path compression.o
38    fn merge_scc(self, other: Self) -> Self;
39
40    /// Merge a successor into this annotation.
41    fn merge_reached(self, other: Self) -> Self;
42
43    fn update_scc(&mut self, other: Self) {
44        *self = self.merge_scc(other)
45    }
46
47    fn update_reachable(&mut self, other: Self) {
48        *self = self.merge_reached(other)
49    }
50}
51
52/// An accumulator for annotations.
53pub trait Annotations<N: Idx> {
54    type Ann: Annotation;
55    type SccIdx: Idx + Ord;
56
57    fn new(&self, element: N) -> Self::Ann;
58    fn annotate_scc(&mut self, scc: Self::SccIdx, annotation: Self::Ann);
59}
60
61/// The nil annotation accumulator, which does nothing.
62struct NoAnnotations<S: Idx + Ord>(PhantomData<S>);
63
64impl<N: Idx, S: Idx + Ord> Annotations<N> for NoAnnotations<S> {
65    type SccIdx = S;
66    type Ann = ();
67    fn new(&self, _element: N) {}
68    fn annotate_scc(&mut self, _scc: S, _annotation: ()) {}
69}
70
71/// The empty annotation, which does nothing.
72impl Annotation for () {
73    fn merge_reached(self, _other: Self) -> Self {
74        ()
75    }
76    fn merge_scc(self, _other: Self) -> Self {
77        ()
78    }
79}
80
81/// Strongly connected components (SCC) of a graph. The type `N` is
82/// the index type for the graph nodes and `S` is the index type for
83/// the SCCs. We can map from each node to the SCC that it
84/// participates in, and we also have the successors of each SCC.
85pub struct Sccs<N: Idx, S: Idx> {
86    /// For each node, what is the SCC index of the SCC to which it
87    /// belongs.
88    scc_indices: IndexVec<N, S>,
89
90    /// Data about all the SCCs.
91    scc_data: SccData<S>,
92}
93
94/// Information about an invidividual SCC node.
95struct SccDetails {
96    /// For this SCC, the range of `all_successors` where its
97    /// successors can be found.
98    range: Range<usize>,
99}
100
101// The name of this struct should discourage you from making it public and leaking
102// its representation. This message was left here by one who came before you,
103// who learnt the hard way that making even small changes in representation
104// is difficult when it's publicly inspectable.
105//
106// Obey the law of Demeter!
107struct SccData<S: Idx> {
108    /// Maps SCC indices to their metadata, including
109    /// offsets into `all_successors`.
110    scc_details: IndexVec<S, SccDetails>,
111
112    /// Contains the successors for all the Sccs, concatenated. The
113    /// range of indices corresponding to a given SCC is found in its
114    /// `scc_details.range`.
115    all_successors: Vec<S>,
116}
117
118impl<N: Idx, S: Idx + Ord> Sccs<N, S> {
119    /// Compute SCCs without annotations.
120    pub fn new(graph: &impl Successors<Node = N>) -> Self {
121        Self::new_with_annotation(graph, &mut NoAnnotations(PhantomData::<S>))
122    }
123
124    /// Compute SCCs and annotate them with a user-supplied annotation
125    pub fn new_with_annotation<A: Annotations<N, SccIdx = S>>(
126        graph: &impl Successors<Node = N>,
127        annotations: &mut A,
128    ) -> Self {
129        SccsConstruction::construct(graph, annotations)
130    }
131
132    pub fn scc_indices(&self) -> &IndexSlice<N, S> {
133        &self.scc_indices
134    }
135
136    /// Returns the number of SCCs in the graph.
137    pub fn num_sccs(&self) -> usize {
138        self.scc_data.len()
139    }
140
141    /// Returns an iterator over the SCCs in the graph.
142    ///
143    /// The SCCs will be iterated in **dependency order** (or **post order**),
144    /// meaning that if `S1 -> S2`, we will visit `S2` first and `S1` after.
145    /// This is convenient when the edges represent dependencies: when you visit
146    /// `S1`, the value for `S2` will already have been computed.
147    pub fn all_sccs(&self) -> impl Iterator<Item = S> + 'static {
148        (0..self.scc_data.len()).map(S::new)
149    }
150
151    /// Returns the SCC to which a node `r` belongs.
152    pub fn scc(&self, r: N) -> S {
153        self.scc_indices[r]
154    }
155
156    /// Returns the successors of the given SCC.
157    pub fn successors(&self, scc: S) -> &[S] {
158        self.scc_data.successors(scc)
159    }
160
161    /// Construct the reverse graph of the SCC graph.
162    pub fn reverse(&self) -> VecGraph<S> {
163        VecGraph::new(
164            self.num_sccs(),
165            self.all_sccs()
166                .flat_map(|source| {
167                    self.successors(source).iter().map(move |&target| (target, source))
168                })
169                .collect(),
170        )
171    }
172}
173
174impl<N: Idx, S: Idx + Ord> DirectedGraph for Sccs<N, S> {
175    type Node = S;
176
177    fn num_nodes(&self) -> usize {
178        self.num_sccs()
179    }
180}
181
182impl<N: Idx, S: Idx + Ord> NumEdges for Sccs<N, S> {
183    fn num_edges(&self) -> usize {
184        self.scc_data.all_successors.len()
185    }
186}
187
188impl<N: Idx, S: Idx + Ord> Successors for Sccs<N, S> {
189    fn successors(&self, node: S) -> impl Iterator<Item = Self::Node> {
190        self.successors(node).iter().cloned()
191    }
192}
193
194impl<S: Idx> SccData<S> {
195    /// Number of SCCs,
196    fn len(&self) -> usize {
197        self.scc_details.len()
198    }
199
200    /// Returns the successors of the given SCC.
201    fn successors(&self, scc: S) -> &[S] {
202        &self.all_successors[self.scc_details[scc].range.clone()]
203    }
204
205    /// Creates a new SCC with `successors` as its successors and
206    /// returns the resulting index.
207    fn create_scc(&mut self, successors: impl IntoIterator<Item = S>) -> S {
208        // Store the successors on `scc_successors_vec`, remembering
209        // the range of indices.
210        let all_successors_start = self.all_successors.len();
211        self.all_successors.extend(successors);
212        let all_successors_end = self.all_successors.len();
213
214        debug!(
215            "create_scc({:?}) successors={:?}",
216            self.len(),
217            &self.all_successors[all_successors_start..all_successors_end],
218        );
219
220        let range = all_successors_start..all_successors_end;
221        let metadata = SccDetails { range };
222        self.scc_details.push(metadata)
223    }
224}
225
226struct SccsConstruction<'c, 'a, G, A>
227where
228    G: DirectedGraph + Successors,
229    A: Annotations<G::Node>,
230{
231    graph: &'c G,
232
233    /// The state of each node; used during walk to record the stack
234    /// and after walk to record what cycle each node ended up being
235    /// in.
236    node_states: IndexVec<G::Node, NodeState<G::Node, A::SccIdx, A::Ann>>,
237
238    /// The stack of nodes that we are visiting as part of the DFS.
239    node_stack: Vec<G::Node>,
240
241    /// The stack of successors: as we visit a node, we mark our
242    /// position in this stack, and when we encounter a successor SCC,
243    /// we push it on the stack. When we complete an SCC, we can pop
244    /// everything off the stack that was found along the way.
245    successors_stack: Vec<A::SccIdx>,
246
247    /// A set used to strip duplicates. As we accumulate successors
248    /// into the successors_stack, we sometimes get duplicate entries.
249    /// We use this set to remove those -- we also keep its storage
250    /// around between successors to amortize memory allocation costs.
251    duplicate_set: FxHashSet<A::SccIdx>,
252
253    scc_data: SccData<A::SccIdx>,
254
255    annotations: &'a mut A,
256}
257
258#[derive(Copy, Clone, Debug)]
259enum NodeState<N, S, A: Annotation> {
260    /// This node has not yet been visited as part of the DFS.
261    ///
262    /// After SCC construction is complete, this state ought to be
263    /// impossible.
264    NotVisited,
265
266    /// This node is currently being walked as part of our DFS. It is on
267    /// the stack at the depth `depth` and its current annotation is
268    /// `annotation`.
269    ///
270    /// After SCC construction is complete, this state ought to be
271    /// impossible.
272    BeingVisited { depth: usize, annotation: A },
273
274    /// Indicates that this node is a member of the given cycle where
275    /// the merged annotation is `annotation`.
276    /// Note that an SCC can have several cycles, so its final annotation
277    /// is the merged value of all its member annotations.
278    InCycle { scc_index: S, annotation: A },
279
280    /// Indicates that this node is a member of whatever cycle
281    /// `parent` is a member of. This state is transient: whenever we
282    /// see it, we try to overwrite it with the current state of
283    /// `parent` (this is the "path compression" step of a union-find
284    /// algorithm).
285    InCycleWith { parent: N },
286}
287
288/// The state of walking a given node.
289#[derive(Copy, Clone, Debug)]
290enum WalkReturn<S, A: Annotation> {
291    /// The walk found a cycle, but the entire component is not known to have
292    /// been fully walked yet. We only know the minimum depth of  this
293    /// component in a minimum spanning tree of the graph. This component
294    /// is tentatively represented by the state of the first node of this
295    /// cycle we met, which is at `min_depth`.
296    Cycle { min_depth: usize, annotation: A },
297    /// The SCC and everything reachable from it have been fully walked.
298    /// At this point we know what is inside the SCC as we have visited every
299    /// node reachable from it. The SCC can now be fully represented by its ID.
300    Complete { scc_index: S, annotation: A },
301}
302
303impl<'c, 'a, G, A> SccsConstruction<'c, 'a, G, A>
304where
305    G: DirectedGraph + Successors,
306    A: Annotations<G::Node>,
307{
308    /// Identifies SCCs in the graph `G` and computes the resulting
309    /// DAG. This uses a variant of [Tarjan's
310    /// algorithm][wikipedia]. The high-level summary of the algorithm
311    /// is that we do a depth-first search. Along the way, we keep a
312    /// stack of each node whose successors are being visited. We
313    /// track the depth of each node on this stack (there is no depth
314    /// if the node is not on the stack). When we find that some node
315    /// N with depth D can reach some other node N' with lower depth
316    /// D' (i.e., D' < D), we know that N, N', and all nodes in
317    /// between them on the stack are part of an SCC.
318    ///
319    /// Additionally, we keep track of a current annotation of the SCC.
320    ///
321    /// [wikipedia]: https://bit.ly/2EZIx84
322    fn construct(graph: &'c G, annotations: &'a mut A) -> Sccs<G::Node, A::SccIdx> {
323        let num_nodes = graph.num_nodes();
324
325        let mut this = Self {
326            graph,
327            node_states: IndexVec::from_elem_n(NodeState::NotVisited, num_nodes),
328            node_stack: Vec::with_capacity(num_nodes),
329            successors_stack: Vec::new(),
330            scc_data: SccData { scc_details: IndexVec::new(), all_successors: Vec::new() },
331            duplicate_set: FxHashSet::default(),
332            annotations,
333        };
334
335        let scc_indices = graph
336            .iter_nodes()
337            .map(|node| match this.start_walk_from(node) {
338                WalkReturn::Complete { scc_index, .. } => scc_index,
339                WalkReturn::Cycle { min_depth, .. } => {
340                    panic!("`start_walk_node({node:?})` returned cycle with depth {min_depth:?}")
341                }
342            })
343            .collect();
344
345        Sccs { scc_indices, scc_data: this.scc_data }
346    }
347
348    fn start_walk_from(&mut self, node: G::Node) -> WalkReturn<A::SccIdx, A::Ann> {
349        self.inspect_node(node).unwrap_or_else(|| self.walk_unvisited_node(node))
350    }
351
352    /// Inspect a node during the DFS. We first examine its current
353    /// state -- if it is not yet visited (`NotVisited`), return `None` so
354    /// that the caller might push it onto the stack and start walking its
355    /// successors.
356    ///
357    /// If it is already on the DFS stack it will be in the state
358    /// `BeingVisited`. In that case, we have found a cycle and we
359    /// return the depth from the stack.
360    ///
361    /// Otherwise, we are looking at a node that has already been
362    /// completely visited. We therefore return `WalkReturn::Complete`
363    /// with its associated SCC index.
364    fn inspect_node(&mut self, node: G::Node) -> Option<WalkReturn<A::SccIdx, A::Ann>> {
365        Some(match self.find_state(node) {
366            NodeState::InCycle { scc_index, annotation } => {
367                WalkReturn::Complete { scc_index, annotation }
368            }
369
370            NodeState::BeingVisited { depth: min_depth, annotation } => {
371                WalkReturn::Cycle { min_depth, annotation }
372            }
373
374            NodeState::NotVisited => return None,
375
376            NodeState::InCycleWith { parent } => panic!(
377                "`find_state` returned `InCycleWith({parent:?})`, which ought to be impossible"
378            ),
379        })
380    }
381
382    /// Fetches the state of the node `r`. If `r` is recorded as being
383    /// in a cycle with some other node `r2`, then fetches the state
384    /// of `r2` (and updates `r` to reflect current result). This is
385    /// basically the "find" part of a standard union-find algorithm
386    /// (with path compression).
387    fn find_state(&mut self, mut node: G::Node) -> NodeState<G::Node, A::SccIdx, A::Ann> {
388        // To avoid recursion we temporarily reuse the `parent` of each
389        // InCycleWith link to encode a downwards link while compressing
390        // the path. After we have found the root or deepest node being
391        // visited, we traverse the reverse links and correct the node
392        // states on the way.
393        //
394        // **Note**: This mutation requires that this is a leaf function
395        // or at least that none of the called functions inspects the
396        // current node states. Luckily, we are a leaf.
397
398        // Remember one previous link. The termination condition when
399        // following links downwards is then simply as soon as we have
400        // found the initial self-loop.
401        let mut previous_node = node;
402
403        // Ultimately propagated to all the transitive parents when following
404        // `InCycleWith` upwards.
405        // This loop performs the downward link encoding mentioned above. Details below!
406        // Note that there are two different states being assigned: the root state, and
407        // a potentially derived version of the root state for non-root nodes in the chain.
408        let (root_state, assigned_state) = {
409            loop {
410                trace!("find_state(r = {node:?} in state {:?})", self.node_states[node]);
411                match self.node_states[node] {
412                    // This must have been the first and only state since it is unexplored*;
413                    // no update needed! * Unless there is a bug :')
414                    s @ NodeState::NotVisited => return s,
415                    // We are in a completely discovered SCC; every node on our path is in that SCC:
416                    s @ NodeState::InCycle { .. } => break (s, s),
417                    // The Interesting Third Base Case: we are a path back to a root node
418                    // still being explored. Now we need that node to keep its state and
419                    // every other node to be recorded as being in whatever component that
420                    // ends up in.
421                    s @ NodeState::BeingVisited { depth, .. } => {
422                        break (s, NodeState::InCycleWith { parent: self.node_stack[depth] });
423                    }
424                    // We are not at the head of a path; keep compressing it!
425                    NodeState::InCycleWith { parent } => {
426                        // We test this, to be extremely sure that we never
427                        // ever break our termination condition for the
428                        // reverse iteration loop.
429                        assert!(node != parent, "Node can not be in cycle with itself");
430
431                        // Store the previous node as an inverted list link
432                        self.node_states[node] = NodeState::InCycleWith { parent: previous_node };
433                        // Update to parent node.
434                        previous_node = node;
435                        node = parent;
436                    }
437                }
438            }
439        };
440
441        // The states form a graph where up to one outgoing link is stored at
442        // each node. Initially in general,
443        //
444        //                                                  E
445        //                                                  ^
446        //                                                  |
447        //                                InCycleWith/BeingVisited/NotVisited
448        //                                                  |
449        //   A-InCycleWith->B-InCycleWith…>C-InCycleWith->D-+
450        //   |
451        //   = node, previous_node
452        //
453        // After the first loop, this will look like
454        //                                                  E
455        //                                                  ^
456        //                                                  |
457        //                                InCycleWith/BeingVisited/NotVisited
458        //                                                  |
459        // +>A<-InCycleWith-B<…InCycleWith-C<-InCycleWith-D-+
460        // | |                             |              |
461        // | InCycleWith                   |              = node
462        // +-+                             =previous_node
463        //
464        // Note in particular that A will be linked to itself in a self-cycle
465        // and no other self-cycles occur due to how InCycleWith is assigned in
466        // the find phase implemented by `walk_unvisited_node`.
467        //
468        // We now want to compress the path, that is assign the state of the
469        // link D-E to all other links.
470        //
471        // We can then walk backwards, starting from `previous_node`, and assign
472        // each node in the list with the updated state. The loop terminates
473        // when we reach the self-cycle.
474
475        // Move backwards until we found the node where we started. We
476        // will know when we hit the state where previous_node == node.
477        loop {
478            // Back at the beginning, we can return. Note that we return the root state.
479            // This is because for components being explored, we would otherwise get a
480            // `node_state[n] = InCycleWith{ parent: n }` and that's wrong.
481            if previous_node == node {
482                return root_state;
483            }
484            trace!("Compressing {node:?} down to {previous_node:?} with state {assigned_state:?}");
485
486            // Update to previous node in the link.
487            match self.node_states[previous_node] {
488                NodeState::InCycleWith { parent: previous } => {
489                    node = previous_node;
490                    previous_node = previous;
491                }
492                // Only InCycleWith nodes were added to the reverse linked list.
493                other => unreachable!("Invalid previous link while compressing cycle: {other:?}"),
494            }
495
496            // Update the node state to the (potentially derived) state.
497            // If the root is still being explored, this is
498            // `InCycleWith{ parent: <root node>}`, otherwise
499            // `assigned_state == root_state`.
500            self.node_states[node] = assigned_state;
501        }
502    }
503
504    /// Walks a node that has never been visited before.
505    ///
506    /// Call this method when `inspect_node` has returned `None`. Having the
507    /// caller decide avoids mutual recursion between the two methods and allows
508    /// us to maintain an allocated stack for nodes on the path between calls.
509    #[instrument(skip(self, initial), level = "trace")]
510    fn walk_unvisited_node(&mut self, initial: G::Node) -> WalkReturn<A::SccIdx, A::Ann> {
511        trace!("Walk unvisited node: {initial:?}");
512        struct VisitingNodeFrame<G: DirectedGraph, Successors, A> {
513            node: G::Node,
514            successors: Option<Successors>,
515            depth: usize,
516            min_depth: usize,
517            successors_len: usize,
518            min_cycle_root: G::Node,
519            successor_node: G::Node,
520            /// The annotation for the SCC starting in `node`. It may or may
521            /// not contain other nodes.
522            current_component_annotation: A,
523        }
524
525        // Move the stack to a local variable. We want to utilize the existing allocation and
526        // mutably borrow it without borrowing self at the same time.
527        let mut successors_stack = core::mem::take(&mut self.successors_stack);
528
529        debug_assert_eq!(successors_stack.len(), 0);
530
531        let mut stack: Vec<VisitingNodeFrame<G, _, _>> = vec![VisitingNodeFrame {
532            node: initial,
533            depth: 0,
534            min_depth: 0,
535            successors: None,
536            successors_len: 0,
537            min_cycle_root: initial,
538            successor_node: initial,
539            current_component_annotation: self.annotations.new(initial),
540        }];
541
542        let mut return_value = None;
543
544        'recurse: while let Some(frame) = stack.last_mut() {
545            let VisitingNodeFrame {
546                node,
547                depth,
548                successors,
549                successors_len,
550                min_depth,
551                min_cycle_root,
552                successor_node,
553                current_component_annotation,
554            } = frame;
555            let node = *node;
556            let depth = *depth;
557
558            trace!(
559                "Visiting {node:?} at depth {depth:?}, annotation: {current_component_annotation:?}"
560            );
561
562            let successors = match successors {
563                Some(successors) => successors,
564                None => {
565                    // This None marks that we still have the initialize this node's frame.
566                    trace!(?depth, ?node);
567
568                    debug_assert_matches!(self.node_states[node], NodeState::NotVisited);
569
570                    // Push `node` onto the stack.
571                    self.node_states[node] = NodeState::BeingVisited {
572                        depth,
573                        annotation: *current_component_annotation,
574                    };
575                    self.node_stack.push(node);
576
577                    // Walk each successor of the node, looking to see if any of
578                    // them can reach a node that is presently on the stack. If
579                    // so, that means they can also reach us.
580                    *successors_len = successors_stack.len();
581                    // Set and return a reference, this is currently empty.
582                    successors.get_or_insert(self.graph.successors(node))
583                }
584            };
585
586            // Now that the successors iterator is initialized, this is a constant for this frame.
587            let successors_len = *successors_len;
588
589            // Construct iterators for the nodes and walk results. There are two cases:
590            // * The walk of a successor node returned.
591            // * The remaining successor nodes.
592            let returned_walk =
593                return_value.take().into_iter().map(|walk| (*successor_node, Some(walk)));
594
595            let successor_walk = successors.map(|successor_node| {
596                trace!(?node, ?successor_node);
597                (successor_node, self.inspect_node(successor_node))
598            });
599            for (successor_node, walk) in returned_walk.chain(successor_walk) {
600                match walk {
601                    // The starting node `node` leads to a cycle whose earliest node,
602                    // `successor_node`, is at `min_depth`. There may be more cycles.
603                    Some(WalkReturn::Cycle {
604                        min_depth: successor_min_depth,
605                        annotation: successor_annotation,
606                    }) => {
607                        trace!(
608                            "Cycle found from {node:?}, minimum depth: {successor_min_depth:?}, annotation: {successor_annotation:?}"
609                        );
610                        // Track the minimum depth we can reach.
611                        assert!(successor_min_depth <= depth);
612                        if successor_min_depth < *min_depth {
613                            trace!(?node, ?successor_min_depth);
614                            *min_depth = successor_min_depth;
615                            *min_cycle_root = successor_node;
616                        }
617                        current_component_annotation.update_scc(successor_annotation);
618                    }
619                    // The starting node `node` is succeeded by a fully identified SCC
620                    // which is now added to the set under `scc_index`.
621                    Some(WalkReturn::Complete {
622                        scc_index: successor_scc_index,
623                        annotation: successor_annotation,
624                    }) => {
625                        trace!(
626                            "Complete; {node:?} is root of complete-visited SCC idx {successor_scc_index:?} with annotation {successor_annotation:?}"
627                        );
628                        // Push the completed SCC indices onto
629                        // the `successors_stack` for later.
630                        trace!(?node, ?successor_scc_index);
631                        successors_stack.push(successor_scc_index);
632                        current_component_annotation.update_reachable(successor_annotation);
633                    }
634                    // `node` has no more (direct) successors; search recursively.
635                    None => {
636                        let depth = depth + 1;
637                        trace!("Recursing down into {successor_node:?} at depth {depth:?}");
638                        trace!(?depth, ?successor_node);
639                        // Remember which node the return value will come from.
640                        frame.successor_node = successor_node;
641                        // Start a new stack frame, then step into it.
642                        stack.push(VisitingNodeFrame {
643                            node: successor_node,
644                            depth,
645                            successors: None,
646                            successors_len: 0,
647                            min_depth: depth,
648                            min_cycle_root: successor_node,
649                            successor_node,
650                            current_component_annotation: self.annotations.new(successor_node),
651                        });
652                        continue 'recurse;
653                    }
654                }
655            }
656
657            trace!("Finished walk from {node:?} with annotation: {current_component_annotation:?}");
658
659            // Completed walk, remove `node` from the stack.
660            let r = self.node_stack.pop();
661            debug_assert_eq!(r, Some(node));
662
663            // Remove the frame, it's done.
664            let frame = stack.pop().unwrap();
665            let current_component_annotation = frame.current_component_annotation;
666            debug_assert_eq!(frame.node, node);
667
668            // If `min_depth == depth`, then we are the root of the
669            // cycle: we can't reach anyone further down the stack.
670
671            // Pass the 'return value' down the stack.
672            // We return one frame at a time so there can't be another return value.
673            debug_assert!(return_value.is_none());
674            return_value = Some(if frame.min_depth == depth {
675                // We are at the head of the component.
676
677                // Note that successor stack may have duplicates, so we
678                // want to remove those:
679                let deduplicated_successors = {
680                    let duplicate_set = &mut self.duplicate_set;
681                    duplicate_set.clear();
682                    successors_stack
683                        .drain(successors_len..)
684                        .filter(move |&i| duplicate_set.insert(i))
685                };
686
687                debug!("Creating SCC rooted in {node:?} with successor {:?}", frame.successor_node);
688
689                let scc_index = self.scc_data.create_scc(deduplicated_successors);
690
691                self.annotations.annotate_scc(scc_index, current_component_annotation);
692
693                self.node_states[node] =
694                    NodeState::InCycle { scc_index, annotation: current_component_annotation };
695
696                WalkReturn::Complete { scc_index, annotation: current_component_annotation }
697            } else {
698                // We are not the head of the cycle. Return back to our
699                // caller. They will take ownership of the
700                // `self.successors` data that we pushed.
701                self.node_states[node] = NodeState::InCycleWith { parent: frame.min_cycle_root };
702                WalkReturn::Cycle {
703                    min_depth: frame.min_depth,
704                    annotation: current_component_annotation,
705                }
706            });
707        }
708
709        // Keep the allocation we used for successors_stack.
710        self.successors_stack = successors_stack;
711        debug_assert_eq!(self.successors_stack.len(), 0);
712
713        return_value.unwrap()
714    }
715}