Skip to main content

ploidy_core/ir/
graph.rs

1use std::{
2    any::{Any, TypeId as StdTypeId},
3    collections::VecDeque,
4    fmt::Debug,
5    num::NonZeroUsize,
6};
7
8use atomic_refcell::AtomicRefCell;
9use fixedbitset::FixedBitSet;
10use itertools::Itertools;
11use petgraph::{
12    Direction,
13    adj::UnweightedList,
14    algo::{TarjanScc, tred},
15    data::Build,
16    graph::{DiGraph, EdgeIndex, NodeIndex},
17    stable_graph::StableDiGraph,
18    visit::{
19        DfsPostOrder, EdgeFiltered, EdgeRef, GraphRef, IntoEdgeReferences, IntoEdges,
20        IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeIndexable,
21        VisitMap, Visitable,
22    },
23};
24use rustc_hash::{FxBuildHasher, FxHashMap};
25
26use crate::{arena::Arena, ir::TypeView, parse::Info};
27
28use super::{
29    spec::{ResolvedSpecType, Spec},
30    types::{
31        FieldMeta, GraphContainer, GraphInlineType, GraphOperation, GraphSchemaType, GraphStruct,
32        GraphTagged, GraphType, GraphUntagged, InlineTypeId, InlineTypeIds, InlineTypePathRoot,
33        OperationUsage, PrimitiveType, SpecInlineType, SpecSchemaType, SpecType, StructFieldName,
34        TaggedVariantMeta, UntaggedVariantMeta, VariantMeta,
35        shape::{Operation, Parameter, ParameterInfo, Request, Response},
36    },
37    views::{TypeId, operation::OperationView, primitive::PrimitiveView, schema::SchemaTypeView},
38};
39
40/// The mutable, sparse graph used for transformations.
41type RawDiGraph<'a> = StableDiGraph<GraphType<'a>, GraphEdge<'a>, usize>;
42
43/// The immutable, dense graph used for code generation.
44type CookedDiGraph<'a> = DiGraph<GraphType<'a>, GraphEdge<'a>, usize>;
45
46/// A mutable intermediate dependency graph of all the types in a [`Spec`],
47/// backed by a sparse [`StableDiGraph`].
48///
49/// This graph is constructed directly from a [`Spec`], and represents
50/// type relationships as they exist in the spec. Transformation methods
51/// rewrite this graph in place.
52///
53/// After applying all transformations, call [`cook`][Self::cook] to
54/// turn this graph into a [`CookedGraph`] that's ready for code generation.
55#[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    /// Builds a raw type graph from the given spec.
67    pub fn new(arena: &'a Arena, spec: &'a Spec<'a>) -> Self {
68        // All roots (named schemas, parameters, request and response bodies),
69        // and all the types within them (inline schemas and primitives).
70        let tys = SpecTypeVisitor::new(
71            spec.schemas
72                .values()
73                .chain(spec.operations.iter().flat_map(|op| op.types().copied())),
74        );
75
76        // Inflate a graph from the traversal.
77        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                    // We might see the same schema multiple times if it's
88                    // referenced multiple times in the spec. Only add
89                    // a new node for the schema if we haven't seen it before.
90                    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        // Map type references in operations to graph indices.
121        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    /// Inlines struct variants that are variants of multiple tagged unions
187    /// with different tags, or that inherit their tagged union's own fields.
188    ///
189    /// In OpenAPI's model of tagged unions, the tag always references a field
190    /// that's defined on each variant struct. This model works well for Python
191    /// and TypeScript, but not Rust; Serde doesn't allow variant structs to
192    /// declare fields with the same name as the tag. The Rust generator
193    /// excludes tag fields when generating structs, but this introduces a
194    /// new problem: a struct can't appear as a variant of multiple unions
195    /// with different tags [^1].
196    ///
197    /// This transformation finds and inlines these structs, so that
198    /// the Rust generator can safely omit their tag fields.
199    ///
200    /// [^1]: If struct A has fields `foo` and `bar`, A is a variant of
201    /// tagged unions C and D, C's tag is `foo`, and D's tag is `bar`...
202    /// only `foo` should be excluded when A is used in C, and only `bar`
203    /// should be excluded when A is used in D; but this can't be modeled
204    /// in Serde without splitting A into two distinct types.
205    pub fn inline_tagged_variants(&mut self) -> &mut Self {
206        // Collect all inlining decisions before mutating the graph,
207        // so that we can check inlinability per variant.
208        let inlinables = self.inlinable_tagged_variants().collect_vec();
209
210        let mut retargets = FxHashMap::default();
211        retargets.reserve(inlinables.len());
212
213        // Add nodes for the inlined variant structs,
214        // and their outgoing edges.
215        for InlinableVariant { tagged, variant } in inlinables {
216            // Duplicate the variant struct as an inline type,
217            // with its original metadata.
218            let id = self.ids.next();
219            let index = self
220                .graph
221                .add_node(GraphType::Inline(GraphInlineType::Struct(id, variant.ty)));
222
223            // Create shadow edges to the original variant struct's fields.
224            // These serve two purposes:
225            //
226            // 1. If a field is recursive, the duplicate joins the field's SCC,
227            //    not the original's SCC, so field edges to the original type
228            //    won't be treated as cyclic.
229            // 2. Hiding the originals' inlines from the duplicate's inlines.
230            //
231            // `fields()` yields edges in reverse order of addition;
232            // we collect and reverse to add them in their original order.
233            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            // Inherit from the tagged union (to pick up its own fields)
246            // and the original variant struct (to pick up its ancestors).
247            // The union is added first so that its fields appear first _and_
248            // can be overridden by the variant's fields.
249            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        // Retarget every tagged union's variant edges to the new structs.
258        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            // Re-add edges. `edges_directed` yields edges in reverse order
273            // of addition; reversing them adds edges in their original order.
274            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    /// Inlines struct variants of untagged unions where the union declares
284    /// own `properties`, but the struct doesn't inherit them via `allOf`.
285    ///
286    /// [`inline_tagged_variants`]: Self::inline_tagged_variants
287    pub fn inline_untagged_variants(&mut self) -> &mut Self {
288        let inlinables = self.inlinable_untagged_variants().collect_vec();
289
290        let mut retargets = FxHashMap::default();
291        retargets.reserve(inlinables.len());
292
293        for InlinableUntaggedVariant { untagged, variant } in inlinables {
294            match variant {
295                VariantStruct::Schema(variant) => {
296                    let id = self.ids.next();
297                    let index = self
298                        .graph
299                        .add_node(GraphType::Inline(GraphInlineType::Struct(id, variant.ty)));
300
301                    let original_field_edges = self.fields(variant.index).collect_vec();
302                    for edge in original_field_edges.into_iter().rev() {
303                        self.graph.add_edge(
304                            index,
305                            edge.target,
306                            GraphEdge::Field {
307                                meta: edge.meta,
308                                shadow: true,
309                            },
310                        );
311                    }
312
313                    self.graph.add_edge(
314                        index,
315                        untagged.index,
316                        GraphEdge::Inherits { shadow: true },
317                    );
318                    self.graph
319                        .add_edge(index, variant.index, GraphEdge::Inherits { shadow: true });
320
321                    retargets.insert((untagged.index, variant.index), index);
322                }
323                VariantStruct::Inline(variant) => {
324                    self.graph.add_edge(
325                        variant,
326                        untagged.index,
327                        GraphEdge::Inherits { shadow: true },
328                    );
329                }
330            }
331        }
332
333        let untaggeds: FixedBitSet = retargets
334            .keys()
335            .map(|&(untagged, _)| untagged.index())
336            .collect();
337        for index in untaggeds.ones().map(NodeIndex::new) {
338            let old_edges = self
339                .graph
340                .edges_directed(index, Direction::Outgoing)
341                .filter(|e| matches!(e.weight(), GraphEdge::Variant(_)))
342                .map(|e| (e.id(), *e.weight(), e.target()))
343                .collect_vec();
344            for &(id, _, _) in &old_edges {
345                self.graph.remove_edge(id);
346            }
347            // Re-add edges. `edges_directed` yields edges in reverse order
348            // of addition; reversing them adds edges in their original order.
349            for (_, weight, target) in old_edges.into_iter().rev() {
350                let new_target = retargets.get(&(index, target)).copied().unwrap_or(target);
351                self.graph.add_edge(index, new_target, weight);
352            }
353        }
354
355        self
356    }
357
358    /// Simplifies trivial `allOf` compositions.
359    ///
360    /// Drops inheritance edges from `Any`, and collapses inline structs and
361    /// unions with a single parent and without own fields or variants,
362    /// to that parent. Trivial named schemas are preserved, so that user code
363    /// can refer to them by name.
364    ///
365    /// This transformation simplifies OpenAPI 3.1 `$ref` schemas with adjacent
366    /// keywords. A schema like `{ "$ref": "...", "description": "..." }` is
367    /// sugar for an inline `allOf` schema with two subschemas: a reference and
368    /// an inline with just a `description`. The latter doesn't contribute any
369    /// type constraints, so the inline `allOf` is redundant.
370    pub fn collapse_trivial_inlines(&mut self) -> &mut Self {
371        // `Any` doesn't contribute any members, and dropping `Inherits` edges
372        // to it here simplifies the logic to find collapsible inlines and
373        // their new targets.
374        let inherits_to_any = self
375            .graph
376            .edge_references()
377            .filter(|edge| {
378                matches!(edge.weight(), GraphEdge::Inherits { .. })
379                    && matches!(
380                        self.graph[edge.target()],
381                        GraphType::Schema(GraphSchemaType::Any(_))
382                            | GraphType::Inline(GraphInlineType::Any(_))
383                    )
384            })
385            .map(|edge| edge.id())
386            .collect_vec();
387        for id in inherits_to_any {
388            self.graph.remove_edge(id);
389        }
390
391        // Find all inlines with no own members (i.e., no fields or variants)
392        // and exactly one parent.
393        let collapsibles: FixedBitSet = self
394            .graph
395            .node_indices()
396            .filter(|&node| {
397                matches!(
398                    self.graph[node],
399                    GraphType::Inline(
400                        GraphInlineType::Struct(..)
401                            | GraphInlineType::Tagged(..)
402                            | GraphInlineType::Untagged(..)
403                    )
404                )
405            })
406            .filter(|&node| {
407                self.graph
408                    .edges_directed(node, Direction::Outgoing)
409                    .all(|edge| {
410                        !matches!(
411                            edge.weight(),
412                            GraphEdge::Field { .. } | GraphEdge::Variant(_)
413                        )
414                    })
415                    && self
416                        .graph
417                        .edges_directed(node, Direction::Outgoing)
418                        .filter(|edge| {
419                            matches!(edge.weight(), GraphEdge::Inherits { .. })
420                                && edge.target() != node
421                        })
422                        .exactly_one()
423                        .is_ok()
424            })
425            .map(|node| node.index())
426            .collect();
427
428        // Compute the transitive closure over the inheritance subgraph
429        // rooted at the collapsible set, then map each collapsible node to its
430        // non-collapsible target. Each collapsible has exactly one
431        // outgoing edge in this subgraph, so its dependency set is a linear
432        // chain ending in either the target or a cycle entirely within the
433        // collapsible set. Only chains with a target are recorded.
434        let closure = Closure::new(&EdgeFiltered::from_fn(&self.graph, |edge| {
435            matches!(edge.weight(), GraphEdge::Inherits { .. })
436                && collapsibles.contains(edge.source().index())
437        }));
438        let collapsed_to: FxHashMap<_, _> = collapsibles
439            .ones()
440            .map(NodeIndex::new)
441            .flat_map(|node| {
442                closure
443                    .dependencies_of(node)
444                    .find(|target| !collapsibles.contains(target.index()))
445                    .map(|target| (node, target))
446            })
447            .collect();
448        if collapsed_to.is_empty() {
449            return self;
450        }
451
452        // Redirect every incoming edge of a collapsible to its target.
453        // Skip edges whose source is also collapsible; we'll remove those last.
454        let sources_to_redirect: FixedBitSet = self
455            .graph
456            .edge_references()
457            .filter(|edge| {
458                collapsed_to.contains_key(&edge.target())
459                    && !collapsed_to.contains_key(&edge.source())
460            })
461            .map(|edge| edge.source().index())
462            .collect();
463        for source in sources_to_redirect.ones().map(NodeIndex::new) {
464            let old_edges = self
465                .graph
466                .edges_directed(source, Direction::Outgoing)
467                .map(|edge| {
468                    let old_target = edge.target();
469                    let new_target = collapsed_to.get(&old_target).copied().unwrap_or(old_target);
470                    (edge.id(), *edge.weight(), new_target)
471                })
472                .collect_vec();
473            for &(id, _, _) in &old_edges {
474                self.graph.remove_edge(id);
475            }
476            // Re-add edges. `edges_directed` yields edges in reverse order
477            // of addition; reversing them adds edges in their original order.
478            // Since edge order matters, we need to remove and re-add them all.
479            for (_, weight, target) in old_edges.into_iter().rev() {
480                self.graph.add_edge(source, target, weight);
481            }
482        }
483
484        // Redirect operation roots, which don't have graph edges.
485        let ops = self
486            .ops
487            .iter()
488            .map(|&op| {
489                let params = op
490                    .params
491                    .iter()
492                    .map(|&param| {
493                        let rewrite = match param {
494                            Parameter::Path(info) => collapsed_to
495                                .get(&info.ty)
496                                .map(|&ty| Parameter::Path(ParameterInfo { ty, ..info })),
497                            Parameter::Query(info) => collapsed_to
498                                .get(&info.ty)
499                                .map(|&ty| Parameter::Query(ParameterInfo { ty, ..info })),
500                        };
501                        rewrite.unwrap_or(param)
502                    })
503                    .collect_vec();
504
505                let request = op
506                    .request
507                    .and_then(|request| match request {
508                        Request::Json(ty) => {
509                            let &ty = collapsed_to.get(&ty)?;
510                            Some(Request::Json(ty))
511                        }
512                        Request::Multipart => None,
513                    })
514                    .or(op.request);
515
516                let response = op
517                    .response
518                    .and_then(|response| match response {
519                        Response::Json(ty) => {
520                            let &ty = collapsed_to.get(&ty)?;
521                            Some(Response::Json(ty))
522                        }
523                    })
524                    .or(op.response);
525
526                if params == op.params && request == op.request && response == op.response {
527                    op
528                } else {
529                    self.arena.alloc(Operation {
530                        params: self.arena.alloc_slice_copy(&params),
531                        request,
532                        response,
533                        ..*op
534                    })
535                }
536            })
537            .collect_vec();
538        if ops != self.ops {
539            self.ops = self.arena.alloc_slice_copy(&ops);
540        }
541
542        // Remove every collapsed node. `remove_node` removes incident edges,
543        // including those between two collapsibles in a chain.
544        for (node, _) in collapsed_to {
545            self.graph.remove_node(node);
546        }
547
548        self
549    }
550
551    /// Builds an immutable [`CookedGraph`] from this mutable raw graph.
552    #[inline]
553    pub fn cook(&self) -> CookedGraph<'a> {
554        CookedGraph::new(self)
555    }
556
557    /// Returns an iterator over all the fields of a struct or union type,
558    /// in reverse insertion order.
559    fn fields(&self, node: NodeIndex<usize>) -> impl Iterator<Item = OutgoingEdge<FieldMeta<'a>>> {
560        self.graph
561            .edges_directed(node, Direction::Outgoing)
562            .filter_map(|e| match e.weight() {
563                &GraphEdge::Field { meta, .. } => {
564                    let target = e.target();
565                    Some(OutgoingEdge { meta, target })
566                }
567                _ => None,
568            })
569    }
570
571    /// Returns an iterator over all the tagged union variant structs
572    /// that should be inlined.
573    fn inlinable_tagged_variants(&self) -> impl Iterator<Item = InlinableVariant<'a>> {
574        // Compute the set of types used by all operations.
575        // Operations don't participate in the graph, but
576        // still need to be considered when deciding
577        // whether to inline a variant struct.
578        //
579        // Otherwise, a struct that's used by same-tag unions
580        // _and_ an operation wouldn't be inlined, incorrectly
581        // removing its tag field.
582        let used_by_ops: FixedBitSet = self
583            .ops
584            .iter()
585            .flat_map(|op| op.types())
586            .map(|index| index.index())
587            .collect();
588
589        self.inlinable_variants()
590            .filter_map(|variant| match variant {
591                InlinableUnionVariant::Tagged(tagged, VariantStruct::Schema(variant)) => {
592                    Some((tagged, variant))
593                }
594                _ => None,
595            })
596            .filter_map(move |(tagged, variant)| {
597                // A variant struct only needs inlining if it has multiple
598                // distinct uses. Skip if (1) no operation uses the struct,
599                // _and_ (2) every incoming edge is from a tagged union with
600                // the same tag and fields. If both hold, all uses agree, so
601                // the struct can be used directly without inlining.
602                if used_by_ops[variant.index.index()] {
603                    return Some((tagged, variant));
604                }
605
606                // Check that all the variant's inbound edges are from
607                // tagged unions, and that all their tags and field
608                // edges match the first union we found.
609                let first_tagged = self
610                    .graph
611                    .neighbors_directed(variant.index, Direction::Incoming)
612                    .find_map(|index| match self.graph[index] {
613                        GraphType::Schema(GraphSchemaType::Tagged(_, ty)) => {
614                            Some(Node { index, ty })
615                        }
616                        _ => None,
617                    })?;
618                let all_agree = self
619                    .graph
620                    .neighbors_directed(variant.index, Direction::Incoming)
621                    .all(|index| match self.graph[index] {
622                        GraphType::Schema(GraphSchemaType::Tagged(_, ty)) => {
623                            ty.tag == first_tagged.ty.tag
624                                && self.fields(index).eq(self.fields(first_tagged.index))
625                        }
626                        _ => false,
627                    });
628                if all_agree {
629                    return None;
630                }
631                Some((tagged, variant))
632            })
633            .filter_map(|(tagged, variant)| {
634                // Skip inlining when the inline copy would be identical
635                // to the original. This happens when the variant
636                // doesn't declare the tag as a field _and_ either
637                // (a) the union has no own fields, or (b) the variant
638                // inherits from the union.
639                let ancestors = EdgeFiltered::from_fn(&self.graph, |e| {
640                    matches!(e.weight(), GraphEdge::Inherits { .. })
641                });
642                let mut dfs = DfsPostOrder::new(&ancestors, variant.index);
643                let has_tag_field = std::iter::from_fn(|| dfs.next(&ancestors))
644                    .filter(|&n| {
645                        matches!(
646                            self.graph[n],
647                            GraphType::Schema(GraphSchemaType::Struct(..))
648                                | GraphType::Inline(GraphInlineType::Struct(..))
649                        )
650                    })
651                    .any(|n| {
652                        self.fields(n).any(|f| {
653                            matches!(f.meta.name, StructFieldName::Name(name)
654                                if name == tagged.ty.tag)
655                        })
656                    });
657
658                // If the variant declares or inherits the tag field,
659                // we must inline, so that the inline copy can safely
660                // omit the tag.
661                if has_tag_field {
662                    return Some(InlinableVariant { tagged, variant });
663                }
664
665                // If the DFS visited the union, the variant already inherits
666                // its fields; the inline copy would be identical.
667                if dfs.discovered[tagged.index.index()] {
668                    return None;
669                }
670
671                // If the variant doesn't inherit from the union, but the union
672                // has no fields of its own, the inline copy would be identical.
673                self.fields(tagged.index).next()?;
674
675                Some(InlinableVariant { tagged, variant })
676            })
677    }
678
679    /// Returns an iterator over all the untagged union variant structs
680    /// that should be inlined.
681    fn inlinable_untagged_variants(&self) -> impl Iterator<Item = InlinableUntaggedVariant<'a>> {
682        self.inlinable_variants()
683            .filter_map(|variant| {
684                let InlinableUnionVariant::Untagged(untagged, meta, variant) = variant else {
685                    return None;
686                };
687                self.fields(untagged.index).next()?;
688                if meta.null {
689                    return None;
690                }
691                Some((untagged, variant))
692            })
693            .filter_map(|(untagged, variant)| {
694                let ancestors = EdgeFiltered::from_fn(&self.graph, |e| {
695                    matches!(e.weight(), GraphEdge::Inherits { .. })
696                });
697                let mut dfs = DfsPostOrder::new(&ancestors, variant.index());
698                while dfs.next(&ancestors).is_some() {}
699                if dfs.discovered[untagged.index.index()] {
700                    return None;
701                }
702
703                Some(InlinableUntaggedVariant { untagged, variant })
704            })
705    }
706
707    /// Returns an iterator over the struct variants of
708    /// tagged and untagged unions.
709    fn inlinable_variants(&self) -> impl Iterator<Item = InlinableUnionVariant<'a>> {
710        self.graph
711            .node_indices()
712            .filter(|&index| {
713                matches!(
714                    self.graph[index],
715                    GraphType::Schema(GraphSchemaType::Tagged(..) | GraphSchemaType::Untagged(..))
716                )
717            })
718            .flat_map(move |index| {
719                self.graph
720                    .edges_directed(index, Direction::Outgoing)
721                    .filter_map(move |edge| {
722                        let &GraphEdge::Variant(meta) = edge.weight() else {
723                            return None;
724                        };
725                        let variant = {
726                            let index = edge.target();
727                            match self.graph[index] {
728                                GraphType::Schema(GraphSchemaType::Struct(_, ty)) => {
729                                    VariantStruct::Schema(Node { index, ty })
730                                }
731                                GraphType::Inline(GraphInlineType::Struct(..)) => {
732                                    VariantStruct::Inline(index)
733                                }
734                                _ => return None,
735                            }
736                        };
737                        Some(match (self.graph[index], meta) {
738                            (
739                                GraphType::Schema(GraphSchemaType::Tagged(_, ty)),
740                                VariantMeta::Tagged(_),
741                            ) => InlinableUnionVariant::Tagged(Node { index, ty }, variant),
742                            (
743                                GraphType::Schema(GraphSchemaType::Untagged(_, ty)),
744                                VariantMeta::Untagged(meta),
745                            ) => InlinableUnionVariant::Untagged(Node { index, ty }, meta, variant),
746                            _ => unreachable!(),
747                        })
748                    })
749            })
750    }
751}
752
753/// The final dependency graph of all the types in a [`Spec`],
754/// backed by a dense [`DiGraph`].
755///
756/// This graph has all transformations applied, and is ready for
757/// code generation.
758#[derive(Debug)]
759pub struct CookedGraph<'a> {
760    arena: &'a Arena,
761    pub(super) graph: CookedDiGraph<'a>,
762    info: &'a Info,
763    schemas: FxHashMap<&'a str, NodeIndex<usize>>,
764    ops: &'a [&'a GraphOperation<'a>],
765    /// Additional metadata for each node.
766    pub(super) metadata: CookedGraphMetadata<'a>,
767}
768
769impl<'a> CookedGraph<'a> {
770    fn new(raw: &RawGraph<'a>) -> Self {
771        // Build a dense graph, mapping sparse raw node indices to
772        // dense cooked node indices.
773        let mut graph =
774            CookedDiGraph::with_capacity(raw.graph.node_count(), raw.graph.edge_count());
775        let mut indices =
776            FxHashMap::with_capacity_and_hasher(raw.graph.node_count(), FxBuildHasher);
777        for raw_index in raw.graph.node_indices() {
778            let cooked_index = graph.add_node(raw.graph[raw_index]);
779            indices.insert(raw_index, cooked_index);
780        }
781
782        // Copy edges.
783        //
784        // `raw.graph.edges()` yields edges in reverse order of addition.
785        // The raw graph adds edges in declaration order, so `edges()`
786        // yields them reversed. Re-adding them to the cooked graph in that
787        // reversed order means they're now stored in reverse-declaration order,
788        // letting the cooked graph's accessors yield edges in declaration order
789        // without any extra work.
790        for index in raw.graph.node_indices() {
791            let from = indices[&index];
792            let edges = raw
793                .graph
794                .edges(index)
795                .map(|e| (indices[&e.target()], *e.weight()));
796            for (to, kind) in edges {
797                graph.add_edge(from, to, kind);
798            }
799        }
800
801        // Remap schema type references in operations.
802        let ops: &_ = raw.arena.alloc_slice_exact(raw.ops.iter().map(|&op| {
803            &*raw.arena.alloc(Operation {
804                id: op.id,
805                method: op.method,
806                path: op.path,
807                resource: op.resource,
808                description: op.description,
809                params: raw
810                    .arena
811                    .alloc_slice_exact(op.params.iter().map(|p| match p {
812                        Parameter::Path(info) => Parameter::Path(ParameterInfo {
813                            name: info.name,
814                            ty: indices[&info.ty],
815                            required: info.required,
816                            description: info.description,
817                            style: info.style,
818                        }),
819                        Parameter::Query(info) => Parameter::Query(ParameterInfo {
820                            name: info.name,
821                            ty: indices[&info.ty],
822                            required: info.required,
823                            description: info.description,
824                            style: info.style,
825                        }),
826                    })),
827                request: op.request.as_ref().map(|r| match r {
828                    Request::Json(ty) => Request::Json(indices[ty]),
829                    Request::Multipart => Request::Multipart,
830                }),
831                response: op.response.as_ref().map(|r| match r {
832                    Response::Json(ty) => Response::Json(indices[ty]),
833                }),
834            })
835        }));
836
837        let metadata = MetadataBuilder::new(raw.arena, &graph, ops).build();
838
839        Self {
840            arena: raw.arena,
841            graph,
842            info: raw.spec.info,
843            schemas: raw
844                .schemas
845                .iter()
846                .map(|(&name, index)| (name, indices[index]))
847                .collect(),
848            ops,
849            metadata,
850        }
851    }
852
853    /// Returns this graph's arena.
854    #[inline]
855    pub fn arena(&self) -> &'a Arena {
856        self.arena
857    }
858
859    /// Returns [`Info`] from the [`Document`][crate::parse::Document]
860    /// used to build this graph.
861    #[inline]
862    pub fn info(&self) -> &'a Info {
863        self.info
864    }
865
866    /// Returns an iterator over all the named schemas in this graph.
867    #[inline]
868    pub fn schemas(&self) -> impl Iterator<Item = SchemaTypeView<'_, 'a>> + use<'_, 'a> {
869        self.graph
870            .node_indices()
871            .filter_map(|index| match self.graph[index] {
872                GraphType::Schema(ty) => Some(SchemaTypeView::new(self, index, ty)),
873                _ => None,
874            })
875    }
876
877    /// Looks up and returns a schema by name.
878    #[inline]
879    pub fn schema(&self, name: &str) -> Option<SchemaTypeView<'_, 'a>> {
880        self.schemas
881            .get(name)
882            .and_then(|&index| match self.graph[index] {
883                GraphType::Schema(ty) => Some(SchemaTypeView::new(self, index, ty)),
884                _ => None,
885            })
886    }
887
888    /// Looks up and returns a type view by its identifier.
889    #[inline]
890    pub fn view(&self, id: TypeId) -> TypeView<'_, 'a> {
891        TypeView::new(self, id.0)
892    }
893
894    /// Returns an iterator over all primitive type nodes in this graph.
895    #[inline]
896    pub fn primitives(&self) -> impl Iterator<Item = PrimitiveView<'_, 'a>> + use<'_, 'a> {
897        self.graph
898            .node_indices()
899            .filter_map(|index| match self.graph[index] {
900                GraphType::Schema(GraphSchemaType::Primitive(_, p))
901                | GraphType::Inline(GraphInlineType::Primitive(_, p)) => {
902                    Some(PrimitiveView::new(self, index, p))
903                }
904                _ => None,
905            })
906    }
907
908    /// Returns an iterator over all the operations in this graph.
909    #[inline]
910    pub fn operations(&self) -> impl Iterator<Item = OperationView<'_, 'a>> + use<'_, 'a> {
911        self.ops.iter().map(|&op| OperationView::new(self, op))
912    }
913
914    #[inline]
915    pub(super) fn inherits(
916        &self,
917        node: NodeIndex<usize>,
918    ) -> impl Iterator<Item = OutgoingEdge<()>> {
919        self.graph
920            .edges_directed(node, Direction::Outgoing)
921            .filter(|e| matches!(e.weight(), GraphEdge::Inherits { .. }))
922            .map(|e| OutgoingEdge {
923                meta: (),
924                target: e.target(),
925            })
926    }
927
928    #[inline]
929    pub(super) fn fields(
930        &self,
931        node: NodeIndex<usize>,
932    ) -> impl Iterator<Item = OutgoingEdge<FieldMeta<'a>>> {
933        self.graph
934            .edges_directed(node, Direction::Outgoing)
935            .filter_map(|e| match e.weight() {
936                &GraphEdge::Field { meta, .. } => {
937                    let target = e.target();
938                    Some(OutgoingEdge { meta, target })
939                }
940                _ => None,
941            })
942    }
943
944    #[inline]
945    pub(super) fn variants(
946        &self,
947        node: NodeIndex<usize>,
948    ) -> impl Iterator<Item = OutgoingEdge<VariantMeta<'a>>> {
949        self.graph
950            .edges_directed(node, Direction::Outgoing)
951            .filter_map(|e| match e.weight() {
952                &GraphEdge::Variant(meta) => {
953                    let target = e.target();
954                    Some(OutgoingEdge { meta, target })
955                }
956                _ => None,
957            })
958    }
959}
960
961/// A variant that should be inlined into its tagged union.
962struct InlinableVariant<'a> {
963    /// The tagged union that owns this variant.
964    tagged: Node<GraphTagged<'a>>,
965    /// The original variant struct node.
966    variant: Node<GraphStruct<'a>>,
967}
968
969/// A variant that should be inlined into its untagged union.
970struct InlinableUntaggedVariant<'a> {
971    /// The untagged union that owns this variant.
972    untagged: Node<GraphUntagged<'a>>,
973    /// The variant struct node.
974    variant: VariantStruct<'a>,
975}
976
977/// A named or inline struct used as a union variant.
978#[derive(Clone, Copy)]
979enum VariantStruct<'a> {
980    Schema(Node<GraphStruct<'a>>),
981    Inline(NodeIndex<usize>),
982}
983
984impl VariantStruct<'_> {
985    #[inline]
986    fn index(&self) -> NodeIndex<usize> {
987        match self {
988            Self::Schema(node) => node.index,
989            &Self::Inline(index) => index,
990        }
991    }
992}
993
994/// A struct variant of a tagged or untagged union.
995enum InlinableUnionVariant<'a> {
996    Tagged(Node<GraphTagged<'a>>, VariantStruct<'a>),
997    Untagged(
998        Node<GraphUntagged<'a>>,
999        UntaggedVariantMeta,
1000        VariantStruct<'a>,
1001    ),
1002}
1003
1004/// An edge between two types in the type graph.
1005///
1006/// Edges describe the relationship between their source and target types.
1007#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1008pub enum GraphEdge<'a> {
1009    /// The source type inherits from the target type.
1010    Inherits { shadow: bool },
1011    /// The source struct, tagged union, or untagged union
1012    /// has the target type as a field.
1013    Field { shadow: bool, meta: FieldMeta<'a> },
1014    /// The source union has the target type as a variant.
1015    Variant(VariantMeta<'a>),
1016    /// The source type is an array, map, or optional that contains
1017    /// the target type.
1018    Contains,
1019}
1020
1021impl GraphEdge<'_> {
1022    /// Returns `true` if the target type should be excluded from
1023    /// the source type's [inlines], but still considered a dependency.
1024    ///
1025    /// Shadow edges prevent inlined variant structs from claiming
1026    /// their originals' inlines.
1027    ///
1028    /// [inlines]: crate::ir::views::View::inlines
1029    #[inline]
1030    pub fn shadow(&self) -> bool {
1031        matches!(
1032            self,
1033            GraphEdge::Field { shadow: true, .. } | GraphEdge::Inherits { shadow: true }
1034        )
1035    }
1036}
1037
1038/// Metadata describing an edge from a source to a target type.
1039#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1040pub struct OutgoingEdge<T> {
1041    pub meta: T,
1042    pub target: NodeIndex<usize>,
1043}
1044
1045#[derive(Clone, Copy)]
1046struct Node<Ty> {
1047    index: NodeIndex<usize>,
1048    ty: Ty,
1049}
1050
1051/// Precomputed metadata for schema types and operations in the graph.
1052pub(super) struct CookedGraphMetadata<'a> {
1053    /// Transitive closure over the type graph.
1054    pub closure: Closure,
1055    /// Maps each type to its SCC equivalence class for boxing decisions.
1056    /// Two types in the same class form a cycle that requires `Box<T>`.
1057    pub box_sccs: Vec<usize>,
1058    /// Whether each type can implement `Eq` and `Hash`.
1059    pub hashable: FixedBitSet,
1060    /// Whether each type can implement `Default`.
1061    pub defaultable: FixedBitSet,
1062    /// Maps each type to the operations that use it.
1063    pub used_by: Vec<Vec<GraphOperation<'a>>>,
1064    /// Maps each operation to the types that it uses.
1065    pub uses: FxHashMap<GraphOperation<'a>, FixedBitSet>,
1066    /// Maps each inline type to its canonical path through the graph.
1067    pub paths: FxHashMap<InlineTypeId, InlineTypePath<'a>>,
1068    /// Opaque extended data for each type.
1069    pub extensions: Vec<AtomicRefCell<ExtensionMap>>,
1070}
1071
1072impl Debug for CookedGraphMetadata<'_> {
1073    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1074        f.debug_struct("CookedGraphMetadata")
1075            .field("closure", &self.closure)
1076            .field("box_sccs", &self.box_sccs)
1077            .field("hashable", &self.hashable)
1078            .field("defaultable", &self.defaultable)
1079            .field("used_by", &self.used_by)
1080            .field("uses", &self.uses)
1081            .finish_non_exhaustive()
1082    }
1083}
1084
1085/// Precomputed bitsets indicating which types can derive
1086/// `Eq` / `Hash` and `Default`.
1087struct HashDefault {
1088    hashable: FixedBitSet,
1089    defaultable: FixedBitSet,
1090}
1091
1092/// Precomputed metadata for an operation that references
1093/// types in the graph.
1094struct Operations<'a> {
1095    /// All the types that each operation depends on, directly and transitively.
1096    pub uses: FxHashMap<GraphOperation<'a>, FixedBitSet>,
1097    /// All the operations that use each type, directly and transitively.
1098    pub used_by: Vec<Vec<GraphOperation<'a>>>,
1099}
1100
1101/// A canonical path through the graph to an inline type.
1102#[derive(Clone, Copy, Debug)]
1103pub(super) struct InlineTypePath<'a> {
1104    pub root: InlineTypePathRoot<'a, NodeIndex<usize>, &'a str>,
1105    pub edges: &'a [EdgeIndex<usize>],
1106}
1107
1108struct MetadataBuilder<'graph, 'a> {
1109    arena: &'a Arena,
1110    graph: &'graph CookedDiGraph<'a>,
1111    ops: &'graph [&'graph GraphOperation<'a>],
1112    /// The full transitive closure of each type's dependencies.
1113    closure: Closure,
1114}
1115
1116impl<'graph, 'a> MetadataBuilder<'graph, 'a> {
1117    fn new(
1118        arena: &'a Arena,
1119        graph: &'graph CookedDiGraph<'a>,
1120        ops: &'graph [&'graph GraphOperation<'a>],
1121    ) -> Self {
1122        Self {
1123            arena,
1124            graph,
1125            ops,
1126            closure: Closure::new(graph),
1127        }
1128    }
1129
1130    fn build(self) -> CookedGraphMetadata<'a> {
1131        let operations = self.operations();
1132        let HashDefault {
1133            hashable,
1134            defaultable,
1135        } = self.hash_default();
1136        let box_sccs = self.box_sccs();
1137        let paths = self.paths();
1138        CookedGraphMetadata {
1139            closure: self.closure,
1140            box_sccs,
1141            hashable,
1142            defaultable,
1143            used_by: operations.used_by,
1144            uses: operations.uses,
1145            paths,
1146            // `AtomicRefCell` doesn't implement `Clone`,
1147            // so we use this idiom instead of `vec!`.
1148            extensions: std::iter::repeat_with(AtomicRefCell::default)
1149                .take(self.graph.node_count())
1150                .collect(),
1151        }
1152    }
1153
1154    fn operations(&self) -> Operations<'a> {
1155        let mut operations = Operations {
1156            uses: FxHashMap::default(),
1157            used_by: vec![vec![]; self.graph.node_count()],
1158        };
1159
1160        for &&op in self.ops {
1161            // Forward propagation: start from the direct types, then
1162            // expand to the full transitive dependency set.
1163            let mut dependencies = FixedBitSet::with_capacity(self.graph.node_count());
1164            for &node in op.types() {
1165                dependencies.extend(self.closure.dependencies_of(node).map(|n| n.index()));
1166            }
1167            operations.uses.entry(op).insert_entry(dependencies);
1168        }
1169
1170        // Backward propagation: mark types as used by their operations.
1171        for (op, deps) in &operations.uses {
1172            for node in deps.ones() {
1173                operations.used_by[node].push(*op);
1174            }
1175        }
1176
1177        operations
1178    }
1179
1180    /// Precomputes canonical paths for all inline type nodes.
1181    fn paths(&self) -> FxHashMap<InlineTypeId, InlineTypePath<'a>> {
1182        #[derive(Clone)]
1183        struct PartialPath<'a> {
1184            root: InlineTypePathRoot<'a, NodeIndex<usize>, &'a str>,
1185            edges: Vec<EdgeIndex<usize>>,
1186        }
1187
1188        let filtered = EdgeFiltered::from_fn(self.graph, |e| {
1189            !e.weight().shadow() && matches!(self.graph[e.target()], GraphType::Inline(_))
1190        });
1191
1192        let mut by_node = FxHashMap::default();
1193        let mut bfs = EdgeBfs::new(&filtered);
1194
1195        // Expand paths for each named schema root.
1196        for index in self.graph.node_indices() {
1197            if matches!(self.graph[index], GraphType::Schema(_)) && bfs.discover(index) {
1198                by_node.insert(
1199                    index,
1200                    PartialPath {
1201                        root: InlineTypePathRoot::Schema(index),
1202                        edges: vec![],
1203                    },
1204                );
1205            }
1206            while let Some(edge) = bfs.next() {
1207                let parent = &by_node[&edge.source()];
1208                let mut child = parent.clone();
1209                child.edges.push(edge.id());
1210                by_node.insert(edge.target(), child);
1211            }
1212        }
1213
1214        // Expand paths for each operation: operations aren't part of the graph;
1215        // their parameter, request, and response types are the roots.
1216        for op in self.ops {
1217            for param in op.params {
1218                let (usage, info) = match param {
1219                    Parameter::Path(info) => (OperationUsage::Path(info.name), info),
1220                    Parameter::Query(info) => (OperationUsage::Query(info.name), info),
1221                };
1222                if matches!(self.graph[info.ty], GraphType::Inline(_)) && bfs.discover(info.ty) {
1223                    by_node.insert(
1224                        info.ty,
1225                        PartialPath {
1226                            root: InlineTypePathRoot::Operation {
1227                                id: op.id,
1228                                resource: op.resource,
1229                                usage,
1230                            },
1231                            edges: vec![],
1232                        },
1233                    );
1234                }
1235            }
1236            if let Some(Request::Json(index)) = op.request
1237                && matches!(self.graph[index], GraphType::Inline(_))
1238                && bfs.discover(index)
1239            {
1240                by_node.insert(
1241                    index,
1242                    PartialPath {
1243                        root: InlineTypePathRoot::Operation {
1244                            id: op.id,
1245                            resource: op.resource,
1246                            usage: OperationUsage::Request,
1247                        },
1248                        edges: vec![],
1249                    },
1250                );
1251            }
1252            if let Some(Response::Json(index)) = op.response
1253                && matches!(self.graph[index], GraphType::Inline(_))
1254                && bfs.discover(index)
1255            {
1256                by_node.insert(
1257                    index,
1258                    PartialPath {
1259                        root: InlineTypePathRoot::Operation {
1260                            id: op.id,
1261                            resource: op.resource,
1262                            usage: OperationUsage::Response,
1263                        },
1264                        edges: vec![],
1265                    },
1266                );
1267            }
1268            while let Some(edge) = bfs.next() {
1269                let parent = &by_node[&edge.source()];
1270                let mut child = parent.clone();
1271                child.edges.push(edge.id());
1272                by_node.insert(edge.target(), child);
1273            }
1274        }
1275
1276        by_node
1277            .into_iter()
1278            .filter_map(
1279                |(index, PartialPath { root, edges })| match self.graph[index] {
1280                    GraphType::Inline(ty) => Some((
1281                        ty.id(),
1282                        InlineTypePath {
1283                            root,
1284                            edges: self.arena.alloc_slice(edges),
1285                        },
1286                    )),
1287                    _ => None,
1288                },
1289            )
1290            .collect()
1291    }
1292
1293    fn box_sccs(&self) -> Vec<usize> {
1294        let box_edges = EdgeFiltered::from_fn(self.graph, |e| match e.weight() {
1295            // Inheritance edges don't contribute to cycles;
1296            // a type can't inherit from itself.
1297            GraphEdge::Inherits { .. } => false,
1298            GraphEdge::Contains => match self.graph[e.source()] {
1299                GraphType::Schema(GraphSchemaType::Container(_, c))
1300                | GraphType::Inline(GraphInlineType::Container(_, c)) => {
1301                    // Array and map containers are heap-allocated,
1302                    // cycles through these edges don't need `Box`.
1303                    !matches!(c, GraphContainer::Array { .. } | GraphContainer::Map { .. })
1304                }
1305                _ => true,
1306            },
1307            _ => true,
1308        });
1309        let mut scc = TarjanScc::new();
1310        scc.run(&box_edges, |_| ());
1311        self.graph
1312            .node_indices()
1313            .map(|node| scc.node_component_index(&box_edges, node))
1314            .collect()
1315    }
1316
1317    fn hash_default(&self) -> HashDefault {
1318        // Mark all leaf types that can't derive `Eq` / `Hash` or `Default`.
1319        let n = self.graph.node_count();
1320        let mut unhashable = FixedBitSet::with_capacity(n);
1321        let mut undefaultable = FixedBitSet::with_capacity(n);
1322        for node in self.graph.node_indices() {
1323            use {GraphType::*, PrimitiveType::*};
1324            match &self.graph[node] {
1325                Schema(GraphSchemaType::Primitive(_, F32 | F64))
1326                | Inline(GraphInlineType::Primitive(_, F32 | F64)) => {
1327                    unhashable.insert(node.index());
1328                }
1329                Schema(
1330                    GraphSchemaType::Primitive(_, Url)
1331                    | GraphSchemaType::Tagged(_, _)
1332                    | GraphSchemaType::Untagged(_, _),
1333                )
1334                | Inline(
1335                    GraphInlineType::Primitive(_, Url)
1336                    | GraphInlineType::Tagged(_, _)
1337                    | GraphInlineType::Untagged(_, _),
1338                ) => {
1339                    undefaultable.insert(node.index());
1340                }
1341                _ => (),
1342            }
1343        }
1344
1345        // Compute the transitive closure over the inheritance subgraph.
1346        let inherits = Closure::new(&EdgeFiltered::from_fn(self.graph, |e| {
1347            matches!(e.weight(), GraphEdge::Inherits { .. })
1348        }));
1349
1350        // Propagate unhashability backward, from leaves to roots.
1351        //
1352        // This is conservative: if a descendant overrides an inherited
1353        // unhashable or undefaultable field with a different hashable or
1354        // defaultable type, that descendant is still marked.
1355        let mut queue: VecDeque<_> = unhashable.ones().map(NodeIndex::new).collect();
1356        while let Some(node) = queue.pop_front() {
1357            for edge in self.graph.edges_directed(node, Direction::Incoming) {
1358                let source = edge.source();
1359                match edge.weight() {
1360                    GraphEdge::Contains | GraphEdge::Variant(_) => {
1361                        if !unhashable.put(source.index()) {
1362                            queue.push_back(source);
1363                        }
1364                    }
1365                    GraphEdge::Field { .. } => {
1366                        if !unhashable.put(source.index()) {
1367                            queue.push_back(source);
1368                        }
1369                        // Every type that inherits from `source` also
1370                        // inherits this unhashable field, so mark all
1371                        // descendants of `source` as unhashable.
1372                        for desc in inherits.dependents_of(source).filter(|&d| d != source) {
1373                            if !unhashable.put(desc.index()) {
1374                                queue.push_back(desc);
1375                            }
1376                        }
1377                    }
1378                    // Don't follow inheritance edges: a parent's intrinsic
1379                    // unhashability (e.g., being a tagged union) doesn't
1380                    // make its children unhashable, because children only
1381                    // inherit the parent's fields, not its shape.
1382                    GraphEdge::Inherits { .. } => {}
1383                }
1384            }
1385        }
1386
1387        // Propagate undefaultability backward.
1388        let mut queue: VecDeque<_> = undefaultable.ones().map(NodeIndex::new).collect();
1389        while let Some(node) = queue.pop_front() {
1390            for edge in self.graph.edges_directed(node, Direction::Incoming) {
1391                if !matches!(
1392                    edge.weight(),
1393                    GraphEdge::Field { meta, .. } if meta.required
1394                ) {
1395                    // Optional fields become `AbsentOr<T>`,
1396                    // which is always `Default`.
1397                    continue;
1398                }
1399                let source = edge.source();
1400                if !undefaultable.put(source.index()) {
1401                    queue.push_back(source);
1402                }
1403                // Every type that inherits from `source` also
1404                // inherits this undefaultable field, so mark all
1405                // descendants of `source` as undefaultable.
1406                for desc in inherits.dependents_of(source).filter(|&d| d != source) {
1407                    if !undefaultable.put(desc.index()) {
1408                        queue.push_back(desc);
1409                    }
1410                }
1411            }
1412        }
1413
1414        HashDefault {
1415            hashable: invert(unhashable),
1416            defaultable: invert(undefaultable),
1417        }
1418    }
1419}
1420
1421/// Inverts every bit in the bitset.
1422fn invert(mut bits: FixedBitSet) -> FixedBitSet {
1423    bits.toggle_range(..);
1424    bits
1425}
1426
1427/// Visits all the types and references contained within a [`SpecType`].
1428#[derive(Debug)]
1429struct SpecTypeVisitor<'a> {
1430    stack: Vec<(Option<(&'a SpecType<'a>, GraphEdge<'a>)>, &'a SpecType<'a>)>,
1431}
1432
1433impl<'a> SpecTypeVisitor<'a> {
1434    /// Creates a visitor with `roots` on the stack of types to visit.
1435    #[inline]
1436    fn new(roots: impl Iterator<Item = &'a SpecType<'a>>) -> Self {
1437        let mut stack = roots.map(|root| (None, root)).collect_vec();
1438        stack.reverse();
1439        Self { stack }
1440    }
1441}
1442
1443impl<'a> Iterator for SpecTypeVisitor<'a> {
1444    type Item = (Option<(&'a SpecType<'a>, GraphEdge<'a>)>, &'a SpecType<'a>);
1445
1446    fn next(&mut self) -> Option<Self::Item> {
1447        let (parent, top) = self.stack.pop()?;
1448        if matches!(parent, Some((parent, _)) if parent == top) {
1449            // Self-edges have no unvisited target; skip them to avoid an
1450            // infinite loop.
1451            return Some((parent, top));
1452        }
1453        match top {
1454            SpecType::Schema(SpecSchemaType::Struct(_, ty))
1455            | SpecType::Inline(SpecInlineType::Struct(_, ty)) => {
1456                self.stack.extend(
1457                    itertools::chain!(
1458                        ty.fields.iter().map(|field| (
1459                            GraphEdge::Field {
1460                                shadow: false,
1461                                meta: FieldMeta {
1462                                    name: field.name,
1463                                    required: field.required,
1464                                    description: field.description,
1465                                    flattened: field.flattened,
1466                                },
1467                            },
1468                            field.ty
1469                        )),
1470                        ty.parents
1471                            .iter()
1472                            .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1473                    )
1474                    .map(|(edge, ty)| (Some((top, edge)), ty))
1475                    .rev(),
1476                );
1477            }
1478            SpecType::Schema(SpecSchemaType::Untagged(_, ty))
1479            | SpecType::Inline(SpecInlineType::Untagged(_, ty)) => {
1480                self.stack.extend(
1481                    itertools::chain!(
1482                        ty.fields.iter().map(|field| (
1483                            GraphEdge::Field {
1484                                shadow: false,
1485                                meta: FieldMeta {
1486                                    name: field.name,
1487                                    required: field.required,
1488                                    description: field.description,
1489                                    flattened: field.flattened,
1490                                },
1491                            },
1492                            field.ty
1493                        )),
1494                        ty.variants.iter().enumerate().map(|(index, variant)| {
1495                            let ordinal = NonZeroUsize::new(index + 1).unwrap();
1496                            match variant {
1497                                &Some(ty) => (
1498                                    GraphEdge::Variant(
1499                                        UntaggedVariantMeta {
1500                                            ordinal,
1501                                            null: false,
1502                                        }
1503                                        .into(),
1504                                    ),
1505                                    ty,
1506                                ),
1507                                // `null` variants have no target type;
1508                                // we represent these variants as self-edges.
1509                                None => (
1510                                    GraphEdge::Variant(
1511                                        UntaggedVariantMeta {
1512                                            ordinal,
1513                                            null: true,
1514                                        }
1515                                        .into(),
1516                                    ),
1517                                    top,
1518                                ),
1519                            }
1520                        }),
1521                        ty.parents
1522                            .iter()
1523                            .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1524                    )
1525                    .map(|(edge, ty)| (Some((top, edge)), ty))
1526                    .rev(),
1527                );
1528            }
1529            SpecType::Schema(SpecSchemaType::Tagged(_, ty))
1530            | SpecType::Inline(SpecInlineType::Tagged(_, ty)) => {
1531                self.stack.extend(
1532                    itertools::chain!(
1533                        ty.fields.iter().map(|field| (
1534                            GraphEdge::Field {
1535                                shadow: false,
1536                                meta: FieldMeta {
1537                                    name: field.name,
1538                                    required: field.required,
1539                                    description: field.description,
1540                                    flattened: field.flattened,
1541                                },
1542                            },
1543                            field.ty
1544                        )),
1545                        ty.variants.iter().map(|variant| (
1546                            GraphEdge::Variant(
1547                                TaggedVariantMeta {
1548                                    name: variant.name,
1549                                    aliases: variant.aliases,
1550                                }
1551                                .into()
1552                            ),
1553                            variant.ty
1554                        )),
1555                        ty.parents
1556                            .iter()
1557                            .map(|parent| (GraphEdge::Inherits { shadow: false }, *parent)),
1558                    )
1559                    .map(|(edge, ty)| (Some((top, edge)), ty))
1560                    .rev(),
1561                );
1562            }
1563            SpecType::Schema(SpecSchemaType::Container(_, container))
1564            | SpecType::Inline(SpecInlineType::Container(_, container)) => {
1565                self.stack
1566                    .push((Some((top, GraphEdge::Contains)), container.inner().ty));
1567            }
1568            SpecType::Schema(
1569                SpecSchemaType::Enum(..) | SpecSchemaType::Primitive(..) | SpecSchemaType::Any(_),
1570            )
1571            | SpecType::Inline(
1572                SpecInlineType::Enum(..) | SpecInlineType::Primitive(..) | SpecInlineType::Any(_),
1573            ) => (),
1574            SpecType::Ref(_) => (),
1575        }
1576        Some((parent, top))
1577    }
1578}
1579
1580/// A map that can store one value for each type.
1581pub(super) type ExtensionMap = FxHashMap<StdTypeId, Box<dyn Extension>>;
1582
1583pub trait Extension: Any + Send + Sync {
1584    fn into_inner(self: Box<Self>) -> Box<dyn Any>;
1585}
1586
1587impl dyn Extension {
1588    #[inline]
1589    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
1590        (self as &dyn Any).downcast_ref::<T>()
1591    }
1592}
1593
1594impl<T: Send + Sync + 'static> Extension for T {
1595    #[inline]
1596    fn into_inner(self: Box<Self>) -> Box<dyn Any> {
1597        self
1598    }
1599}
1600
1601/// Strongly connected components (SCCs) in topological order.
1602///
1603/// [`TopoSccs`] uses Tarjan's single-pass algorithm to find all SCCs,
1604/// and provides topological ordering, efficient membership testing, and
1605/// condensation for computing the transitive closure. These are
1606/// building blocks for cycle detection and dependency propagation.
1607struct TopoSccs<G> {
1608    graph: G,
1609    tarjan: TarjanScc<NodeIndex<usize>>,
1610    sccs: Vec<Vec<usize>>,
1611}
1612
1613impl<G> TopoSccs<G>
1614where
1615    G: Closable<NodeIndex<usize>> + Copy,
1616{
1617    fn new(graph: G) -> Self {
1618        let mut sccs = Vec::new();
1619        let mut tarjan = TarjanScc::new();
1620        tarjan.run(graph, |scc_nodes| {
1621            sccs.push(scc_nodes.iter().map(|node| node.index()).collect());
1622        });
1623        // Tarjan's algorithm returns SCCs in reverse topological order;
1624        // reverse them to get the topological order.
1625        sccs.reverse();
1626        Self {
1627            graph,
1628            tarjan,
1629            sccs,
1630        }
1631    }
1632
1633    #[inline]
1634    fn scc_count(&self) -> usize {
1635        self.sccs.len()
1636    }
1637
1638    /// Returns the topological index of the SCC that contains the given node.
1639    #[inline]
1640    fn topo_index(&self, node: NodeIndex<usize>) -> usize {
1641        // Tarjan's algorithm returns indices in reverse topological order;
1642        // inverting the component index gets us the topological index.
1643        self.sccs.len() - 1 - self.tarjan.node_component_index(self.graph, node)
1644    }
1645
1646    /// Builds a condensed DAG of SCCs.
1647    ///
1648    /// The condensed graph is represented as an adjacency list, where both
1649    /// the node indices and the neighbors of each node are stored in
1650    /// topological order. This specific ordering is required by
1651    /// [`tred::dag_transitive_reduction_closure`].
1652    fn condensation(&self) -> UnweightedList<usize> {
1653        let mut dag = UnweightedList::with_capacity(self.scc_count());
1654        for to in 0..self.scc_count() {
1655            dag.add_node();
1656            for neighbor in self.sccs[to].iter().flat_map(|&index| {
1657                self.graph
1658                    .neighbors_directed(NodeIndex::new(index), Direction::Incoming)
1659            }) {
1660                let from = self.topo_index(neighbor);
1661                if from != to {
1662                    dag.update_edge(from, to, ());
1663                }
1664            }
1665        }
1666        dag
1667    }
1668}
1669
1670/// The transitive closure of a graph.
1671#[derive(Debug)]
1672pub(super) struct Closure {
1673    /// Maps each node index to its SCC's topological index.
1674    scc_indices: Vec<usize>,
1675    /// Members of each SCC, indexed by topological SCC index.
1676    scc_members: Vec<Vec<usize>>,
1677    /// Maps each SCC to a list of all the SCCs that it transitively depends on,
1678    /// excluding itself.
1679    scc_deps: Vec<Vec<usize>>,
1680    /// Maps each SCC to a list of all the SCCs that transitively depend on it,
1681    /// excluding itself.
1682    scc_rdeps: Vec<Vec<usize>>,
1683}
1684
1685impl Closure {
1686    /// Computes the transitive closure of a graph.
1687    fn new<G>(graph: G) -> Self
1688    where
1689        G: Closable<NodeIndex<usize>> + Copy,
1690    {
1691        let sccs = TopoSccs::new(graph);
1692        let condensation = sccs.condensation();
1693        let (_, closure) = tred::dag_transitive_reduction_closure(&condensation);
1694
1695        // Build the forward and reverse adjacency lists
1696        // from the transitive closure graph.
1697        let scc_deps = (0..sccs.scc_count())
1698            .map(|scc| closure.neighbors(scc).collect_vec())
1699            .collect_vec();
1700        let mut scc_rdeps = vec![vec![]; sccs.scc_count()];
1701        for (scc, deps) in scc_deps.iter().enumerate() {
1702            for &dep in deps {
1703                scc_rdeps[dep].push(scc);
1704            }
1705        }
1706
1707        let mut scc_indices = vec![0; graph.node_count()];
1708        for node in graph.node_identifiers() {
1709            scc_indices[node.index()] = sccs.topo_index(node);
1710        }
1711
1712        Closure {
1713            scc_indices,
1714            scc_members: sccs.sccs.iter().cloned().collect_vec(),
1715            scc_deps,
1716            scc_rdeps,
1717        }
1718    }
1719
1720    /// Returns the topological SCC index for the given node.
1721    #[inline]
1722    pub fn scc_index_of(&self, node: NodeIndex<usize>) -> usize {
1723        self.scc_indices[node.index()]
1724    }
1725
1726    /// Iterates over all nodes that `node` transitively depends on,
1727    /// including `node` and all members of its SCC.
1728    pub fn dependencies_of(
1729        &self,
1730        node: NodeIndex<usize>,
1731    ) -> impl Iterator<Item = NodeIndex<usize>> {
1732        let scc = self.scc_index_of(node);
1733        std::iter::once(scc)
1734            .chain(self.scc_deps[scc].iter().copied())
1735            .flat_map(|s| self.scc_members[s].iter().copied()) // Expand SCCs to nodes.
1736            .map(NodeIndex::new)
1737    }
1738
1739    /// Iterates over all nodes that transitively depend on `node`,
1740    /// including `node` and all members of its SCC.
1741    pub fn dependents_of(&self, node: NodeIndex<usize>) -> impl Iterator<Item = NodeIndex<usize>> {
1742        let scc = self.scc_index_of(node);
1743        std::iter::once(scc)
1744            .chain(self.scc_rdeps[scc].iter().copied())
1745            .flat_map(|s| self.scc_members[s].iter().copied())
1746            .map(NodeIndex::new)
1747    }
1748
1749    /// Returns whether `node` transitively depends on `other`,
1750    /// or `false` when `node == other`.
1751    #[inline]
1752    pub fn depends_on(&self, node: NodeIndex<usize>, other: NodeIndex<usize>) -> bool {
1753        if node == other {
1754            return false;
1755        }
1756        let scc = self.scc_index_of(node);
1757        let other_scc = self.scc_index_of(other);
1758        scc == other_scc || self.scc_deps[scc].contains(&other_scc)
1759    }
1760}
1761
1762/// Trait requirements for computing a transitive closure.
1763trait Closable<N>:
1764    NodeCount
1765    + IntoNodeIdentifiers<NodeId = N>
1766    + IntoNeighbors<NodeId = N>
1767    + IntoNeighborsDirected<NodeId = N>
1768    + NodeIndexable<NodeId = N>
1769{
1770}
1771
1772impl<N, G> Closable<N> for G where
1773    G: NodeCount
1774        + IntoNodeIdentifiers<NodeId = N>
1775        + IntoNeighbors<NodeId = N>
1776        + IntoNeighborsDirected<NodeId = N>
1777        + NodeIndexable<NodeId = N>
1778{
1779}
1780
1781/// A breadth-first search that yields discovery edges instead of nodes.
1782///
1783/// A standard [`Bfs`] yields each node as it's dequeued. This variant yields
1784/// the edge that first discovered each target, which is the primitive we need
1785/// to build inline type paths.
1786///
1787/// [`Bfs`]: petgraph::visit::Bfs
1788struct EdgeBfs<G, N, VM> {
1789    graph: G,
1790    queue: VecDeque<N>,
1791    discovered: VM,
1792}
1793
1794impl<G, N, VM> EdgeBfs<G, N, VM>
1795where
1796    N: Copy,
1797    VM: VisitMap<N>,
1798{
1799    /// Creates an empty traversal for the given graph.
1800    fn new(graph: G) -> Self
1801    where
1802        G: GraphRef + Visitable<NodeId = N, Map = VM>,
1803    {
1804        let discovered = graph.visit_map();
1805        Self {
1806            graph,
1807            queue: VecDeque::new(),
1808            discovered,
1809        }
1810    }
1811
1812    /// Marks `node` as discovered and enqueues it for expansion.
1813    /// Returns `true` if the node was newly discovered.
1814    fn discover(&mut self, node: N) -> bool {
1815        if self.discovered.visit(node) {
1816            self.queue.push_back(node);
1817            true
1818        } else {
1819            false
1820        }
1821    }
1822
1823    /// Returns the next edge whose target has not been discovered.
1824    ///
1825    /// The traversal keeps the source node at the front of the queue until
1826    /// the targets of all its outgoing edges have been discovered. Each
1827    /// returned edge marks its target as discovered and enqueues it for
1828    /// later expansion.
1829    fn next(&mut self) -> Option<G::EdgeRef>
1830    where
1831        G: IntoEdges<NodeId = N>,
1832    {
1833        loop {
1834            let &source = self.queue.front()?;
1835            for edge in self.graph.edges(source) {
1836                let target = edge.target();
1837                if self.discovered.visit(target) {
1838                    self.queue.push_back(target);
1839                    return Some(edge);
1840                }
1841            }
1842            self.queue.pop_front();
1843        }
1844    }
1845}
1846
1847#[cfg(test)]
1848mod tests {
1849    use super::*;
1850
1851    use crate::tests::assert_matches;
1852
1853    /// Creates a simple graph: `A -> B -> C`.
1854    fn linear_graph() -> DiGraph<(), (), usize> {
1855        let mut g = DiGraph::default();
1856        let a = g.add_node(());
1857        let b = g.add_node(());
1858        let c = g.add_node(());
1859        g.extend_with_edges([(a, b), (b, c)]);
1860        g
1861    }
1862
1863    /// Creates a cyclic graph: `A -> B -> C -> A`, with `D -> A`.
1864    fn cyclic_graph() -> DiGraph<(), (), usize> {
1865        let mut g = DiGraph::default();
1866        let a = g.add_node(());
1867        let b = g.add_node(());
1868        let c = g.add_node(());
1869        let d = g.add_node(());
1870        g.extend_with_edges([(a, b), (b, c), (c, a), (d, a)]);
1871        g
1872    }
1873
1874    // MARK: SCC detection
1875
1876    #[test]
1877    fn test_linear_graph_has_singleton_sccs() {
1878        let g = linear_graph();
1879        let sccs = TopoSccs::new(&g);
1880        let sizes = sccs.sccs.iter().map(|scc| scc.len()).collect_vec();
1881        assert_matches!(&*sizes, [1, 1, 1]);
1882    }
1883
1884    #[test]
1885    fn test_cyclic_graph_has_one_multi_node_scc() {
1886        let g = cyclic_graph();
1887        let sccs = TopoSccs::new(&g);
1888
1889        // A-B-C form one SCC; D is its own SCC. Since D has an edge to
1890        // the cycle, D must precede the cycle in topological order.
1891        let sizes = sccs.sccs.iter().map(|scc| scc.len()).collect_vec();
1892        assert_matches!(&*sizes, [1, 3]);
1893    }
1894
1895    // MARK: Topological ordering
1896
1897    #[test]
1898    fn test_sccs_are_in_topological_order() {
1899        let g = cyclic_graph();
1900        let sccs = TopoSccs::new(&g);
1901
1902        let d_topo = sccs.topo_index(3.into());
1903        let a_topo = sccs.topo_index(0.into());
1904        assert!(
1905            d_topo < a_topo,
1906            "D should precede A-B-C in topological order"
1907        );
1908    }
1909
1910    #[test]
1911    fn test_topo_index_consistent_within_scc() {
1912        let g = cyclic_graph();
1913        let sccs = TopoSccs::new(&g);
1914
1915        // A, B, C are in the same SCC, so they should have
1916        // the same topological index.
1917        let a_topo = sccs.topo_index(0.into());
1918        let b_topo = sccs.topo_index(1.into());
1919        let c_topo = sccs.topo_index(2.into());
1920
1921        assert_eq!(a_topo, b_topo);
1922        assert_eq!(b_topo, c_topo);
1923    }
1924
1925    // MARK: Condensation
1926
1927    #[test]
1928    fn test_condensation_has_correct_node_count() {
1929        let g = cyclic_graph();
1930        let sccs = TopoSccs::new(&g);
1931        let dag = sccs.condensation();
1932
1933        assert_eq!(dag.node_count(), 2);
1934    }
1935
1936    #[test]
1937    fn test_condensation_has_correct_edges() {
1938        let g = cyclic_graph();
1939        let sccs = TopoSccs::new(&g);
1940        let dag = sccs.condensation();
1941
1942        // D should have an edge to the A-B-C SCC, and
1943        // A-B-C shouldn't create a self-loop.
1944        let d_topo = sccs.topo_index(3.into());
1945        let abc_topo = sccs.topo_index(0.into());
1946
1947        let d_neighbors = dag.neighbors(d_topo).collect_vec();
1948        assert_eq!(&*d_neighbors, [abc_topo]);
1949
1950        let abc_neighbors = dag.neighbors(abc_topo).collect_vec();
1951        assert!(abc_neighbors.is_empty());
1952    }
1953
1954    #[test]
1955    fn test_condensation_neighbors_in_topological_order() {
1956        // Matches Petgraph's `dag_to_toposorted_adjacency_list` example:
1957        // edges added as `(top, second), (top, first)`, but neighbors should be
1958        // `[first, second]` in topological order.
1959        let mut g = DiGraph::<(), (), usize>::default();
1960        let second = g.add_node(());
1961        let top = g.add_node(());
1962        let first = g.add_node(());
1963        g.extend_with_edges([(top, second), (top, first), (first, second)]);
1964
1965        let sccs = TopoSccs::new(&g);
1966        let dag = sccs.condensation();
1967
1968        let top_topo = sccs.topo_index(top);
1969        assert_eq!(top_topo, 0);
1970
1971        let first_topo = sccs.topo_index(first);
1972        assert_eq!(first_topo, 1);
1973
1974        let second_topo = sccs.topo_index(second);
1975        assert_eq!(second_topo, 2);
1976
1977        let neighbors = dag.neighbors(top_topo).collect_vec();
1978        assert_eq!(&*neighbors, [first_topo, second_topo]);
1979    }
1980}