adam_rs/template/provenance.rs
1//! Flat, source-independent occurrence and authored-method mappings.
2//!
3//! Occurrences follow depth-first inclusion order. Their parent links retain
4//! invocation paths without copying full paths or retaining recipe DAG edges.
5//!
6//! ```
7//! use adam_rs::{Sheet, TemplateBuilder};
8//! let template = TemplateBuilder::new().finish()?;
9//! let instance = template.instantiate(&mut Sheet::new(), &[])?;
10//! let root = &instance.occurrences()[0];
11//! assert_eq!(root.template(), &template.id());
12//! assert!(root.parent().is_none());
13//! assert_eq!(instance.occurrence(root.id()).unwrap().id(), root.id());
14//! # Ok::<(), adam_rs::TemplateError>(())
15//! ```
16
17use std::hash::{Hash, Hasher};
18use std::ops::Range;
19use std::rc::Rc;
20
21use super::{CellSlot, Instance, RelationshipSlot};
22use crate::{CellId, RelationshipId};
23
24/// Identifies an immutable recipe without retaining its factories or inclusion edges.
25///
26/// Clones share identity. Equality and hashing compare ownership, not structure.
27///
28/// # Examples
29/// See the [occurrence example](InstanceOccurrence).
30#[derive(Clone, Debug)]
31pub struct TemplateId(pub(super) Rc<()>);
32
33impl PartialEq for TemplateId {
34 /// Compares immutable recipe ownership.
35 fn eq(&self, other: &Self) -> bool {
36 Rc::ptr_eq(&self.0, &other.0)
37 }
38}
39
40impl Eq for TemplateId {}
41
42impl Hash for TemplateId {
43 /// Hashes the ownership identity used by equality.
44 fn hash<H: Hasher>(&self, state: &mut H) {
45 Rc::as_ptr(&self.0).hash(state);
46 }
47}
48
49/// Identifies one inclusion occurrence within exactly one live expansion.
50///
51/// The retained token keeps identity distinct after the instance is dropped.
52///
53/// # Examples
54/// See the [occurrence example](InstanceOccurrence).
55#[derive(Clone, Debug)]
56pub struct OccurrenceId {
57 pub(super) owner: Rc<()>,
58 pub(super) index: usize,
59}
60
61impl PartialEq for OccurrenceId {
62 /// Compares expansion ownership and occurrence index.
63 fn eq(&self, other: &Self) -> bool {
64 Rc::ptr_eq(&self.owner, &other.owner) && self.index == other.index
65 }
66}
67
68impl Eq for OccurrenceId {}
69
70impl Hash for OccurrenceId {
71 /// Hashes the expansion owner and local index used by equality.
72 fn hash<H: Hasher>(&self, state: &mut H) {
73 Rc::as_ptr(&self.owner).hash(state);
74 self.index.hash(state);
75 }
76}
77
78/// Retains one occurrence's identity, immediate parent, and local mapping ranges.
79///
80/// Parent/site pairs distinguish repeated uses of shared children. Root occurrences
81/// have neither a parent nor an inclusion site. No record owns another record.
82///
83/// ```
84/// use adam_rs::{Sheet, TemplateBuilder};
85/// let template = TemplateBuilder::new().finish()?;
86/// let instance = template.instantiate(&mut Sheet::new(), &[])?;
87/// let root = &instance.occurrences()[0];
88/// assert_eq!(root.template(), &template.id());
89/// assert!(root.parent().is_none());
90/// assert!(root.inclusion_site().is_none());
91/// assert_eq!(instance.occurrence(root.id()).unwrap().id(), root.id());
92/// # Ok::<(), adam_rs::TemplateError>(())
93/// ```
94#[derive(Debug)]
95pub struct InstanceOccurrence {
96 pub(super) id: OccurrenceId,
97 pub(super) template: TemplateId,
98 pub(super) parent: Option<OccurrenceId>,
99 pub(super) inclusion_site: Option<RelationshipSlot>,
100 pub(super) cells: Range<usize>,
101 pub(super) groups: Range<usize>,
102 pub(super) conditionals: Vec<crate::ConditionalId>,
103}
104
105impl InstanceOccurrence {
106 /// Borrows live conditional identities in this definition's authored order.
107 ///
108 /// These identities remain usable after the template is dropped.
109 ///
110 /// # Examples
111 ///
112 /// ```
113 /// let template = adam_rs::TemplateBuilder::new().finish()?;
114 /// let instance = template.instantiate(&mut adam_rs::Sheet::new(), &[])?;
115 /// assert!(instance.occurrences()[0].conditionals().is_empty());
116 /// # Ok::<(), adam_rs::TemplateError>(())
117 /// ```
118 pub fn conditionals(&self) -> &[crate::ConditionalId] {
119 &self.conditionals
120 }
121
122 /// Borrows this occurrence's expansion-scoped identity.
123 ///
124 /// # Examples
125 ///
126 /// See the [occurrence example](InstanceOccurrence).
127 pub fn id(&self) -> &OccurrenceId {
128 &self.id
129 }
130
131 /// Borrows the identity of the expanded recipe.
132 ///
133 /// # Examples
134 ///
135 /// See the [occurrence example](InstanceOccurrence).
136 pub fn template(&self) -> &TemplateId {
137 &self.template
138 }
139
140 /// Borrows the immediate parent identity, or returns `None` for the root.
141 ///
142 /// # Examples
143 ///
144 /// See the [occurrence example](InstanceOccurrence).
145 pub fn parent(&self) -> Option<&OccurrenceId> {
146 self.parent.as_ref()
147 }
148
149 /// Borrows the inclusion group in the parent's namespace, or returns `None` for the root.
150 ///
151 /// # Examples
152 ///
153 /// See the [occurrence example](InstanceOccurrence).
154 pub fn inclusion_site(&self) -> Option<&RelationshipSlot> {
155 self.inclusion_site.as_ref()
156 }
157}
158
159/// Maps one compact live method index back to its authored declaration.
160///
161/// Private output writers have authored index zero. Source specialization leaves
162/// gaps in authored indices without changing compact runtime indices.
163///
164/// # Examples
165/// See [`Instance::method_origin`].
166#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct MethodOrigin {
168 /// Identifies the occurrence containing the authored relationship.
169 pub occurrence: OccurrenceId,
170 /// Identifies the relationship in that occurrence's local recipe.
171 pub relationship: RelationshipSlot,
172 /// Identifies the original method position before source specialization.
173 pub authored_index: usize,
174}
175
176/// Maps one live relationship to its occurrence, local slot and surviving method indices.
177#[derive(Debug)]
178pub(super) struct RelationshipOrigin {
179 pub(super) occurrence: usize,
180 pub(super) slot: usize,
181 pub(super) methods: Range<usize>,
182}
183
184impl Instance {
185 /// Enumerates live methods and their authored origins in expansion order.
186 ///
187 /// Tuple fields are relationship identity, compact method index, and authored origin.
188 /// This avoids repeated linear searches when retaining all provenance.
189 /// - Complexity: O(r + m) time for r relationships and m surviving methods;
190 /// O(1) additional space, excluding collected results.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// let template = adam_rs::TemplateBuilder::new().finish()?;
196 /// let instance = template.instantiate(&mut adam_rs::Sheet::new(), &[])?;
197 /// assert_eq!(instance.method_origins().count(), 0);
198 /// # Ok::<(), adam_rs::TemplateError>(())
199 /// ```
200 pub fn method_origins(
201 &self,
202 ) -> impl Iterator<Item = (RelationshipId, usize, MethodOrigin)> + '_ {
203 self.relationships
204 .iter()
205 .zip(&self.relationship_origins)
206 .flat_map(move |(&relationship, origin)| {
207 let record = &self.occurrences[origin.occurrence];
208 self.authored_methods[origin.methods.clone()]
209 .iter()
210 .enumerate()
211 .map(move |(live, &authored_index)| {
212 (
213 relationship,
214 live,
215 MethodOrigin {
216 occurrence: record.id.clone(),
217 relationship: RelationshipSlot {
218 owner: record.template.0.clone(),
219 index: origin.slot,
220 },
221 authored_index,
222 },
223 )
224 })
225 })
226 }
227}
228
229impl Instance {
230 /// Borrows occurrences in depth-first inclusion order, beginning with the root.
231 ///
232 /// Filtering by immediate parent yields children in authored inclusion order.
233 ///
234 /// # Examples
235 ///
236 /// See the [occurrence example](InstanceOccurrence).
237 pub fn occurrences(&self) -> &[InstanceOccurrence] {
238 &self.occurrences
239 }
240
241 /// Returns an occurrence only if its identity belongs to this expansion.
242 ///
243 /// # Examples
244 ///
245 /// See the [occurrence example](InstanceOccurrence).
246 pub fn occurrence(&self, id: &OccurrenceId) -> Option<&InstanceOccurrence> {
247 if !Rc::ptr_eq(&self._identity, &id.owner) {
248 return None;
249 }
250 self.occurrences.get(id.index)
251 }
252
253 /// Maps an owner-checked local cell to its live identity within an occurrence.
254 ///
255 /// Returns `None` for a foreign occurrence or recipe handle. Bound formals
256 /// retain the caller's cell identity.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use adam_rs::{TemplateBuilder, Sheet};
262 /// let mut builder = TemplateBuilder::new();
263 /// let slot = builder.cell("private", || Ok(1i32))?;
264 /// let instance = builder.finish()?.instantiate(&mut Sheet::new(), &[])?;
265 /// assert_eq!(instance.cell(instance.occurrences()[0].id(), &slot), Some(instance.cells()[0]));
266 /// # Ok::<(), adam_rs::TemplateError>(())
267 /// ```
268 pub fn cell(&self, occurrence: &OccurrenceId, slot: &CellSlot) -> Option<CellId> {
269 let record = self.occurrence(occurrence)?;
270 if !Rc::ptr_eq(&record.template.0, &slot.owner) {
271 return None;
272 }
273 self.local_cells[record.cells.clone()]
274 .get(slot.index)
275 .copied()
276 }
277
278 /// Borrows a local relationship's live members, preserving separate runtime identities.
279 ///
280 /// Inclusion groups contain their full transitive expansion, possibly empty.
281 /// Returns `None` for foreign occurrence or recipe handles.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// use adam_rs::{TemplateBuilder, Sheet};
287 /// use std::rc::Rc;
288 /// let child = Rc::new(TemplateBuilder::new().finish()?);
289 /// let mut builder = TemplateBuilder::new();
290 /// let included = builder.include(child, &[])?;
291 /// let instance = builder.finish()?.instantiate(&mut Sheet::new(), &[])?;
292 /// assert_eq!(instance.relationship_group(instance.occurrences()[0].id(),
293 /// &included.relationships()[0]), Some(&[][..]));
294 /// # Ok::<(), adam_rs::TemplateError>(())
295 /// ```
296 pub fn relationship_group(
297 &self,
298 occurrence: &OccurrenceId,
299 slot: &RelationshipSlot,
300 ) -> Option<&[RelationshipId]> {
301 let record = self.occurrence(occurrence)?;
302 if !Rc::ptr_eq(&record.template.0, &slot.owner) {
303 return None;
304 }
305 let range = self.local_groups[record.groups.clone()].get(slot.index)?;
306 Some(&self.relationships[range.clone()])
307 }
308
309 /// Returns the authored origin of a compact live method in this expansion.
310 ///
311 /// Returns `None` if the relationship is absent or the method index is out of range.
312 /// - Complexity: O(r) time for r live relationships.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// use adam_rs::{TemplateBuilder, MethodRecipe, Method, Sheet};
318 /// let mut builder = TemplateBuilder::new();
319 /// let a = builder.cell("a", || Ok(1i32))?;
320 /// let b = builder.cell("b", || Ok(0i32))?;
321 /// builder.relationship(vec![MethodRecipe::new(&[a], &[b], |i, o| {
322 /// Ok(Method::from_fn_1_1(i[0], o[0], |x: &i32| Ok(*x)))
323 /// })?])?;
324 /// let instance = builder.finish()?.instantiate(&mut Sheet::new(), &[])?;
325 /// let origin = instance.method_origin(instance.relationships()[0], 0).unwrap();
326 /// assert_eq!(origin.authored_index, 0);
327 /// # Ok::<(), adam_rs::TemplateError>(())
328 /// ```
329 pub fn method_origin(
330 &self,
331 relationship: RelationshipId,
332 live_method: usize,
333 ) -> Option<MethodOrigin> {
334 let index = self
335 .relationships
336 .iter()
337 .position(|id| *id == relationship)?;
338 let origin = &self.relationship_origins[index];
339 let authored_index = *self.authored_methods[origin.methods.clone()].get(live_method)?;
340 let record = &self.occurrences[origin.occurrence];
341 Some(MethodOrigin {
342 occurrence: record.id.clone(),
343 relationship: RelationshipSlot {
344 owner: record.template.0.clone(),
345 index: origin.slot,
346 },
347 authored_index,
348 })
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::{Method, MethodRecipe, Sheet, TemplateBuilder};
356 use std::collections::HashSet;
357
358 /// Preserves clone/hash identity laws and rejects foreign owners and out-of-range mappings.
359 #[test]
360 fn identity_and_mapping_boundaries() {
361 let mut builder = TemplateBuilder::new();
362 let input = builder.cell("input", || Ok(3_i32)).unwrap();
363 let output = builder.cell("output", || Ok(0_i32)).unwrap();
364 let relationship = builder
365 .relationship(vec![
366 MethodRecipe::new(
367 std::slice::from_ref(&input),
368 std::slice::from_ref(&output),
369 |i, o| Ok(Method::from_fn_1_1(i[0], o[0], |v: &i32| Ok(*v))),
370 )
371 .unwrap(),
372 ])
373 .unwrap();
374 let template = builder.finish().unwrap();
375 let other = TemplateBuilder::new().finish().unwrap();
376 assert_eq!(
377 HashSet::from([template.id(), template.id().clone(), other.id()]).len(),
378 2
379 );
380 let mut sheet = Sheet::new();
381 let instance = template.instantiate(&mut sheet, &[]).unwrap();
382 let foreign = other.instantiate(&mut sheet, &[]).unwrap();
383 let root = &instance.occurrences()[0];
384 assert_eq!(root.template(), &template.id());
385 assert!(root.parent().is_none());
386 assert!(root.inclusion_site().is_none());
387 assert!(root.conditionals().is_empty());
388 assert_eq!(
389 HashSet::from([
390 root.id().clone(),
391 root.id().clone(),
392 foreign.occurrences()[0].id().clone()
393 ])
394 .len(),
395 2
396 );
397 assert!(instance.occurrence(foreign.occurrences()[0].id()).is_none());
398 let invalid = OccurrenceId {
399 owner: root.id.owner.clone(),
400 index: 1,
401 };
402 assert!(instance.occurrence(&invalid).is_none());
403 assert_eq!(instance.cell(root.id(), &input), Some(instance.cells()[0]));
404 assert_eq!(instance.cell(root.id(), &output), Some(instance.cells()[1]));
405 let invalid_slot = CellSlot {
406 owner: template.owner.clone(),
407 index: 2,
408 };
409 assert!(instance.cell(root.id(), &invalid_slot).is_none());
410 let foreign_slot = CellSlot {
411 owner: other.owner.clone(),
412 index: 0,
413 };
414 assert!(instance.cell(root.id(), &foreign_slot).is_none());
415 assert_eq!(
416 instance.relationship_group(root.id(), &relationship),
417 Some(instance.relationships())
418 );
419 let invalid_group = RelationshipSlot {
420 owner: template.owner.clone(),
421 index: 1,
422 };
423 assert!(
424 instance
425 .relationship_group(root.id(), &invalid_group)
426 .is_none()
427 );
428 let foreign_group = RelationshipSlot {
429 owner: other.owner.clone(),
430 index: 0,
431 };
432 assert!(
433 instance
434 .relationship_group(root.id(), &foreign_group)
435 .is_none()
436 );
437 for (rel, live, origin) in instance.method_origins() {
438 assert_eq!(instance.method_origin(rel, live), Some(origin));
439 assert!(instance.method_origin(rel, live + 1).is_none());
440 }
441 assert!(
442 instance
443 .method_origin(RelationshipId::default(), 0)
444 .is_none()
445 );
446 assert!(foreign.cells().is_empty());
447 assert!(foreign.relationships().is_empty());
448 assert_eq!(foreign.method_origins().count(), 0);
449 }
450}