1use std::{
2 any::{Any, TypeId as StdTypeId},
3 collections::VecDeque,
4 fmt::Debug,
5};
6
7use atomic_refcell::AtomicRefCell;
8use fixedbitset::FixedBitSet;
9use itertools::Itertools;
10use petgraph::{
11 Direction,
12 adj::UnweightedList,
13 algo::{TarjanScc, tred},
14 data::Build,
15 graph::{DiGraph, EdgeIndex, NodeIndex},
16 stable_graph::StableDiGraph,
17 visit::{
18 DfsPostOrder, EdgeFiltered, EdgeRef, GraphRef, IntoEdgeReferences, IntoEdges,
19 IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeIndexable,
20 VisitMap, Visitable,
21 },
22};
23use rustc_hash::{FxBuildHasher, FxHashMap};
24
25use crate::{arena::Arena, ir::TypeView, parse::Info};
26
27use super::{
28 spec::{ResolvedSpecType, Spec},
29 types::{
30 FieldMeta, GraphContainer, GraphInlineType, GraphOperation, GraphSchemaType, GraphStruct,
31 GraphTagged, GraphType, InlineTypeId, InlineTypeIds, InlineTypePathRoot, OperationUsage,
32 PrimitiveType, SpecInlineType, SpecSchemaType, SpecType, SpecUntaggedVariant,
33 StructFieldName, TaggedVariantMeta, UntaggedVariantMeta, VariantMeta,
34 shape::{Operation, Parameter, ParameterInfo, Request, Response},
35 },
36 views::{TypeId, operation::OperationView, primitive::PrimitiveView, schema::SchemaTypeView},
37};
38
39type RawDiGraph<'a> = StableDiGraph<GraphType<'a>, GraphEdge<'a>, usize>;
41
42type CookedDiGraph<'a> = DiGraph<GraphType<'a>, GraphEdge<'a>, usize>;
44
45#[derive(Debug)]
56pub struct RawGraph<'a> {
57 arena: &'a Arena,
58 spec: &'a Spec<'a>,
59 graph: RawDiGraph<'a>,
60 schemas: FxHashMap<&'a str, NodeIndex<usize>>,
61 ops: &'a [&'a GraphOperation<'a>],
62 ids: InlineTypeIds<'a>,
63}
64
65impl<'a> RawGraph<'a> {
66 pub fn new(arena: &'a Arena, spec: &'a Spec<'a>) -> Self {
68 let tys = SpecTypeVisitor::new(
71 spec.schemas
72 .values()
73 .chain(spec.operations.iter().flat_map(|op| op.types().copied())),
74 );
75
76 let mut indices = FxHashMap::default();
78 let mut schemas = FxHashMap::default();
79 let mut graph = RawDiGraph::default();
80 for (parent, child) in tys {
81 use std::collections::hash_map::Entry;
82
83 let source = spec.resolve(child);
84 let &mut to = match indices.entry(source) {
85 Entry::Occupied(entry) => entry.into_mut(),
86 Entry::Vacant(entry) => {
87 let index = graph.add_node(match *entry.key() {
91 ResolvedSpecType::Schema(&ty) => GraphType::Schema(ty.into()),
92 ResolvedSpecType::Inline(&ty) => GraphType::Inline(ty.into()),
93 });
94 if let ResolvedSpecType::Schema(ty) = source {
95 schemas.entry(ty.name()).or_insert(index);
96 }
97 entry.insert(index)
98 }
99 };
100
101 if let Some((parent, edge)) = parent {
102 let destination = spec.resolve(parent);
103 let &mut from = match indices.entry(destination) {
104 Entry::Occupied(entry) => entry.into_mut(),
105 Entry::Vacant(entry) => {
106 let index = graph.add_node(match *entry.key() {
107 ResolvedSpecType::Schema(&ty) => GraphType::Schema(ty.into()),
108 ResolvedSpecType::Inline(&ty) => GraphType::Inline(ty.into()),
109 });
110 if let ResolvedSpecType::Schema(ty) = destination {
111 schemas.entry(ty.name()).or_insert(index);
112 }
113 entry.insert(index)
114 }
115 };
116 graph.add_edge(from, to, edge);
117 }
118 }
119
120 let ops = arena.alloc_slice_exact(spec.operations.iter().map(|op| {
122 let params = arena.alloc_slice_exact(op.params.iter().map(|param| match param {
123 Parameter::Path(info) => Parameter::Path(ParameterInfo {
124 name: info.name,
125 ty: match info.ty {
126 SpecType::Schema(s) => indices[&ResolvedSpecType::Schema(s)],
127 SpecType::Inline(i) => indices[&ResolvedSpecType::Inline(i)],
128 SpecType::Ref(r) => schemas[&*r.name()],
129 },
130 required: info.required,
131 description: info.description,
132 style: info.style,
133 }),
134 Parameter::Query(info) => Parameter::Query(ParameterInfo {
135 name: info.name,
136 ty: match info.ty {
137 SpecType::Schema(s) => indices[&ResolvedSpecType::Schema(s)],
138 SpecType::Inline(i) => indices[&ResolvedSpecType::Inline(i)],
139 SpecType::Ref(r) => schemas[&*r.name()],
140 },
141 required: info.required,
142 description: info.description,
143 style: info.style,
144 }),
145 }));
146
147 let request = op.request.as_ref().map(|r| match r {
148 Request::Json(ty) => Request::Json(match ty {
149 SpecType::Schema(s) => indices[&ResolvedSpecType::Schema(s)],
150 SpecType::Inline(i) => indices[&ResolvedSpecType::Inline(i)],
151 SpecType::Ref(r) => schemas[&*r.name()],
152 }),
153 Request::Multipart => Request::Multipart,
154 });
155
156 let response = op.response.as_ref().map(|r| match r {
157 Response::Json(ty) => Response::Json(match ty {
158 SpecType::Schema(s) => indices[&ResolvedSpecType::Schema(s)],
159 SpecType::Inline(i) => indices[&ResolvedSpecType::Inline(i)],
160 SpecType::Ref(r) => schemas[&*r.name()],
161 }),
162 });
163
164 &*arena.alloc(Operation {
165 id: op.id,
166 method: op.method,
167 path: op.path,
168 resource: op.resource,
169 description: op.description,
170 params,
171 request,
172 response,
173 })
174 }));
175
176 Self {
177 arena,
178 spec,
179 graph,
180 schemas,
181 ops,
182 ids: spec.ids,
183 }
184 }
185
186 pub fn inline_tagged_variants(&mut self) -> &mut Self {
206 let inlinables = self.inlinable_tagged_variants().collect_vec();
209
210 let mut retargets = FxHashMap::default();
211 retargets.reserve(inlinables.len());
212
213 for InlinableVariant { tagged, variant } in inlinables {
216 let id = self.ids.next();
219 let index = self
220 .graph
221 .add_node(GraphType::Inline(GraphInlineType::Struct(id, variant.ty)));
222
223 let original_field_edges = self.fields(variant.index).collect_vec();
234 for edge in original_field_edges.into_iter().rev() {
235 self.graph.add_edge(
236 index,
237 edge.target,
238 GraphEdge::Field {
239 meta: edge.meta,
240 shadow: true,
241 },
242 );
243 }
244
245 self.graph
250 .add_edge(index, tagged.index, GraphEdge::Inherits { shadow: true });
251 self.graph
252 .add_edge(index, variant.index, GraphEdge::Inherits { shadow: true });
253
254 retargets.insert((tagged.index, variant.index), index);
255 }
256
257 let taggeds: FixedBitSet = retargets
259 .keys()
260 .map(|&(tagged, _)| tagged.index())
261 .collect();
262 for index in taggeds.ones().map(NodeIndex::new) {
263 let old_edges = self
264 .graph
265 .edges_directed(index, Direction::Outgoing)
266 .filter(|e| matches!(e.weight(), GraphEdge::Variant(_)))
267 .map(|e| (e.id(), *e.weight(), e.target()))
268 .collect_vec();
269 for &(id, _, _) in &old_edges {
270 self.graph.remove_edge(id);
271 }
272 for (_, weight, target) in old_edges.into_iter().rev() {
275 let new_target = retargets.get(&(index, target)).copied().unwrap_or(target);
276 self.graph.add_edge(index, new_target, weight);
277 }
278 }
279
280 self
281 }
282
283 pub fn collapse_trivial_inlines(&mut self) -> &mut Self {
296 let inherits_to_any = self
300 .graph
301 .edge_references()
302 .filter(|edge| {
303 matches!(edge.weight(), GraphEdge::Inherits { .. })
304 && matches!(
305 self.graph[edge.target()],
306 GraphType::Schema(GraphSchemaType::Any(_))
307 | GraphType::Inline(GraphInlineType::Any(_))
308 )
309 })
310 .map(|edge| edge.id())
311 .collect_vec();
312 for id in inherits_to_any {
313 self.graph.remove_edge(id);
314 }
315
316 let collapsibles: FixedBitSet = self
319 .graph
320 .node_indices()
321 .filter(|&node| {
322 matches!(
323 self.graph[node],
324 GraphType::Inline(
325 GraphInlineType::Struct(..)
326 | GraphInlineType::Tagged(..)
327 | GraphInlineType::Untagged(..)
328 )
329 )
330 })
331 .filter(|&node| {
332 self.graph
333 .edges_directed(node, Direction::Outgoing)
334 .all(|edge| {
335 !matches!(
336 edge.weight(),
337 GraphEdge::Field { .. } | GraphEdge::Variant(_)
338 )
339 })
340 && self
341 .graph
342 .edges_directed(node, Direction::Outgoing)
343 .filter(|edge| {
344 matches!(edge.weight(), GraphEdge::Inherits { .. })
345 && edge.target() != node
346 })
347 .exactly_one()
348 .is_ok()
349 })
350 .map(|node| node.index())
351 .collect();
352
353 let closure = Closure::new(&EdgeFiltered::from_fn(&self.graph, |edge| {
360 matches!(edge.weight(), GraphEdge::Inherits { .. })
361 && collapsibles.contains(edge.source().index())
362 }));
363 let collapsed_to: FxHashMap<_, _> = collapsibles
364 .ones()
365 .map(NodeIndex::new)
366 .flat_map(|node| {
367 closure
368 .dependencies_of(node)
369 .find(|target| !collapsibles.contains(target.index()))
370 .map(|target| (node, target))
371 })
372 .collect();
373 if collapsed_to.is_empty() {
374 return self;
375 }
376
377 let sources_to_redirect: FixedBitSet = self
380 .graph
381 .edge_references()
382 .filter(|edge| {
383 collapsed_to.contains_key(&edge.target())
384 && !collapsed_to.contains_key(&edge.source())
385 })
386 .map(|edge| edge.source().index())
387 .collect();
388 for source in sources_to_redirect.ones().map(NodeIndex::new) {
389 let old_edges = self
390 .graph
391 .edges_directed(source, Direction::Outgoing)
392 .map(|edge| {
393 let old_target = edge.target();
394 let new_target = collapsed_to.get(&old_target).copied().unwrap_or(old_target);
395 (edge.id(), *edge.weight(), new_target)
396 })
397 .collect_vec();
398 for &(id, _, _) in &old_edges {
399 self.graph.remove_edge(id);
400 }
401 for (_, weight, target) in old_edges.into_iter().rev() {
405 self.graph.add_edge(source, target, weight);
406 }
407 }
408
409 let ops = self
411 .ops
412 .iter()
413 .map(|&op| {
414 let params = op
415 .params
416 .iter()
417 .map(|¶m| {
418 let rewrite = match param {
419 Parameter::Path(info) => collapsed_to
420 .get(&info.ty)
421 .map(|&ty| Parameter::Path(ParameterInfo { ty, ..info })),
422 Parameter::Query(info) => collapsed_to
423 .get(&info.ty)
424 .map(|&ty| Parameter::Query(ParameterInfo { ty, ..info })),
425 };
426 rewrite.unwrap_or(param)
427 })
428 .collect_vec();
429
430 let request = op
431 .request
432 .and_then(|request| match request {
433 Request::Json(ty) => {
434 let &ty = collapsed_to.get(&ty)?;
435 Some(Request::Json(ty))
436 }
437 Request::Multipart => None,
438 })
439 .or(op.request);
440
441 let response = op
442 .response
443 .and_then(|response| match response {
444 Response::Json(ty) => {
445 let &ty = collapsed_to.get(&ty)?;
446 Some(Response::Json(ty))
447 }
448 })
449 .or(op.response);
450
451 if params == op.params && request == op.request && response == op.response {
452 op
453 } else {
454 self.arena.alloc(Operation {
455 params: self.arena.alloc_slice_copy(¶ms),
456 request,
457 response,
458 ..*op
459 })
460 }
461 })
462 .collect_vec();
463 if ops != self.ops {
464 self.ops = self.arena.alloc_slice_copy(&ops);
465 }
466
467 for (node, _) in collapsed_to {
470 self.graph.remove_node(node);
471 }
472
473 self
474 }
475
476 #[inline]
478 pub fn cook(&self) -> CookedGraph<'a> {
479 CookedGraph::new(self)
480 }
481
482 fn fields(&self, node: NodeIndex<usize>) -> impl Iterator<Item = OutgoingEdge<FieldMeta<'a>>> {
485 self.graph
486 .edges_directed(node, Direction::Outgoing)
487 .filter_map(|e| match e.weight() {
488 &GraphEdge::Field { meta, .. } => {
489 let target = e.target();
490 Some(OutgoingEdge { meta, target })
491 }
492 _ => None,
493 })
494 }
495
496 fn inlinable_tagged_variants(&self) -> impl Iterator<Item = InlinableVariant<'a>> {
499 let used_by_ops: FixedBitSet = self
508 .ops
509 .iter()
510 .flat_map(|op| op.types())
511 .map(|index| index.index())
512 .collect();
513
514 self.graph
515 .node_indices()
516 .filter_map(|index| match self.graph[index] {
517 GraphType::Schema(GraphSchemaType::Tagged(_, ty)) => Some(Node { index, ty }),
518 _ => None,
519 })
520 .flat_map(move |tagged| {
521 self.graph
522 .edges_directed(tagged.index, Direction::Outgoing)
523 .filter(|e| matches!(e.weight(), GraphEdge::Variant(_)))
524 .filter_map(move |e| match self.graph[e.target()] {
525 GraphType::Schema(GraphSchemaType::Struct(_, ty)) => {
526 let index = e.target();
527 Some((tagged, Node { index, ty }))
528 }
529 _ => None,
530 })
531 })
532 .filter_map(move |(tagged, variant)| {
533 if used_by_ops[variant.index.index()] {
539 return Some((tagged, variant));
540 }
541
542 let first_tagged = self
546 .graph
547 .neighbors_directed(variant.index, Direction::Incoming)
548 .find_map(|index| match self.graph[index] {
549 GraphType::Schema(GraphSchemaType::Tagged(_, ty)) => {
550 Some(Node { index, ty })
551 }
552 _ => None,
553 })?;
554 let all_agree = self
555 .graph
556 .neighbors_directed(variant.index, Direction::Incoming)
557 .all(|index| match self.graph[index] {
558 GraphType::Schema(GraphSchemaType::Tagged(_, ty)) => {
559 ty.tag == first_tagged.ty.tag
560 && self.fields(index).eq(self.fields(first_tagged.index))
561 }
562 _ => false,
563 });
564 if all_agree {
565 return None;
566 }
567 Some((tagged, variant))
568 })
569 .filter_map(|(tagged, variant)| {
570 let ancestors = EdgeFiltered::from_fn(&self.graph, |e| {
576 matches!(e.weight(), GraphEdge::Inherits { .. })
577 });
578 let mut dfs = DfsPostOrder::new(&ancestors, variant.index);
579 let has_tag_field = std::iter::from_fn(|| dfs.next(&ancestors))
580 .filter(|&n| {
581 matches!(
582 self.graph[n],
583 GraphType::Schema(GraphSchemaType::Struct(..))
584 | GraphType::Inline(GraphInlineType::Struct(..))
585 )
586 })
587 .any(|n| {
588 self.fields(n).any(|f| {
589 matches!(f.meta.name, StructFieldName::Name(name)
590 if name == tagged.ty.tag)
591 })
592 });
593
594 if has_tag_field {
598 return Some(InlinableVariant { tagged, variant });
599 }
600
601 if dfs.discovered[tagged.index.index()] {
604 return None;
605 }
606
607 self.fields(tagged.index).next()?;
610
611 Some(InlinableVariant { tagged, variant })
612 })
613 }
614}
615
616#[derive(Debug)]
622pub struct CookedGraph<'a> {
623 arena: &'a Arena,
624 pub(super) graph: CookedDiGraph<'a>,
625 info: &'a Info,
626 schemas: FxHashMap<&'a str, NodeIndex<usize>>,
627 ops: &'a [&'a GraphOperation<'a>],
628 pub(super) metadata: CookedGraphMetadata<'a>,
630}
631
632impl<'a> CookedGraph<'a> {
633 fn new(raw: &RawGraph<'a>) -> Self {
634 let mut graph =
637 CookedDiGraph::with_capacity(raw.graph.node_count(), raw.graph.edge_count());
638 let mut indices =
639 FxHashMap::with_capacity_and_hasher(raw.graph.node_count(), FxBuildHasher);
640 for raw_index in raw.graph.node_indices() {
641 let cooked_index = graph.add_node(raw.graph[raw_index]);
642 indices.insert(raw_index, cooked_index);
643 }
644
645 for index in raw.graph.node_indices() {
654 let from = indices[&index];
655 let edges = raw
656 .graph
657 .edges(index)
658 .map(|e| (indices[&e.target()], *e.weight()));
659 for (to, kind) in edges {
660 graph.add_edge(from, to, kind);
661 }
662 }
663
664 let ops: &_ = raw.arena.alloc_slice_exact(raw.ops.iter().map(|&op| {
666 &*raw.arena.alloc(Operation {
667 id: op.id,
668 method: op.method,
669 path: op.path,
670 resource: op.resource,
671 description: op.description,
672 params: raw
673 .arena
674 .alloc_slice_exact(op.params.iter().map(|p| match p {
675 Parameter::Path(info) => Parameter::Path(ParameterInfo {
676 name: info.name,
677 ty: indices[&info.ty],
678 required: info.required,
679 description: info.description,
680 style: info.style,
681 }),
682 Parameter::Query(info) => Parameter::Query(ParameterInfo {
683 name: info.name,
684 ty: indices[&info.ty],
685 required: info.required,
686 description: info.description,
687 style: info.style,
688 }),
689 })),
690 request: op.request.as_ref().map(|r| match r {
691 Request::Json(ty) => Request::Json(indices[ty]),
692 Request::Multipart => Request::Multipart,
693 }),
694 response: op.response.as_ref().map(|r| match r {
695 Response::Json(ty) => Response::Json(indices[ty]),
696 }),
697 })
698 }));
699
700 let metadata = MetadataBuilder::new(raw.arena, &graph, ops).build();
701
702 Self {
703 arena: raw.arena,
704 graph,
705 info: raw.spec.info,
706 schemas: raw
707 .schemas
708 .iter()
709 .map(|(&name, index)| (name, indices[index]))
710 .collect(),
711 ops,
712 metadata,
713 }
714 }
715
716 #[inline]
718 pub fn arena(&self) -> &'a Arena {
719 self.arena
720 }
721
722 #[inline]
725 pub fn info(&self) -> &'a Info {
726 self.info
727 }
728
729 #[inline]
731 pub fn schemas(&self) -> impl Iterator<Item = SchemaTypeView<'_, 'a>> + use<'_, 'a> {
732 self.graph
733 .node_indices()
734 .filter_map(|index| match self.graph[index] {
735 GraphType::Schema(ty) => Some(SchemaTypeView::new(self, index, ty)),
736 _ => None,
737 })
738 }
739
740 #[inline]
742 pub fn schema(&self, name: &str) -> Option<SchemaTypeView<'_, 'a>> {
743 self.schemas
744 .get(name)
745 .and_then(|&index| match self.graph[index] {
746 GraphType::Schema(ty) => Some(SchemaTypeView::new(self, index, ty)),
747 _ => None,
748 })
749 }
750
751 #[inline]
753 pub fn view(&self, id: TypeId) -> TypeView<'_, 'a> {
754 TypeView::new(self, id.0)
755 }
756
757 #[inline]
759 pub fn primitives(&self) -> impl Iterator<Item = PrimitiveView<'_, 'a>> + use<'_, 'a> {
760 self.graph
761 .node_indices()
762 .filter_map(|index| match self.graph[index] {
763 GraphType::Schema(GraphSchemaType::Primitive(_, p))
764 | GraphType::Inline(GraphInlineType::Primitive(_, p)) => {
765 Some(PrimitiveView::new(self, index, p))
766 }
767 _ => None,
768 })
769 }
770
771 #[inline]
773 pub fn operations(&self) -> impl Iterator<Item = OperationView<'_, 'a>> + use<'_, 'a> {
774 self.ops.iter().map(|&op| OperationView::new(self, op))
775 }
776
777 #[inline]
778 pub(super) fn inherits(
779 &self,
780 node: NodeIndex<usize>,
781 ) -> impl Iterator<Item = OutgoingEdge<()>> {
782 self.graph
783 .edges_directed(node, Direction::Outgoing)
784 .filter(|e| matches!(e.weight(), GraphEdge::Inherits { .. }))
785 .map(|e| OutgoingEdge {
786 meta: (),
787 target: e.target(),
788 })
789 }
790
791 #[inline]
792 pub(super) fn fields(
793 &self,
794 node: NodeIndex<usize>,
795 ) -> impl Iterator<Item = OutgoingEdge<FieldMeta<'a>>> {
796 self.graph
797 .edges_directed(node, Direction::Outgoing)
798 .filter_map(|e| match e.weight() {
799 &GraphEdge::Field { meta, .. } => {
800 let target = e.target();
801 Some(OutgoingEdge { meta, target })
802 }
803 _ => None,
804 })
805 }
806
807 #[inline]
808 pub(super) fn variants(
809 &self,
810 node: NodeIndex<usize>,
811 ) -> impl Iterator<Item = OutgoingEdge<VariantMeta<'a>>> {
812 self.graph
813 .edges_directed(node, Direction::Outgoing)
814 .filter_map(|e| match e.weight() {
815 &GraphEdge::Variant(meta) => {
816 let target = e.target();
817 Some(OutgoingEdge { meta, target })
818 }
819 _ => None,
820 })
821 }
822}
823
824struct InlinableVariant<'a> {
826 tagged: Node<GraphTagged<'a>>,
828 variant: Node<GraphStruct<'a>>,
830}
831
832#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
836pub enum GraphEdge<'a> {
837 Inherits { shadow: bool },
839 Field { shadow: bool, meta: FieldMeta<'a> },
842 Variant(VariantMeta<'a>),
844 Contains,
847}
848
849impl GraphEdge<'_> {
850 #[inline]
858 pub fn shadow(&self) -> bool {
859 matches!(
860 self,
861 GraphEdge::Field { shadow: true, .. } | GraphEdge::Inherits { shadow: true }
862 )
863 }
864}
865
866#[derive(Clone, Copy, Debug, Eq, PartialEq)]
868pub struct OutgoingEdge<T> {
869 pub meta: T,
870 pub target: NodeIndex<usize>,
871}
872
873#[derive(Clone, Copy)]
874struct Node<Ty> {
875 index: NodeIndex<usize>,
876 ty: Ty,
877}
878
879pub(super) struct CookedGraphMetadata<'a> {
881 pub closure: Closure,
883 pub box_sccs: Vec<usize>,
886 pub hashable: FixedBitSet,
888 pub defaultable: FixedBitSet,
890 pub used_by: Vec<Vec<GraphOperation<'a>>>,
892 pub uses: FxHashMap<GraphOperation<'a>, FixedBitSet>,
894 pub paths: FxHashMap<InlineTypeId, InlineTypePath<'a>>,
896 pub extensions: Vec<AtomicRefCell<ExtensionMap>>,
898}
899
900impl Debug for CookedGraphMetadata<'_> {
901 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902 f.debug_struct("CookedGraphMetadata")
903 .field("closure", &self.closure)
904 .field("box_sccs", &self.box_sccs)
905 .field("hashable", &self.hashable)
906 .field("defaultable", &self.defaultable)
907 .field("used_by", &self.used_by)
908 .field("uses", &self.uses)
909 .finish_non_exhaustive()
910 }
911}
912
913struct HashDefault {
916 hashable: FixedBitSet,
917 defaultable: FixedBitSet,
918}
919
920struct Operations<'a> {
923 pub uses: FxHashMap<GraphOperation<'a>, FixedBitSet>,
925 pub used_by: Vec<Vec<GraphOperation<'a>>>,
927}
928
929#[derive(Clone, Copy, Debug)]
931pub(super) struct InlineTypePath<'a> {
932 pub root: InlineTypePathRoot<'a, NodeIndex<usize>, &'a str>,
933 pub edges: &'a [EdgeIndex<usize>],
934}
935
936struct MetadataBuilder<'graph, 'a> {
937 arena: &'a Arena,
938 graph: &'graph CookedDiGraph<'a>,
939 ops: &'graph [&'graph GraphOperation<'a>],
940 closure: Closure,
942}
943
944impl<'graph, 'a> MetadataBuilder<'graph, 'a> {
945 fn new(
946 arena: &'a Arena,
947 graph: &'graph CookedDiGraph<'a>,
948 ops: &'graph [&'graph GraphOperation<'a>],
949 ) -> Self {
950 Self {
951 arena,
952 graph,
953 ops,
954 closure: Closure::new(graph),
955 }
956 }
957
958 fn build(self) -> CookedGraphMetadata<'a> {
959 let operations = self.operations();
960 let HashDefault {
961 hashable,
962 defaultable,
963 } = self.hash_default();
964 let box_sccs = self.box_sccs();
965 let paths = self.paths();
966 CookedGraphMetadata {
967 closure: self.closure,
968 box_sccs,
969 hashable,
970 defaultable,
971 used_by: operations.used_by,
972 uses: operations.uses,
973 paths,
974 extensions: std::iter::repeat_with(AtomicRefCell::default)
977 .take(self.graph.node_count())
978 .collect(),
979 }
980 }
981
982 fn operations(&self) -> Operations<'a> {
983 let mut operations = Operations {
984 uses: FxHashMap::default(),
985 used_by: vec![vec![]; self.graph.node_count()],
986 };
987
988 for &&op in self.ops {
989 let mut dependencies = FixedBitSet::with_capacity(self.graph.node_count());
992 for &node in op.types() {
993 dependencies.extend(self.closure.dependencies_of(node).map(|n| n.index()));
994 }
995 operations.uses.entry(op).insert_entry(dependencies);
996 }
997
998 for (op, deps) in &operations.uses {
1000 for node in deps.ones() {
1001 operations.used_by[node].push(*op);
1002 }
1003 }
1004
1005 operations
1006 }
1007
1008 fn paths(&self) -> FxHashMap<InlineTypeId, InlineTypePath<'a>> {
1010 #[derive(Clone)]
1011 struct PartialPath<'a> {
1012 root: InlineTypePathRoot<'a, NodeIndex<usize>, &'a str>,
1013 edges: Vec<EdgeIndex<usize>>,
1014 }
1015
1016 let filtered = EdgeFiltered::from_fn(self.graph, |e| {
1017 !e.weight().shadow() && matches!(self.graph[e.target()], GraphType::Inline(_))
1018 });
1019
1020 let mut by_node = FxHashMap::default();
1021 let mut bfs = EdgeBfs::new(&filtered);
1022
1023 for index in self.graph.node_indices() {
1025 if matches!(self.graph[index], GraphType::Schema(_)) && bfs.discover(index) {
1026 by_node.insert(
1027 index,
1028 PartialPath {
1029 root: InlineTypePathRoot::Schema(index),
1030 edges: vec![],
1031 },
1032 );
1033 }
1034 while let Some(edge) = bfs.next() {
1035 let parent = &by_node[&edge.source()];
1036 let mut child = parent.clone();
1037 child.edges.push(edge.id());
1038 by_node.insert(edge.target(), child);
1039 }
1040 }
1041
1042 for op in self.ops {
1045 for param in op.params {
1046 let (usage, info) = match param {
1047 Parameter::Path(info) => (OperationUsage::Path(info.name), info),
1048 Parameter::Query(info) => (OperationUsage::Query(info.name), info),
1049 };
1050 if matches!(self.graph[info.ty], GraphType::Inline(_)) && bfs.discover(info.ty) {
1051 by_node.insert(
1052 info.ty,
1053 PartialPath {
1054 root: InlineTypePathRoot::Operation {
1055 id: op.id,
1056 resource: op.resource,
1057 usage,
1058 },
1059 edges: vec![],
1060 },
1061 );
1062 }
1063 }
1064 if let Some(Request::Json(index)) = op.request
1065 && matches!(self.graph[index], GraphType::Inline(_))
1066 && bfs.discover(index)
1067 {
1068 by_node.insert(
1069 index,
1070 PartialPath {
1071 root: InlineTypePathRoot::Operation {
1072 id: op.id,
1073 resource: op.resource,
1074 usage: OperationUsage::Request,
1075 },
1076 edges: vec![],
1077 },
1078 );
1079 }
1080 if let Some(Response::Json(index)) = op.response
1081 && matches!(self.graph[index], GraphType::Inline(_))
1082 && bfs.discover(index)
1083 {
1084 by_node.insert(
1085 index,
1086 PartialPath {
1087 root: InlineTypePathRoot::Operation {
1088 id: op.id,
1089 resource: op.resource,
1090 usage: OperationUsage::Response,
1091 },
1092 edges: vec![],
1093 },
1094 );
1095 }
1096 while let Some(edge) = bfs.next() {
1097 let parent = &by_node[&edge.source()];
1098 let mut child = parent.clone();
1099 child.edges.push(edge.id());
1100 by_node.insert(edge.target(), child);
1101 }
1102 }
1103
1104 by_node
1105 .into_iter()
1106 .filter_map(
1107 |(index, PartialPath { root, edges })| match self.graph[index] {
1108 GraphType::Inline(ty) => Some((
1109 ty.id(),
1110 InlineTypePath {
1111 root,
1112 edges: self.arena.alloc_slice(edges),
1113 },
1114 )),
1115 _ => None,
1116 },
1117 )
1118 .collect()
1119 }
1120
1121 fn box_sccs(&self) -> Vec<usize> {
1122 let box_edges = EdgeFiltered::from_fn(self.graph, |e| match e.weight() {
1123 GraphEdge::Inherits { .. } => false,
1126 GraphEdge::Contains => match self.graph[e.source()] {
1127 GraphType::Schema(GraphSchemaType::Container(_, c))
1128 | GraphType::Inline(GraphInlineType::Container(_, c)) => {
1129 !matches!(c, GraphContainer::Array { .. } | GraphContainer::Map { .. })
1132 }
1133 _ => true,
1134 },
1135 _ => true,
1136 });
1137 let mut scc = TarjanScc::new();
1138 scc.run(&box_edges, |_| ());
1139 self.graph
1140 .node_indices()
1141 .map(|node| scc.node_component_index(&box_edges, node))
1142 .collect()
1143 }
1144
1145 fn hash_default(&self) -> HashDefault {
1146 let n = self.graph.node_count();
1148 let mut unhashable = FixedBitSet::with_capacity(n);
1149 let mut undefaultable = FixedBitSet::with_capacity(n);
1150 for node in self.graph.node_indices() {
1151 use {GraphType::*, PrimitiveType::*};
1152 match &self.graph[node] {
1153 Schema(GraphSchemaType::Primitive(_, F32 | F64))
1154 | Inline(GraphInlineType::Primitive(_, F32 | F64)) => {
1155 unhashable.insert(node.index());
1156 }
1157 Schema(
1158 GraphSchemaType::Primitive(_, Url)
1159 | GraphSchemaType::Tagged(_, _)
1160 | GraphSchemaType::Untagged(_, _),
1161 )
1162 | Inline(
1163 GraphInlineType::Primitive(_, Url)
1164 | GraphInlineType::Tagged(_, _)
1165 | GraphInlineType::Untagged(_, _),
1166 ) => {
1167 undefaultable.insert(node.index());
1168 }
1169 _ => (),
1170 }
1171 }
1172
1173 let inherits = Closure::new(&EdgeFiltered::from_fn(self.graph, |e| {
1175 matches!(e.weight(), GraphEdge::Inherits { .. })
1176 }));
1177
1178 let mut queue: VecDeque<_> = unhashable.ones().map(NodeIndex::new).collect();
1184 while let Some(node) = queue.pop_front() {
1185 for edge in self.graph.edges_directed(node, Direction::Incoming) {
1186 let source = edge.source();
1187 match edge.weight() {
1188 GraphEdge::Contains | GraphEdge::Variant(_) => {
1189 if !unhashable.put(source.index()) {
1190 queue.push_back(source);
1191 }
1192 }
1193 GraphEdge::Field { .. } => {
1194 if !unhashable.put(source.index()) {
1195 queue.push_back(source);
1196 }
1197 for desc in inherits.dependents_of(source).filter(|&d| d != source) {
1201 if !unhashable.put(desc.index()) {
1202 queue.push_back(desc);
1203 }
1204 }
1205 }
1206 GraphEdge::Inherits { .. } => {}
1211 }
1212 }
1213 }
1214
1215 let mut queue: VecDeque<_> = undefaultable.ones().map(NodeIndex::new).collect();
1217 while let Some(node) = queue.pop_front() {
1218 for edge in self.graph.edges_directed(node, Direction::Incoming) {
1219 if !matches!(
1220 edge.weight(),
1221 GraphEdge::Field { meta, .. } if meta.required
1222 ) {
1223 continue;
1226 }
1227 let source = edge.source();
1228 if !undefaultable.put(source.index()) {
1229 queue.push_back(source);
1230 }
1231 for desc in inherits.dependents_of(source).filter(|&d| d != source) {
1235 if !undefaultable.put(desc.index()) {
1236 queue.push_back(desc);
1237 }
1238 }
1239 }
1240 }
1241
1242 HashDefault {
1243 hashable: invert(unhashable),
1244 defaultable: invert(undefaultable),
1245 }
1246 }
1247}
1248
1249fn invert(mut bits: FixedBitSet) -> FixedBitSet {
1251 bits.toggle_range(..);
1252 bits
1253}
1254
1255#[derive(Debug)]
1257struct SpecTypeVisitor<'a> {
1258 stack: Vec<(Option<(&'a SpecType<'a>, GraphEdge<'a>)>, &'a SpecType<'a>)>,
1259}
1260
1261impl<'a> SpecTypeVisitor<'a> {
1262 #[inline]
1264 fn new(roots: impl Iterator<Item = &'a SpecType<'a>>) -> Self {
1265 let mut stack = roots.map(|root| (None, root)).collect_vec();
1266 stack.reverse();
1267 Self { stack }
1268 }
1269}
1270
1271impl<'a> Iterator for SpecTypeVisitor<'a> {
1272 type Item = (Option<(&'a SpecType<'a>, GraphEdge<'a>)>, &'a SpecType<'a>);
1273
1274 fn next(&mut self) -> Option<Self::Item> {
1275 let (parent, top) = self.stack.pop()?;
1276 if matches!(
1277 parent,
1278 Some((
1279 _,
1280 GraphEdge::Variant(VariantMeta::Untagged(UntaggedVariantMeta::Null))
1281 ))
1282 ) {
1283 return Some((parent, top));
1286 }
1287 match top {
1288 SpecType::Schema(SpecSchemaType::Struct(_, ty))
1289 | SpecType::Inline(SpecInlineType::Struct(_, ty)) => {
1290 self.stack.extend(
1291 itertools::chain!(
1292 ty.fields.iter().map(|field| (
1293 GraphEdge::Field {
1294 shadow: false,
1295 meta: FieldMeta {
1296 name: field.name,
1297 required: field.required,
1298 description: field.description,
1299 flattened: field.flattened,
1300 },
1301 },
1302 field.ty
1303 )),
1304 ty.parents
1305 .iter()
1306 .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1307 )
1308 .map(|(edge, ty)| (Some((top, edge)), ty))
1309 .rev(),
1310 );
1311 }
1312 SpecType::Schema(SpecSchemaType::Untagged(_, ty))
1313 | SpecType::Inline(SpecInlineType::Untagged(_, ty)) => {
1314 self.stack.extend(
1315 itertools::chain!(
1316 ty.fields.iter().map(|field| (
1317 GraphEdge::Field {
1318 shadow: false,
1319 meta: FieldMeta {
1320 name: field.name,
1321 required: field.required,
1322 description: field.description,
1323 flattened: field.flattened,
1324 },
1325 },
1326 field.ty
1327 )),
1328 ty.variants.iter().map(|variant| match variant {
1329 &SpecUntaggedVariant::Some(hint, ty) => {
1330 let meta = UntaggedVariantMeta::Type { hint };
1331 (GraphEdge::Variant(meta.into()), ty)
1332 }
1333 SpecUntaggedVariant::Null => {
1336 (GraphEdge::Variant(UntaggedVariantMeta::Null.into()), top)
1337 }
1338 }),
1339 ty.parents
1340 .iter()
1341 .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1342 )
1343 .map(|(edge, ty)| (Some((top, edge)), ty))
1344 .rev(),
1345 );
1346 }
1347 SpecType::Schema(SpecSchemaType::Tagged(_, ty))
1348 | SpecType::Inline(SpecInlineType::Tagged(_, ty)) => {
1349 self.stack.extend(
1350 itertools::chain!(
1351 ty.fields.iter().map(|field| (
1352 GraphEdge::Field {
1353 shadow: false,
1354 meta: FieldMeta {
1355 name: field.name,
1356 required: field.required,
1357 description: field.description,
1358 flattened: field.flattened,
1359 },
1360 },
1361 field.ty
1362 )),
1363 ty.variants.iter().map(|variant| (
1364 GraphEdge::Variant(
1365 TaggedVariantMeta {
1366 name: variant.name,
1367 aliases: variant.aliases,
1368 }
1369 .into()
1370 ),
1371 variant.ty
1372 )),
1373 ty.parents
1374 .iter()
1375 .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1376 )
1377 .map(|(edge, ty)| (Some((top, edge)), ty))
1378 .rev(),
1379 );
1380 }
1381 SpecType::Schema(SpecSchemaType::Container(_, container))
1382 | SpecType::Inline(SpecInlineType::Container(_, container)) => {
1383 self.stack
1384 .push((Some((top, GraphEdge::Contains)), container.inner().ty));
1385 }
1386 SpecType::Schema(
1387 SpecSchemaType::Enum(..) | SpecSchemaType::Primitive(..) | SpecSchemaType::Any(_),
1388 )
1389 | SpecType::Inline(
1390 SpecInlineType::Enum(..) | SpecInlineType::Primitive(..) | SpecInlineType::Any(_),
1391 ) => (),
1392 SpecType::Ref(_) => (),
1393 }
1394 Some((parent, top))
1395 }
1396}
1397
1398pub(super) type ExtensionMap = FxHashMap<StdTypeId, Box<dyn Extension>>;
1400
1401pub trait Extension: Any + Send + Sync {
1402 fn into_inner(self: Box<Self>) -> Box<dyn Any>;
1403}
1404
1405impl dyn Extension {
1406 #[inline]
1407 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
1408 (self as &dyn Any).downcast_ref::<T>()
1409 }
1410}
1411
1412impl<T: Send + Sync + 'static> Extension for T {
1413 #[inline]
1414 fn into_inner(self: Box<Self>) -> Box<dyn Any> {
1415 self
1416 }
1417}
1418
1419struct TopoSccs<G> {
1426 graph: G,
1427 tarjan: TarjanScc<NodeIndex<usize>>,
1428 sccs: Vec<Vec<usize>>,
1429}
1430
1431impl<G> TopoSccs<G>
1432where
1433 G: Closable<NodeIndex<usize>> + Copy,
1434{
1435 fn new(graph: G) -> Self {
1436 let mut sccs = Vec::new();
1437 let mut tarjan = TarjanScc::new();
1438 tarjan.run(graph, |scc_nodes| {
1439 sccs.push(scc_nodes.iter().map(|node| node.index()).collect());
1440 });
1441 sccs.reverse();
1444 Self {
1445 graph,
1446 tarjan,
1447 sccs,
1448 }
1449 }
1450
1451 #[inline]
1452 fn scc_count(&self) -> usize {
1453 self.sccs.len()
1454 }
1455
1456 #[inline]
1458 fn topo_index(&self, node: NodeIndex<usize>) -> usize {
1459 self.sccs.len() - 1 - self.tarjan.node_component_index(self.graph, node)
1462 }
1463
1464 fn condensation(&self) -> UnweightedList<usize> {
1471 let mut dag = UnweightedList::with_capacity(self.scc_count());
1472 for to in 0..self.scc_count() {
1473 dag.add_node();
1474 for neighbor in self.sccs[to].iter().flat_map(|&index| {
1475 self.graph
1476 .neighbors_directed(NodeIndex::new(index), Direction::Incoming)
1477 }) {
1478 let from = self.topo_index(neighbor);
1479 if from != to {
1480 dag.update_edge(from, to, ());
1481 }
1482 }
1483 }
1484 dag
1485 }
1486}
1487
1488#[derive(Debug)]
1490pub(super) struct Closure {
1491 scc_indices: Vec<usize>,
1493 scc_members: Vec<Vec<usize>>,
1495 scc_deps: Vec<Vec<usize>>,
1498 scc_rdeps: Vec<Vec<usize>>,
1501}
1502
1503impl Closure {
1504 fn new<G>(graph: G) -> Self
1506 where
1507 G: Closable<NodeIndex<usize>> + Copy,
1508 {
1509 let sccs = TopoSccs::new(graph);
1510 let condensation = sccs.condensation();
1511 let (_, closure) = tred::dag_transitive_reduction_closure(&condensation);
1512
1513 let scc_deps = (0..sccs.scc_count())
1516 .map(|scc| closure.neighbors(scc).collect_vec())
1517 .collect_vec();
1518 let mut scc_rdeps = vec![vec![]; sccs.scc_count()];
1519 for (scc, deps) in scc_deps.iter().enumerate() {
1520 for &dep in deps {
1521 scc_rdeps[dep].push(scc);
1522 }
1523 }
1524
1525 let mut scc_indices = vec![0; graph.node_count()];
1526 for node in graph.node_identifiers() {
1527 scc_indices[node.index()] = sccs.topo_index(node);
1528 }
1529
1530 Closure {
1531 scc_indices,
1532 scc_members: sccs.sccs.iter().cloned().collect_vec(),
1533 scc_deps,
1534 scc_rdeps,
1535 }
1536 }
1537
1538 #[inline]
1540 pub fn scc_index_of(&self, node: NodeIndex<usize>) -> usize {
1541 self.scc_indices[node.index()]
1542 }
1543
1544 pub fn dependencies_of(
1547 &self,
1548 node: NodeIndex<usize>,
1549 ) -> impl Iterator<Item = NodeIndex<usize>> {
1550 let scc = self.scc_index_of(node);
1551 std::iter::once(scc)
1552 .chain(self.scc_deps[scc].iter().copied())
1553 .flat_map(|s| self.scc_members[s].iter().copied()) .map(NodeIndex::new)
1555 }
1556
1557 pub fn dependents_of(&self, node: NodeIndex<usize>) -> impl Iterator<Item = NodeIndex<usize>> {
1560 let scc = self.scc_index_of(node);
1561 std::iter::once(scc)
1562 .chain(self.scc_rdeps[scc].iter().copied())
1563 .flat_map(|s| self.scc_members[s].iter().copied())
1564 .map(NodeIndex::new)
1565 }
1566
1567 #[inline]
1570 pub fn depends_on(&self, node: NodeIndex<usize>, other: NodeIndex<usize>) -> bool {
1571 if node == other {
1572 return false;
1573 }
1574 let scc = self.scc_index_of(node);
1575 let other_scc = self.scc_index_of(other);
1576 scc == other_scc || self.scc_deps[scc].contains(&other_scc)
1577 }
1578}
1579
1580trait Closable<N>:
1582 NodeCount
1583 + IntoNodeIdentifiers<NodeId = N>
1584 + IntoNeighbors<NodeId = N>
1585 + IntoNeighborsDirected<NodeId = N>
1586 + NodeIndexable<NodeId = N>
1587{
1588}
1589
1590impl<N, G> Closable<N> for G where
1591 G: NodeCount
1592 + IntoNodeIdentifiers<NodeId = N>
1593 + IntoNeighbors<NodeId = N>
1594 + IntoNeighborsDirected<NodeId = N>
1595 + NodeIndexable<NodeId = N>
1596{
1597}
1598
1599struct EdgeBfs<G, N, VM> {
1607 graph: G,
1608 queue: VecDeque<N>,
1609 discovered: VM,
1610}
1611
1612impl<G, N, VM> EdgeBfs<G, N, VM>
1613where
1614 N: Copy,
1615 VM: VisitMap<N>,
1616{
1617 fn new(graph: G) -> Self
1619 where
1620 G: GraphRef + Visitable<NodeId = N, Map = VM>,
1621 {
1622 let discovered = graph.visit_map();
1623 Self {
1624 graph,
1625 queue: VecDeque::new(),
1626 discovered,
1627 }
1628 }
1629
1630 fn discover(&mut self, node: N) -> bool {
1633 if self.discovered.visit(node) {
1634 self.queue.push_back(node);
1635 true
1636 } else {
1637 false
1638 }
1639 }
1640
1641 fn next(&mut self) -> Option<G::EdgeRef>
1648 where
1649 G: IntoEdges<NodeId = N>,
1650 {
1651 loop {
1652 let &source = self.queue.front()?;
1653 for edge in self.graph.edges(source) {
1654 let target = edge.target();
1655 if self.discovered.visit(target) {
1656 self.queue.push_back(target);
1657 return Some(edge);
1658 }
1659 }
1660 self.queue.pop_front();
1661 }
1662 }
1663}
1664
1665#[cfg(test)]
1666mod tests {
1667 use super::*;
1668
1669 use crate::tests::assert_matches;
1670
1671 fn linear_graph() -> DiGraph<(), (), usize> {
1673 let mut g = DiGraph::default();
1674 let a = g.add_node(());
1675 let b = g.add_node(());
1676 let c = g.add_node(());
1677 g.extend_with_edges([(a, b), (b, c)]);
1678 g
1679 }
1680
1681 fn cyclic_graph() -> DiGraph<(), (), usize> {
1683 let mut g = DiGraph::default();
1684 let a = g.add_node(());
1685 let b = g.add_node(());
1686 let c = g.add_node(());
1687 let d = g.add_node(());
1688 g.extend_with_edges([(a, b), (b, c), (c, a), (d, a)]);
1689 g
1690 }
1691
1692 #[test]
1695 fn test_linear_graph_has_singleton_sccs() {
1696 let g = linear_graph();
1697 let sccs = TopoSccs::new(&g);
1698 let sizes = sccs.sccs.iter().map(|scc| scc.len()).collect_vec();
1699 assert_matches!(&*sizes, [1, 1, 1]);
1700 }
1701
1702 #[test]
1703 fn test_cyclic_graph_has_one_multi_node_scc() {
1704 let g = cyclic_graph();
1705 let sccs = TopoSccs::new(&g);
1706
1707 let sizes = sccs.sccs.iter().map(|scc| scc.len()).collect_vec();
1710 assert_matches!(&*sizes, [1, 3]);
1711 }
1712
1713 #[test]
1716 fn test_sccs_are_in_topological_order() {
1717 let g = cyclic_graph();
1718 let sccs = TopoSccs::new(&g);
1719
1720 let d_topo = sccs.topo_index(3.into());
1721 let a_topo = sccs.topo_index(0.into());
1722 assert!(
1723 d_topo < a_topo,
1724 "D should precede A-B-C in topological order"
1725 );
1726 }
1727
1728 #[test]
1729 fn test_topo_index_consistent_within_scc() {
1730 let g = cyclic_graph();
1731 let sccs = TopoSccs::new(&g);
1732
1733 let a_topo = sccs.topo_index(0.into());
1736 let b_topo = sccs.topo_index(1.into());
1737 let c_topo = sccs.topo_index(2.into());
1738
1739 assert_eq!(a_topo, b_topo);
1740 assert_eq!(b_topo, c_topo);
1741 }
1742
1743 #[test]
1746 fn test_condensation_has_correct_node_count() {
1747 let g = cyclic_graph();
1748 let sccs = TopoSccs::new(&g);
1749 let dag = sccs.condensation();
1750
1751 assert_eq!(dag.node_count(), 2);
1752 }
1753
1754 #[test]
1755 fn test_condensation_has_correct_edges() {
1756 let g = cyclic_graph();
1757 let sccs = TopoSccs::new(&g);
1758 let dag = sccs.condensation();
1759
1760 let d_topo = sccs.topo_index(3.into());
1763 let abc_topo = sccs.topo_index(0.into());
1764
1765 let d_neighbors = dag.neighbors(d_topo).collect_vec();
1766 assert_eq!(&*d_neighbors, [abc_topo]);
1767
1768 let abc_neighbors = dag.neighbors(abc_topo).collect_vec();
1769 assert!(abc_neighbors.is_empty());
1770 }
1771
1772 #[test]
1773 fn test_condensation_neighbors_in_topological_order() {
1774 let mut g = DiGraph::<(), (), usize>::default();
1778 let second = g.add_node(());
1779 let top = g.add_node(());
1780 let first = g.add_node(());
1781 g.extend_with_edges([(top, second), (top, first), (first, second)]);
1782
1783 let sccs = TopoSccs::new(&g);
1784 let dag = sccs.condensation();
1785
1786 let top_topo = sccs.topo_index(top);
1787 assert_eq!(top_topo, 0);
1788
1789 let first_topo = sccs.topo_index(first);
1790 assert_eq!(first_topo, 1);
1791
1792 let second_topo = sccs.topo_index(second);
1793 assert_eq!(second_topo, 2);
1794
1795 let neighbors = dag.neighbors(top_topo).collect_vec();
1796 assert_eq!(&*neighbors, [first_topo, second_topo]);
1797 }
1798}