1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
34use ahash::AHashMap;
5use fixedbitset::FixedBitSet;
6use nested::Nested;
7use petgraph::{
8 algo::kosaraju_scc,
9 graph::IndexType,
10 prelude::*,
11 visit::{IntoNeighborsDirected, IntoNodeIdentifiers, VisitMap, Visitable},
12};
13use std::slice;
1415#[derive(Clone, Debug)]
16pub(crate) struct Sccs<Ix: IndexType> {
17 sccs: Nested<Vec<NodeIndex<Ix>>>,
18// Map of node indexes to the index of the SCC they belong to. If a node is not part of an SCC,
19 // then the corresponding index is not stored here.
20multi_map: AHashMap<NodeIndex<Ix>, usize>,
21}
2223impl<Ix: IndexType> Sccs<Ix> {
24/// Creates a new instance from the provided graph and the given sorter.
25pub fn new<G>(graph: G, mut scc_sorter: impl FnMut(&mut Vec<NodeIndex<Ix>>)) -> Self
26where
27G: IntoNeighborsDirected<NodeId = NodeIndex<Ix>> + Visitable + IntoNodeIdentifiers,
28 <G as Visitable>::Map: VisitMap<NodeIndex<Ix>>,
29 {
30// Use kosaraju_scc since it is iterative (tarjan_scc is recursive) and package graphs
31 // have unbounded depth.
32let sccs = kosaraju_scc(graph);
33let sccs: Nested<Vec<_>> = sccs
34 .into_iter()
35 .map(|mut scc| {
36if scc.len() > 1 {
37 scc_sorter(&mut scc);
38 }
39 scc
40 })
41// kosaraju_scc returns its sccs in reverse topological order. Reverse it again for
42 // forward topological order.
43.rev()
44 .collect();
45let mut multi_map = AHashMap::new();
46for (idx, scc) in sccs.iter().enumerate() {
47if scc.len() > 1 {
48 multi_map.extend(scc.iter().map(|ix| (*ix, idx)));
49 }
50 }
51Self { sccs, multi_map }
52 }
5354/// Returns true if `a` and `b` are in the same scc.
55pub fn is_same_scc(&self, a: NodeIndex<Ix>, b: NodeIndex<Ix>) -> bool {
56if a == b {
57return true;
58 }
59match (self.multi_map.get(&a), self.multi_map.get(&b)) {
60 (Some(a_scc), Some(b_scc)) => a_scc == b_scc,
61_ => false,
62 }
63 }
6465/// Returns all the SCCs with more than one element.
66pub fn multi_sccs(&self) -> impl DoubleEndedIterator<Item = &[NodeIndex<Ix>]> {
67self.sccs.iter().filter(|scc| scc.len() > 1)
68 }
6970/// Returns all the nodes of this graph that have no incoming edges to them, and all the nodes
71 /// in an SCC into which there are no incoming edges.
72pub fn externals<'a, G>(&'a self, graph: G) -> impl Iterator<Item = NodeIndex<Ix>> + 'a
73where
74G: 'a + IntoNodeIdentifiers + IntoNeighborsDirected<NodeId = NodeIndex<Ix>>,
75 Ix: IndexType,
76 {
77// Consider each SCC as one logical node.
78let mut external_sccs = FixedBitSet::with_capacity(self.sccs.len());
79let mut internal_sccs = FixedBitSet::with_capacity(self.sccs.len());
80 graph
81 .node_identifiers()
82 .filter(move |ix| match self.multi_map.get(ix) {
83Some(&scc_idx) => {
84// Consider one node identifier for each scc -- whichever one comes first.
85if external_sccs.contains(scc_idx) {
86return true;
87 }
88if internal_sccs.contains(scc_idx) {
89return false;
90 }
9192let scc = &self.sccs[scc_idx];
93let is_external = scc
94 .iter()
95 .flat_map(|ix| {
96// Look at all incoming nodes from every SCC member.
97graph.neighbors_directed(*ix, Incoming)
98 })
99 .all(|neighbor_ix| {
100// * Accept any nodes are in the same SCC.
101 // * Any other results imply that this isn't an external scc.
102match self.multi_map.get(&neighbor_ix) {
103Some(neighbor_scc_idx) => neighbor_scc_idx == &scc_idx,
104None => false,
105 }
106 });
107if is_external {
108 external_sccs.insert(scc_idx);
109 } else {
110 internal_sccs.insert(scc_idx);
111 }
112 is_external
113 }
114None => {
115// Not part of an SCC -- just look at whether there are any incoming nodes
116 // at all.
117graph.neighbors_directed(*ix, Incoming).next().is_none()
118 }
119 })
120 }
121122/// Iterate over all nodes in the direction specified.
123pub fn node_iter(&self, direction: Direction) -> NodeIter<Ix> {
124 NodeIter {
125 node_ixs: self.sccs.data().iter(),
126 direction,
127 }
128 }
129}
130131/// An iterator over the nodes of strongly connected components.
132#[derive(Clone, Debug)]
133pub(crate) struct NodeIter<'a, Ix> {
134 node_ixs: slice::Iter<'a, NodeIndex<Ix>>,
135 direction: Direction,
136}
137138impl<Ix> NodeIter<'_, Ix> {
139/// Returns the direction this iteration is happening in.
140#[allow(dead_code)]
141pub fn direction(&self) -> Direction {
142self.direction
143 }
144}
145146impl<Ix: IndexType> Iterator for NodeIter<'_, Ix> {
147type Item = NodeIndex<Ix>;
148149fn next(&mut self) -> Option<NodeIndex<Ix>> {
150// Note that outgoing implies iterating over the sccs in forward order, while incoming means
151 // sccs in reverse order.
152match self.direction {
153 Direction::Outgoing => self.node_ixs.next().copied(),
154 Direction::Incoming => self.node_ixs.next_back().copied(),
155 }
156 }
157}