Skip to main content

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}