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