use std::{
any::{Any, TypeId},
collections::VecDeque,
fmt::Debug,
};
use atomic_refcell::AtomicRefCell;
use enum_map::{Enum, EnumMap, enum_map};
use fixedbitset::FixedBitSet;
use itertools::Itertools;
use petgraph::{
Direction,
adj::UnweightedList,
algo::{TarjanScc, tred},
data::Build,
graph::{DiGraph, NodeIndex},
stable_graph::StableDiGraph,
visit::{DfsPostOrder, EdgeFiltered, EdgeRef, IntoNeighbors, VisitMap, Visitable},
};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{arena::Arena, parse::Info};
use super::{
spec::{ResolvedSpecType, Spec},
types::{
GraphInlineType, GraphOperation, GraphSchemaType, GraphStruct, GraphTagged,
GraphTaggedVariant, GraphType, InlineTypePath, InlineTypePathRoot, InlineTypePathSegment,
SchemaTypeInfo, SpecInlineType, SpecSchemaType, SpecType, SpecUntaggedVariant,
StructFieldName, mapper::TypeMapper,
},
views::{operation::OperationView, primitive::PrimitiveView, schema::SchemaTypeView},
};
type RawDiGraph<'a> = StableDiGraph<GraphType<'a>, EdgeKind, usize>;
type CookedDiGraph<'a> = DiGraph<GraphType<'a>, EdgeKind, usize>;
#[derive(Debug)]
pub struct RawGraph<'a> {
arena: &'a Arena,
spec: &'a Spec<'a>,
graph: RawDiGraph<'a>,
ops: &'a [&'a GraphOperation<'a>],
}
impl<'a> RawGraph<'a> {
pub fn new(arena: &'a Arena, spec: &'a Spec<'a>) -> Self {
let tys = SpecTypeVisitor::new(
spec.schemas
.values()
.chain(spec.operations.iter().flat_map(|op| op.types().copied())),
);
let mut indices = FxHashMap::default();
let mut schemas = FxHashMap::default();
let mut nodes = vec![];
let mut edges = vec![];
for (parent, kind, child) in tys {
use std::collections::hash_map::Entry;
let source = spec.resolve(child);
let &mut to = match indices.entry(source) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let index = NodeIndex::new(nodes.len());
nodes.push(*entry.key());
entry.insert(index)
}
};
if let ResolvedSpecType::Schema(ty) = source {
schemas.entry(ty.name()).or_insert(to);
}
if let Some(parent) = parent {
let destination = spec.resolve(parent);
let &mut from = match indices.entry(destination) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let index = NodeIndex::new(nodes.len());
nodes.push(*entry.key());
entry.insert(index)
}
};
if let ResolvedSpecType::Schema(ty) = destination {
schemas.entry(ty.name()).or_insert(from);
}
edges.push((from, to, kind));
}
}
let mut graph = RawDiGraph::with_capacity(nodes.len(), edges.len());
let mapper = TypeMapper::new(arena, |ty: &SpecType<'_>| match ty {
SpecType::Schema(s) => indices[&ResolvedSpecType::Schema(s)],
SpecType::Inline(i) => indices[&ResolvedSpecType::Inline(i)],
SpecType::Ref(r) => schemas[&*r.name()],
});
for node in nodes {
let mapped = match node {
ResolvedSpecType::Schema(ty) => GraphType::Schema(mapper.schema(ty)),
ResolvedSpecType::Inline(ty) => GraphType::Inline(mapper.inline(ty)),
};
let index = graph.add_node(mapped);
debug_assert_eq!(index, indices[&node]);
}
for (from, to, kind) in edges {
graph.add_edge(from, to, kind);
}
let ops = arena.alloc_slice_exact(spec.operations.iter().map(|op| mapper.operation(op)));
Self {
arena,
spec,
graph,
ops,
}
}
pub fn inline_tagged_variants(&mut self) -> &mut Self {
struct TaggedPlan<'a> {
tagged_index: NodeIndex<usize>,
info: SchemaTypeInfo<'a>,
tagged: GraphTagged<'a>,
inlines: Vec<VariantInline<'a>>,
}
struct VariantInline<'a> {
node: GraphType<'a>,
variant_index: NodeIndex<usize>,
parent_indices: &'a [NodeIndex<usize>],
name: &'a str,
aliases: &'a [&'a str],
}
let used_by_ops: FixedBitSet = self
.ops
.iter()
.flat_map(|op| op.types())
.map(|node| node.index())
.collect();
let plans = self
.graph
.node_indices()
.filter_map(|index| {
let GraphType::Schema(GraphSchemaType::Tagged(info, tagged)) = self.graph[index]
else {
return None;
};
let mut inlines = vec![];
for variant in tagged.variants {
let variant_index = variant.ty;
let GraphType::Schema(GraphSchemaType::Struct(variant_info, variant_struct)) =
self.graph[variant_index]
else {
continue;
};
if !used_by_ops.contains(variant_index.index()) {
let Some(first) = ({
self.graph
.neighbors_directed(variant_index, Direction::Incoming)
.find_map(|neighbor| match self.graph[neighbor] {
GraphType::Schema(GraphSchemaType::Tagged(_, t)) => Some(t),
_ => None,
})
}) else {
continue;
};
let all_agree = self
.graph
.neighbors_directed(variant_index, Direction::Incoming)
.all(|neighbor| {
matches!(
self.graph[neighbor],
GraphType::Schema(GraphSchemaType::Tagged(_, t))
if t.tag == first.tag && t.fields == first.fields,
)
});
if all_agree {
continue;
}
}
let (has_tag_field, already_inherits) = {
let inherits = EdgeFiltered::from_fn(&self.graph, |e| {
matches!(e.weight(), EdgeKind::Inherits)
});
let mut dfs = DfsPostOrder::new(&inherits, variant_index);
let mut has_tag_field = false;
let mut already_inherits = false;
while let Some(ancestor) = dfs.next(&inherits)
&& !(has_tag_field && already_inherits)
{
already_inherits |= ancestor == index;
has_tag_field |= match self.graph[ancestor] {
GraphType::Schema(GraphSchemaType::Struct(_, s))
| GraphType::Inline(GraphInlineType::Struct(_, s)) => {
s.fields.iter().any(|f| {
matches!(
f.name,
StructFieldName::Name(n) if n == tagged.tag,
)
})
}
_ => false,
};
}
(has_tag_field, already_inherits)
};
if !has_tag_field && (tagged.fields.is_empty() || already_inherits) {
continue;
}
let parents = self.arena.alloc_slice(itertools::chain!(
variant_struct.parents.iter().copied(),
std::iter::once(index),
));
let node = GraphType::Inline(GraphInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type(info.name),
segments: self.arena.alloc_slice_copy(&[
InlineTypePathSegment::TaggedVariant(variant_info.name),
]),
},
GraphStruct {
description: variant_struct.description,
fields: variant_struct.fields,
parents,
},
));
inlines.push(VariantInline {
node,
variant_index,
parent_indices: parents,
name: variant.name,
aliases: variant.aliases,
});
}
if inlines.is_empty() {
return None;
}
Some(TaggedPlan {
tagged_index: index,
info,
tagged,
inlines,
})
})
.collect_vec();
for plan in plans {
let mut new_variants = FxHashMap::default();
for entry in &plan.inlines {
let node_index = self.graph.add_node(entry.node);
self.graph
.add_edge(node_index, entry.variant_index, EdgeKind::Reference);
for &parent_index in entry.parent_indices {
self.graph
.add_edge(node_index, parent_index, EdgeKind::Inherits);
}
new_variants.insert(
entry.variant_index,
GraphTaggedVariant {
name: entry.name,
aliases: entry.aliases,
ty: node_index,
},
);
}
let edges_to_retarget = self
.graph
.edges_directed(plan.tagged_index, Direction::Outgoing)
.filter(|e| {
matches!(e.weight(), EdgeKind::Reference)
&& new_variants.contains_key(&e.target())
})
.map(|e| (e.id(), new_variants[&e.target()].ty))
.collect_vec();
for (edge_id, new_target) in edges_to_retarget {
self.graph.remove_edge(edge_id);
self.graph
.add_edge(plan.tagged_index, new_target, EdgeKind::Reference);
}
let modified_tagged = GraphTagged {
description: plan.tagged.description,
tag: plan.tagged.tag,
variants: self.arena.alloc_slice_exact(
plan.tagged
.variants
.iter()
.map(|&v| new_variants.get(&v.ty).copied().unwrap_or(v)),
),
fields: plan.tagged.fields,
};
self.graph[plan.tagged_index] =
GraphType::Schema(GraphSchemaType::Tagged(plan.info, modified_tagged));
}
self
}
#[inline]
pub fn cook(&self) -> CookedGraph<'a> {
CookedGraph::new(self)
}
}
#[derive(Debug)]
pub struct CookedGraph<'a> {
pub(super) graph: CookedDiGraph<'a>,
info: &'a Info,
ops: &'a [&'a GraphOperation<'a>],
pub(super) metadata: CookedGraphMetadata<'a>,
}
impl<'a> CookedGraph<'a> {
fn new(raw: &RawGraph<'a>) -> Self {
let indices: FxHashMap<_, _> = raw
.graph
.node_indices()
.enumerate()
.map(|(cooked, raw)| (raw, NodeIndex::new(cooked)))
.collect();
let mapper = TypeMapper::new(raw.arena, |index| indices[&index]);
let mut graph = CookedDiGraph::with_capacity(indices.len(), raw.graph.edge_count());
for index in raw.graph.node_indices() {
let node = raw.graph[index];
let mapped = match node {
GraphType::Schema(ty) => GraphType::Schema(mapper.schema(&ty)),
GraphType::Inline(ty) => GraphType::Inline(mapper.inline(&ty)),
};
let cooked = graph.add_node(mapped);
debug_assert_eq!(indices[&index], cooked);
}
for index in raw.graph.node_indices() {
let from = indices[&index];
let edges = raw
.graph
.edges(index)
.map(|e| (indices[&e.target()], *e.weight()))
.collect_vec();
for (to, kind) in edges.into_iter().rev() {
graph.add_edge(from, to, kind);
}
}
let sccs = TopoSccs::new(&graph);
let (metadata, ops) = {
let mut metadata = CookedGraphMetadata {
scc_indices: {
let refs = EdgeFiltered::from_fn(&graph, |e| {
matches!(e.weight(), EdgeKind::Reference)
});
let mut scc = TarjanScc::new();
scc.run(&refs, |_| ());
graph
.node_indices()
.map(|node| scc.node_component_index(&refs, node))
.collect()
},
schemas: std::iter::repeat_with(GraphNodeMeta::default)
.take(graph.node_count())
.collect(),
operations: FxHashMap::default(),
};
let ops: &_ = raw
.arena
.alloc_slice_exact(raw.ops.iter().map(|&op| mapper.operation(op)));
for &&op in ops {
metadata.operations.entry(op).or_default().types =
op.types().map(|node| node.index()).collect();
}
{
let condensation = sccs.condensation();
let (_, closure) = tred::dag_transitive_reduction_closure(&condensation);
let mut scc_deps =
vec![FixedBitSet::with_capacity(graph.node_count()); sccs.scc_count()];
for (scc, deps) in scc_deps.iter_mut().enumerate() {
deps.extend(
std::iter::once(scc)
.chain(closure.neighbors(scc))
.flat_map(|scc| sccs.sccs[scc].ones()),
);
}
let mut node_dependents =
vec![FixedBitSet::with_capacity(graph.node_count()); graph.node_count()];
for node in graph.node_indices() {
let mut deps = scc_deps[sccs.topo_index(node)].clone();
deps.remove(node.index()); for dep in deps.ones() {
node_dependents[dep].insert(node.index());
}
metadata.schemas[node.index()].dependencies = deps;
}
for (index, dependents) in node_dependents.into_iter().enumerate() {
metadata.schemas[index].dependents = dependents;
}
}
for &&op in ops {
let meta = &metadata.operations[&op];
let mut transitive_deps = FixedBitSet::with_capacity(graph.node_count());
for node in meta.types.ones() {
transitive_deps.insert(node);
transitive_deps.union_with(&metadata.schemas[node].dependencies);
}
for node in transitive_deps.ones() {
metadata.schemas[node].used_by.insert(op);
}
}
(metadata, ops)
};
Self {
graph,
info: raw.spec.info,
ops,
metadata,
}
}
#[inline]
pub fn info(&self) -> &'a Info {
self.info
}
#[inline]
pub fn schemas(&self) -> impl Iterator<Item = SchemaTypeView<'_>> {
self.graph
.node_indices()
.filter_map(|index| match self.graph[index] {
GraphType::Schema(ty) => Some(SchemaTypeView::new(self, index, ty)),
_ => None,
})
}
#[inline]
pub fn primitives(&self) -> impl Iterator<Item = PrimitiveView<'_>> {
self.graph
.node_indices()
.filter_map(|index| match self.graph[index] {
GraphType::Schema(GraphSchemaType::Primitive(_, p))
| GraphType::Inline(GraphInlineType::Primitive(_, p)) => {
Some(PrimitiveView::new(self, index, p))
}
_ => None,
})
}
#[inline]
pub fn operations(&self) -> impl Iterator<Item = OperationView<'_>> {
self.ops.iter().map(move |&op| OperationView::new(self, op))
}
}
#[derive(Clone, Copy, Debug, Enum, Eq, Hash, PartialEq)]
pub enum EdgeKind {
Reference,
Inherits,
}
#[derive(Debug, Default)]
pub(super) struct CookedGraphMetadata<'a> {
pub scc_indices: Vec<usize>,
pub schemas: Vec<GraphNodeMeta<'a>>,
pub operations: FxHashMap<GraphOperation<'a>, GraphOperationMeta>,
}
#[derive(Debug, Default)]
pub(super) struct GraphOperationMeta {
pub types: FixedBitSet,
}
#[derive(Default)]
pub(super) struct GraphNodeMeta<'a> {
pub used_by: FxHashSet<GraphOperation<'a>>,
pub dependencies: FixedBitSet,
pub dependents: FixedBitSet,
pub extensions: AtomicRefCell<ExtensionMap>,
}
impl Debug for GraphNodeMeta<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GraphNodeMeta")
.field("used_by", &self.used_by)
.field("dependencies", &self.dependencies)
.field("dependents", &self.dependents)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
struct SpecTypeVisitor<'a> {
stack: Vec<(Option<&'a SpecType<'a>>, EdgeKind, &'a SpecType<'a>)>,
}
impl<'a> SpecTypeVisitor<'a> {
#[inline]
fn new(roots: impl Iterator<Item = &'a SpecType<'a>>) -> Self {
let mut stack = roots
.map(|root| (None, EdgeKind::Reference, root))
.collect_vec();
stack.reverse();
Self { stack }
}
}
impl<'a> Iterator for SpecTypeVisitor<'a> {
type Item = (Option<&'a SpecType<'a>>, EdgeKind, &'a SpecType<'a>);
fn next(&mut self) -> Option<Self::Item> {
let (parent, kind, top) = self.stack.pop()?;
match top {
SpecType::Schema(SpecSchemaType::Struct(_, ty))
| SpecType::Inline(SpecInlineType::Struct(_, ty)) => {
self.stack.extend(
itertools::chain!(
ty.fields
.iter()
.map(|field| (EdgeKind::Reference, field.ty)),
ty.parents
.iter()
.map(|parent| (EdgeKind::Inherits, *parent)),
)
.map(|(kind, ty)| (Some(top), kind, ty))
.rev(),
);
}
SpecType::Schema(SpecSchemaType::Untagged(_, ty))
| SpecType::Inline(SpecInlineType::Untagged(_, ty)) => {
self.stack.extend(
itertools::chain!(
ty.fields
.iter()
.map(|field| (EdgeKind::Reference, field.ty)),
ty.variants.iter().filter_map(|variant| match variant {
SpecUntaggedVariant::Some(_, ty) => {
Some((EdgeKind::Reference, *ty))
}
_ => None,
}),
)
.map(|(kind, ty)| (Some(top), kind, ty))
.rev(),
);
}
SpecType::Schema(SpecSchemaType::Tagged(_, ty))
| SpecType::Inline(SpecInlineType::Tagged(_, ty)) => {
self.stack.extend(
itertools::chain!(
ty.fields
.iter()
.map(|field| (EdgeKind::Reference, field.ty)),
ty.variants
.iter()
.map(|variant| (EdgeKind::Reference, variant.ty)),
)
.map(|(kind, ty)| (Some(top), kind, ty))
.rev(),
);
}
SpecType::Schema(SpecSchemaType::Container(_, container))
| SpecType::Inline(SpecInlineType::Container(_, container)) => {
self.stack
.push((Some(top), EdgeKind::Reference, container.inner().ty));
}
SpecType::Schema(
SpecSchemaType::Enum(..) | SpecSchemaType::Primitive(..) | SpecSchemaType::Any(_),
)
| SpecType::Inline(
SpecInlineType::Enum(..) | SpecInlineType::Primitive(..) | SpecInlineType::Any(_),
) => (),
SpecType::Ref(_) => (),
}
Some((parent, kind, top))
}
}
pub(super) type ExtensionMap = FxHashMap<TypeId, Box<dyn Extension>>;
pub trait Extension: Any + Send + Sync {
fn into_inner(self: Box<Self>) -> Box<dyn Any>;
}
impl dyn Extension {
#[inline]
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref::<T>()
}
}
impl<T: Send + Sync + 'static> Extension for T {
#[inline]
fn into_inner(self: Box<Self>) -> Box<dyn Any> {
self
}
}
struct TopoSccs<'a, N, E> {
graph: &'a DiGraph<N, E, usize>,
tarjan: TarjanScc<NodeIndex<usize>>,
sccs: Vec<FixedBitSet>,
}
impl<'a, N, E> TopoSccs<'a, N, E> {
fn new(graph: &'a DiGraph<N, E, usize>) -> Self {
let mut sccs = Vec::new();
let mut tarjan = TarjanScc::new();
tarjan.run(graph, |scc_nodes| {
sccs.push(scc_nodes.iter().map(|node| node.index()).collect());
});
sccs.reverse();
Self {
graph,
tarjan,
sccs,
}
}
#[inline]
fn scc_count(&self) -> usize {
self.sccs.len()
}
#[inline]
fn topo_index(&self, node: NodeIndex<usize>) -> usize {
self.sccs.len() - 1 - self.tarjan.node_component_index(self.graph, node)
}
#[cfg(test)]
fn iter(&self) -> std::slice::Iter<'_, FixedBitSet> {
self.sccs.iter()
}
fn condensation(&self) -> UnweightedList<usize> {
let mut dag = UnweightedList::with_capacity(self.scc_count());
for to in 0..self.scc_count() {
dag.add_node();
for index in self.sccs[to].ones().map(NodeIndex::new) {
for neighbor in self.graph.neighbors_directed(index, Direction::Incoming) {
let from = self.topo_index(neighbor);
if from != to {
dag.update_edge(from, to, ());
}
}
}
}
dag
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Traversal {
Visit,
Stop,
Skip,
Ignore,
}
pub struct Traverse<'a> {
graph: &'a CookedDiGraph<'a>,
stack: VecDeque<(EdgeKind, NodeIndex<usize>)>,
discovered: EnumMap<EdgeKind, FixedBitSet>,
direction: Direction,
}
impl<'a> Traverse<'a> {
pub fn at_root(
graph: &'a CookedDiGraph<'a>,
root: NodeIndex<usize>,
direction: Direction,
) -> Self {
let mut discovered = enum_map!(_ => graph.visit_map());
discovered[EdgeKind::Reference].grow_and_insert(root.index());
Self {
graph,
stack: VecDeque::from([(EdgeKind::Reference, root)]),
discovered,
direction,
}
}
pub fn at_roots(
graph: &'a CookedDiGraph<'a>,
roots: &FixedBitSet,
direction: Direction,
) -> Self {
let mut stack = VecDeque::new();
let mut discovered = enum_map!(_ => graph.visit_map());
stack.extend(
roots
.ones()
.map(|index| (EdgeKind::Reference, NodeIndex::new(index))),
);
discovered[EdgeKind::Reference].union_with(roots);
Self {
graph,
stack,
discovered,
direction,
}
}
pub fn from_neighbors(
graph: &'a CookedDiGraph<'a>,
root: NodeIndex<usize>,
direction: Direction,
) -> Self {
let mut stack = VecDeque::new();
let mut discovered = enum_map! {
_ => {
let mut map = graph.visit_map();
map.visit(root);
map
}
};
for (kind, neighbors) in neighbors(graph, root, direction) {
stack.extend(
neighbors
.difference(&discovered[kind])
.map(|index| (kind, NodeIndex::new(index))),
);
discovered[kind].union_with(&neighbors);
}
Self {
graph,
stack,
discovered,
direction,
}
}
pub fn run<F>(mut self, filter: F) -> impl Iterator<Item = NodeIndex<usize>> + use<'a, F>
where
F: Fn(EdgeKind, NodeIndex<usize>) -> Traversal,
{
std::iter::from_fn(move || {
while let Some((kind, index)) = self.stack.pop_front() {
let traversal = filter(kind, index);
if matches!(traversal, Traversal::Visit | Traversal::Skip) {
for (kind, neighbors) in neighbors(self.graph, index, self.direction) {
for neighbor in neighbors.difference(&self.discovered[kind]) {
self.stack.push_back((kind, NodeIndex::new(neighbor)));
}
self.discovered[kind].union_with(&neighbors);
}
}
if matches!(traversal, Traversal::Visit | Traversal::Stop) {
return Some(index);
}
}
None
})
}
}
fn neighbors(
graph: &CookedDiGraph<'_>,
node: NodeIndex<usize>,
direction: Direction,
) -> EnumMap<EdgeKind, FixedBitSet> {
let mut neighbors = enum_map!(_ => graph.visit_map());
for edge in graph.edges_directed(node, direction) {
let neighbor = match direction {
Direction::Outgoing => edge.target(),
Direction::Incoming => edge.source(),
};
neighbors[*edge.weight()].insert(neighbor.index());
}
neighbors
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::visit::NodeCount;
use crate::tests::assert_matches;
fn linear_graph() -> DiGraph<(), (), usize> {
let mut g = DiGraph::default();
let a = g.add_node(());
let b = g.add_node(());
let c = g.add_node(());
g.extend_with_edges([(a, b), (b, c)]);
g
}
fn cyclic_graph() -> DiGraph<(), (), usize> {
let mut g = DiGraph::default();
let a = g.add_node(());
let b = g.add_node(());
let c = g.add_node(());
let d = g.add_node(());
g.extend_with_edges([(a, b), (b, c), (c, a), (d, a)]);
g
}
#[test]
fn test_linear_graph_has_singleton_sccs() {
let g = linear_graph();
let sccs = TopoSccs::new(&g);
let sizes = sccs.iter().map(|scc| scc.count_ones(..)).collect_vec();
assert_matches!(&*sizes, [1, 1, 1]);
}
#[test]
fn test_cyclic_graph_has_one_multi_node_scc() {
let g = cyclic_graph();
let sccs = TopoSccs::new(&g);
let sizes = sccs.iter().map(|scc| scc.count_ones(..)).collect_vec();
assert_matches!(&*sizes, [1, 3]);
}
#[test]
fn test_sccs_are_in_topological_order() {
let g = cyclic_graph();
let sccs = TopoSccs::new(&g);
let d_topo = sccs.topo_index(3.into());
let a_topo = sccs.topo_index(0.into());
assert!(
d_topo < a_topo,
"D should precede A-B-C in topological order"
);
}
#[test]
fn test_topo_index_consistent_within_scc() {
let g = cyclic_graph();
let sccs = TopoSccs::new(&g);
let a_topo = sccs.topo_index(0.into());
let b_topo = sccs.topo_index(1.into());
let c_topo = sccs.topo_index(2.into());
assert_eq!(a_topo, b_topo);
assert_eq!(b_topo, c_topo);
}
#[test]
fn test_condensation_has_correct_node_count() {
let g = cyclic_graph();
let sccs = TopoSccs::new(&g);
let dag = sccs.condensation();
assert_eq!(dag.node_count(), 2);
}
#[test]
fn test_condensation_has_correct_edges() {
let g = cyclic_graph();
let sccs = TopoSccs::new(&g);
let dag = sccs.condensation();
let d_topo = sccs.topo_index(3.into());
let abc_topo = sccs.topo_index(0.into());
let d_neighbors = dag.neighbors(d_topo).collect_vec();
assert_eq!(&*d_neighbors, [abc_topo]);
let abc_neighbors = dag.neighbors(abc_topo).collect_vec();
assert!(abc_neighbors.is_empty());
}
#[test]
fn test_condensation_neighbors_in_topological_order() {
let mut g = DiGraph::<(), (), usize>::default();
let second = g.add_node(());
let top = g.add_node(());
let first = g.add_node(());
g.extend_with_edges([(top, second), (top, first), (first, second)]);
let sccs = TopoSccs::new(&g);
let dag = sccs.condensation();
let top_topo = sccs.topo_index(top);
assert_eq!(top_topo, 0);
let first_topo = sccs.topo_index(first);
assert_eq!(first_topo, 1);
let second_topo = sccs.topo_index(second);
assert_eq!(second_topo, 2);
let neighbors = dag.neighbors(top_topo).collect_vec();
assert_eq!(&*neighbors, [first_topo, second_topo]);
}
}