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}