Skip to main content

pedant_graph/
graph.rs

1//! The immutable graph value and the one checked store every build inserts
2//! through.
3
4use pedant_types::ResolutionTier;
5use serde::Serialize;
6
7use crate::containment::ContainmentEdge;
8use crate::edge::{EdgeDraft, GraphEdge, GraphEdgeOrigin};
9use crate::error::GraphBuildError;
10use crate::id::{GraphEdgeId, GraphNodeId, GraphReferenceId, index_of};
11use crate::limits::{GraphCollection, GraphLimits};
12use crate::node::{GraphNode, NodeDraft};
13use crate::reference::{GraphReference, ReferenceDraft};
14
15/// One deterministic, language-tagged code topology.
16///
17/// Immutable after construction: fields are private, every accessor borrows,
18/// and the value implements neither `Clone` nor `Deserialize`. A consumer that
19/// wants a graph builds one from the facts it holds rather than hydrating one
20/// whose records nothing validated.
21#[derive(Serialize, Debug, PartialEq, Eq)]
22pub struct CodeGraph {
23    schema_version: u32,
24    tier: ResolutionTier,
25    nodes: Box<[GraphNode]>,
26    containment: Box<[ContainmentEdge]>,
27    references: Box<[GraphReference]>,
28    edges: Box<[GraphEdge]>,
29}
30
31impl CodeGraph {
32    /// The version of the serialized topology contract this crate emits.
33    pub const SCHEMA_VERSION: u32 = 1;
34
35    /// The version of the serialized topology contract this value carries.
36    pub fn schema_version(&self) -> u32 {
37        self.schema_version
38    }
39
40    /// Which resolution mechanism produced the facts behind this graph.
41    pub fn tier(&self) -> ResolutionTier {
42        self.tier
43    }
44
45    /// Every node, indexed by its own dense identity.
46    pub fn nodes(&self) -> &[GraphNode] {
47        &self.nodes
48    }
49
50    /// The node one identity selects.
51    pub fn node(&self, id: GraphNodeId) -> Option<&GraphNode> {
52        self.nodes.get(index_of(id.index()))
53    }
54
55    /// Every containment edge, sorted by child.
56    pub fn containment(&self) -> &[ContainmentEdge] {
57        &self.containment
58    }
59
60    /// Every reference record, indexed by its own dense identity.
61    pub fn references(&self) -> &[GraphReference] {
62        &self.references
63    }
64
65    /// The reference record one identity selects.
66    pub fn reference(&self, id: GraphReferenceId) -> Option<&GraphReference> {
67        self.references.get(index_of(id.index()))
68    }
69
70    /// Every edge, indexed by its own dense identity.
71    pub fn edges(&self) -> &[GraphEdge] {
72        &self.edges
73    }
74
75    /// The edge one identity selects.
76    pub fn edge(&self, id: GraphEdgeId) -> Option<&GraphEdge> {
77        self.edges.get(index_of(id.index()))
78    }
79}
80
81/// How many records one build states before its first pass.
82///
83/// Every count is a stated size, never a ceiling: the store reserves the
84/// smaller of the count and the limit that collection refuses at.
85pub(crate) struct RecordCapacity {
86    /// The unit containers and definition nodes the report states.
87    pub(crate) nodes: usize,
88    /// The reference records the report states.
89    pub(crate) references: usize,
90    /// The dependency and candidate edges the inputs state.
91    pub(crate) edges: usize,
92}
93
94/// The growing record store one build inserts through.
95///
96/// Every identity is minted by [`GraphRecords::admit`], so the configured
97/// ceiling and the fixed-width identity ceiling are one check and a refusal
98/// never leaves a partially inserted record behind. [`GraphRecords::contain`]
99/// mints nothing: it states a relation between identities this store has
100/// already issued.
101pub(crate) struct GraphRecords {
102    limits: GraphLimits,
103    nodes: Vec<GraphNode>,
104    containment: Vec<ContainmentEdge>,
105    references: Vec<(GraphReferenceId, ReferenceDraft)>,
106    reference_edges: Vec<Vec<GraphEdgeId>>,
107    edges: Vec<GraphEdge>,
108}
109
110impl GraphRecords {
111    /// An empty store bounded by `limits`, sized for `capacity`.
112    pub(crate) fn new(limits: GraphLimits, capacity: RecordCapacity) -> Self {
113        let nodes = reserved(capacity.nodes, limits.ceiling(GraphCollection::Node));
114        let references = reserved(
115            capacity.references,
116            limits.ceiling(GraphCollection::Reference),
117        );
118        Self {
119            limits,
120            nodes: Vec::with_capacity(nodes),
121            containment: Vec::with_capacity(nodes),
122            references: Vec::with_capacity(references),
123            reference_edges: Vec::with_capacity(references),
124            edges: Vec::with_capacity(reserved(
125                capacity.edges,
126                limits.ceiling(GraphCollection::Edge),
127            )),
128        }
129    }
130
131    /// Append one node, or refuse before the store is touched.
132    pub(crate) fn insert_node(&mut self, draft: NodeDraft) -> Result<GraphNodeId, GraphBuildError> {
133        let index = self.admit(GraphCollection::Node)?;
134        let id = GraphNodeId::new(index);
135        self.nodes.push(GraphNode::new(id, draft));
136        Ok(id)
137    }
138
139    /// Append one reference record, or refuse before the store is touched.
140    pub(crate) fn insert_reference(
141        &mut self,
142        draft: ReferenceDraft,
143    ) -> Result<GraphReferenceId, GraphBuildError> {
144        let index = self.admit(GraphCollection::Reference)?;
145        let id = GraphReferenceId::new(index);
146        self.references.push((id, draft));
147        self.reference_edges.push(Vec::new());
148        Ok(id)
149    }
150
151    /// Append one edge and link it to the record that produced it, or refuse
152    /// before the store is touched.
153    ///
154    /// An edge whose origin names a reference is attached to that record here,
155    /// so minting and linking are one step and no identity can reach
156    /// [`GraphReference::edges`] without an edge behind it. A reference that
157    /// answers for no record refuses before the edge exists: dropping the link
158    /// would leave the edge in the graph and its record claiming it produced
159    /// nothing.
160    pub(crate) fn insert_edge(&mut self, draft: EdgeDraft) -> Result<GraphEdgeId, GraphBuildError> {
161        let index = self.admit(GraphCollection::Edge)?;
162        let id = GraphEdgeId::new(index);
163        self.link_origin(&draft.origin, id)?;
164        self.edges.push(GraphEdge::new(id, draft));
165        Ok(id)
166    }
167
168    /// Record that the reference an edge names produced it.
169    ///
170    /// A dependency edge links nothing: the declaration it carries is its own
171    /// evidence, and no record claims it.
172    fn link_origin(
173        &mut self,
174        origin: &GraphEdgeOrigin,
175        edge: GraphEdgeId,
176    ) -> Result<(), GraphBuildError> {
177        let reference = match origin {
178            GraphEdgeOrigin::Reference { reference } => *reference,
179            GraphEdgeOrigin::Dependency { .. } => return Ok(()),
180        };
181        match self.reference_edges.get_mut(index_of(reference.index())) {
182            Some(produced) => {
183                produced.push(edge);
184                Ok(())
185            }
186            None => Err(GraphBuildError::MissingReferenceRecord {
187                reference: reference.index(),
188            }),
189        }
190    }
191
192    /// Record that `parent` logically owns `child`.
193    pub(crate) fn contain(&mut self, parent: GraphNodeId, child: GraphNodeId) {
194        self.containment.push(ContainmentEdge::new(parent, child));
195    }
196
197    /// Every containment edge stated so far.
198    pub(crate) fn containment(&self) -> &[ContainmentEdge] {
199        &self.containment
200    }
201
202    /// How many nodes the store holds.
203    pub(crate) fn node_count(&self) -> usize {
204        self.nodes.len()
205    }
206
207    /// Seal the store into one immutable graph.
208    ///
209    /// Containment sorts by child then parent, which is a total order over the
210    /// stated edges rather than one that holds only while a separate validator
211    /// rejects a twice-claimed child. Each reference record keeps the identity
212    /// its insertion minted, so a record can never be renumbered away from the
213    /// edges that name it.
214    pub(crate) fn finish(mut self, tier: ResolutionTier) -> CodeGraph {
215        self.containment
216            .sort_unstable_by_key(|edge| (edge.child(), edge.parent()));
217        let references = self
218            .references
219            .into_iter()
220            .zip(self.reference_edges)
221            .map(|((id, draft), edges)| GraphReference::new(id, draft, edges.into_boxed_slice()))
222            .collect();
223        CodeGraph {
224            schema_version: CodeGraph::SCHEMA_VERSION,
225            tier,
226            nodes: self.nodes.into_boxed_slice(),
227            containment: self.containment.into_boxed_slice(),
228            references,
229            edges: self.edges.into_boxed_slice(),
230        }
231    }
232
233    /// The identity the next entry of `collection` would take.
234    ///
235    /// The one place a graph identity is minted: it proves the configured
236    /// ceiling and the fixed-width ceiling together, before any mutation. The
237    /// count is read from the named collection rather than supplied, so an
238    /// identity can never be minted from the length of another slice.
239    fn admit(&self, collection: GraphCollection) -> Result<u32, GraphBuildError> {
240        let count = match collection {
241            GraphCollection::Node => self.nodes.len(),
242            GraphCollection::Reference => self.references.len(),
243            GraphCollection::Edge => self.edges.len(),
244        };
245        let limit = self.limits.ceiling(collection);
246        u32::try_from(count)
247            .ok()
248            .filter(|next| *next < limit)
249            .ok_or(GraphBuildError::CapacityExceeded { collection, limit })
250    }
251}
252
253/// How much one collection reserves for a stated count.
254///
255/// Never more than the ceiling that collection refuses at, so a build the
256/// limits are going to refuse allocates no more than the graph it would have
257/// admitted. Every table sized from a stated count reserves through here, so
258/// one rule bounds them all.
259pub(crate) fn reserved(stated: usize, ceiling: u32) -> usize {
260    stated.min(index_of(ceiling))
261}