Skip to main content

adam_rs/template/
instantiate.rs

1//! Checked binding and expansion through existing native graph mutators.
2
3use std::any::TypeId;
4use std::collections::{HashMap, HashSet};
5use std::ops::Range;
6use std::rc::Rc;
7
8use super::provenance::RelationshipOrigin;
9use super::{
10    CellSlot, Instance, InstanceOccurrence, Item, MethodRecipe, OccurrenceId, RelationshipRecipe,
11    RelationshipSlot, SheetTemplate, TemplateError,
12};
13use crate::{CellId, CellKind, Method, RelationshipId, Sheet};
14
15#[cfg(test)]
16thread_local! {
17    /// Counts entered source-check frames to verify the specialization complexity bound.
18    static SOURCE_PREFLIGHT_VISITS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
19}
20
21/// Allocates initialized traversal storage without infallible reservation.
22///
23/// - Complexity: O(n) time and space for n elements.
24fn storage<T: Clone>(count: usize, value: T) -> Result<Vec<T>, TemplateError> {
25    let mut result = Vec::new();
26    result
27        .try_reserve(count)
28        .map_err(|_| TemplateError::Capacity)?;
29    result.resize(count, value);
30    Ok(result)
31}
32
33/// Maps declared positional edges to live IDs.
34///
35/// - Precondition: each slot belongs to the mapped recipe and indexes `cells`.
36/// - Complexity: O(e) time and space for e edges.
37fn edges(slots: &[CellSlot], cells: &[CellId]) -> Result<Vec<CellId>, TemplateError> {
38    debug_assert!(slots.iter().all(|slot| slot.index < cells.len()));
39    let mut result = Vec::new();
40    result
41        .try_reserve(slots.len())
42        .map_err(|_| TemplateError::Capacity)?;
43    result.extend(slots.iter().map(|s| cells[s.index]));
44    Ok(result)
45}
46
47/// Checks a complete positional signature against registered concrete types.
48///
49/// - Complexity: O(e) time for e edges.
50pub(super) fn signature_matches(sheet: &Sheet, ids: &[CellId], types: &[TypeId]) -> bool {
51    ids.len() == types.len()
52        && ids
53            .iter()
54            .zip(types)
55            .all(|(id, ty)| sheet.cell_type_id(*id) == Some(*ty))
56}
57
58/// Expands group ranges into distinct live relationship IDs in authored group order.
59///
60/// - Precondition: each slot belongs to the mapped recipe and indexes `groups`;
61///   every selected group range indexes `all`.
62/// - Complexity: O(g + r) time and O(r) space for g groups and r members.
63pub(super) fn group_members(
64    slots: &[RelationshipSlot],
65    groups: &[Range<usize>],
66    all: &[RelationshipId],
67) -> Result<Vec<RelationshipId>, TemplateError> {
68    debug_assert!(slots.iter().all(|slot| {
69        groups
70            .get(slot.index)
71            .is_some_and(|group| group.start <= group.end && group.end <= all.len())
72    }));
73    let count = slots
74        .iter()
75        .try_fold(0usize, |n, slot| n.checked_add(groups[slot.index].len()))
76        .ok_or(TemplateError::Capacity)?;
77    let mut result = Vec::new();
78    result
79        .try_reserve(count)
80        .map_err(|_| TemplateError::Capacity)?;
81    for slot in slots {
82        result.extend_from_slice(&all[groups[slot.index].clone()]);
83    }
84    Ok(result)
85}
86
87/// Holds one active occurrence during non-recursive source preflight.
88struct SourceFrame<'a> {
89    recipe: &'a SheetTemplate,
90    sources: Vec<bool>,
91    next: usize,
92    incoming: Option<RelationshipSlot>,
93}
94
95/// Holds local live mappings while replaying one occurrence in declaration order.
96///
97/// `next` separates entered items from pending items. Every entered cell has a live
98/// handle; unentered private cells retain the sentinel until construction. Completed
99/// group ranges refer to the instance's flattened relationship storage, including
100/// each inclusion's contiguous transitive members.
101struct LiveFrame<'a> {
102    recipe: &'a SheetTemplate,
103    cells: Vec<CellId>,
104    groups: Vec<Range<usize>>,
105    next: usize,
106    incoming: Option<usize>,
107    occurrence: usize,
108}
109
110impl<'a> LiveFrame<'a> {
111    /// Allocates one occurrence's local mappings using fallible reservation.
112    ///
113    /// - Complexity: O(c + r) time and space for local cells c and groups r.
114    fn new(
115        recipe: &'a SheetTemplate,
116        incoming: Option<usize>,
117        occurrence: usize,
118    ) -> Result<Self, TemplateError> {
119        Ok(Self {
120            recipe,
121            cells: storage(recipe.cells.len(), CellId::default())?,
122            groups: storage(recipe.relationships.len(), 0..0)?,
123            next: 0,
124            incoming,
125            occurrence,
126        })
127    }
128
129    /// Retains the current conditional's local identity and active inclusion chain.
130    ///
131    /// - Precondition: The most recently entered item is a conditional.
132    /// - Complexity: O(n + h) time and O(h) space for entered local items n and depth h.
133    fn conditional_error(&self, source: TemplateError, instance: &Instance) -> TemplateError {
134        debug_assert!(matches!(
135            self.recipe.items.get(self.next - 1),
136            Some(Item::Conditional(_))
137        ));
138        let conditional = self.recipe.items[..self.next - 1]
139            .iter()
140            .filter(|item| matches!(item, Item::Conditional(_)))
141            .count();
142        TemplateError::ConditionalConstruction {
143            template: self.recipe.id(),
144            conditional,
145            inclusions: self.inclusions(instance),
146            source: Box::new(source),
147        }
148    }
149
150    /// Retains only the current occurrence's active root-to-child inclusion identities.
151    ///
152    /// - Complexity: O(h) time and space for ancestor depth h.
153    fn inclusions(&self, instance: &Instance) -> Vec<RelationshipSlot> {
154        let mut inclusions = std::iter::successors(Some(self.occurrence), |&index| {
155            instance.occurrences[index]
156                .parent
157                .as_ref()
158                .map(|parent| parent.index)
159        })
160        .filter_map(|index| instance.occurrences[index].inclusion_site.clone())
161        .collect::<Vec<_>>();
162        inclusions.reverse();
163        inclusions
164    }
165
166    /// Wraps an original construction failure at its exact authored boundary.
167    ///
168    /// - Complexity: O(h) time and space for ancestor depth h.
169    fn construction_error(
170        &self,
171        source: TemplateError,
172        site: super::ConstructionSite,
173        instance: &Instance,
174    ) -> TemplateError {
175        TemplateError::Construction {
176            template: self.recipe.id(),
177            site,
178            inclusions: self.inclusions(instance),
179            source: Box::new(source),
180        }
181    }
182}
183
184impl SheetTemplate {
185    /// Expands this recipe into `destination`, preserving bound-cell identities and values.
186    ///
187    /// Binding, declared structure, expanded sizes, result reservation and empty
188    /// specialized relationships are checked before mutation or factory calls, including
189    /// relationships in inactive branches. A source output removes its entire method.
190    /// Every authored method factory is checked against its declared edges/types
191    /// before pruning or registration, even when that method writes a source.
192    /// Out-cell arguments are unsupported.
193    /// Later factory, allocation or native failures may leave partial structure; the destination
194    /// remains safely discardable. Factories run only here, never at propagation.
195    ///
196    /// # Errors
197    ///
198    /// Returns binding errors for unknown, missing, repeated, aliased, invalid,
199    /// out-kind, or wrongly typed actuals. Returns [`TemplateError::Construction`]
200    /// for initializer, method, policy, relationship and empty-specialization failures,
201    /// retaining the exact definition, authored site and actual inclusion chain.
202    /// Its source preserves the original [`TemplateError::Factory`],
203    /// [`TemplateError::InvalidDefinition`], [`TemplateError::Runtime`] or
204    /// [`TemplateError::EmptyRelationship`]. Returns [`TemplateError::Capacity`]
205    /// without a declaration site on checked-size overflow or failed reservation.
206    /// A conditional construction failure returns [`TemplateError::ConditionalConstruction`],
207    /// retaining its definition, registration ordinal and actual root-to-child inclusion
208    /// sites. Its source is the original [`TemplateError::ConditionalFactory`],
209    /// [`TemplateError::InvalidDefinition`], [`TemplateError::Capacity`] or native
210    /// construction error.
211    ///
212    /// - Complexity: Expected O(d + c + b + Σ(m² × e)) time and O(d + c + b + e + r + h)
213    ///   additional space for expanded declarations d, cells c, bindings b,
214    ///   relationships r, depth h, and each relationship's m methods of at most e
215    ///   edges, excluding factory costs.
216    ///
217    /// # Examples
218    ///
219    /// See the [module example](super).
220    pub fn instantiate(
221        &self,
222        destination: &mut Sheet,
223        bindings: &[(&str, CellId)],
224    ) -> Result<Instance, TemplateError> {
225        let bound = self.bind(bindings, |name, actual| {
226            let type_id =
227                destination
228                    .cell_type_id(*actual)
229                    .ok_or_else(|| TemplateError::InvalidActual {
230                        name: name.into(),
231                        actual: *actual,
232                    })?;
233            if destination.cell_kind(*actual) == Some(CellKind::Out) {
234                return Err(TemplateError::OutArgument {
235                    name: name.into(),
236                    actual: *actual,
237                });
238            }
239            Ok(type_id)
240        })?;
241        let size = self.expanded_size.ok_or(TemplateError::Capacity)?;
242        let mut instance = Instance {
243            _identity: Rc::new(()),
244            cells: Vec::new(),
245            relationships: Vec::new(),
246            occurrences: Vec::new(),
247            local_cells: Vec::new(),
248            local_groups: Vec::new(),
249            relationship_origins: Vec::new(),
250            authored_methods: Vec::new(),
251        };
252        instance
253            .cells
254            .try_reserve(size)
255            .map_err(|_| TemplateError::Capacity)?;
256        instance
257            .relationships
258            .try_reserve(size)
259            .map_err(|_| TemplateError::Capacity)?;
260        instance
261            .occurrences
262            .try_reserve(size)
263            .map_err(|_| TemplateError::Capacity)?;
264        instance
265            .local_cells
266            .try_reserve(size)
267            .map_err(|_| TemplateError::Capacity)?;
268        instance
269            .local_groups
270            .try_reserve(size)
271            .map_err(|_| TemplateError::Capacity)?;
272        instance
273            .relationship_origins
274            .try_reserve(size)
275            .map_err(|_| TemplateError::Capacity)?;
276        instance
277            .authored_methods
278            .try_reserve(size)
279            .map_err(|_| TemplateError::Capacity)?;
280        self.preflight_sources(destination, &bound)?;
281        self.expand(destination, &bound, &mut instance)?;
282        Ok(instance)
283    }
284
285    /// Rejects every source-specialized empty constituent without invoking factories.
286    ///
287    /// Source status flows through checked child bindings; private sources remain fixed.
288    /// Cached sizes reject oversized DAGs before this traversal.
289    ///
290    /// - Complexity: expected O(w) time and O(k + h) space, where w counts local
291    ///   cells, relationships, method outputs and child masks across distinct
292    ///   (recipe, complete source mask) specializations; k counts specializations
293    ///   and their mask cells, and h is inclusion depth. Distinct masks can still
294    ///   grow exponentially.
295    fn preflight_sources(
296        &self,
297        destination: &Sheet,
298        bound: &HashMap<usize, CellId>,
299    ) -> Result<(), TemplateError> {
300        let mut sources = storage(self.cells.len(), false)?;
301        for (i, cell) in self.cells.iter().enumerate() {
302            sources[i] = cell.kind == CellKind::Source
303                || bound
304                    .get(&i)
305                    .is_some_and(|id| destination.cell_kind(*id) == Some(CellKind::Source));
306        }
307        self.preflight_source_mask(sources)
308    }
309
310    /// Checks complete named formal source statuses without constructing a live sheet.
311    ///
312    /// Uses the same whole-method pruning preflight as [`Self::instantiate`].
313    /// No initializer, method, policy, or conditional factory executes.
314    ///
315    /// # Errors
316    /// Rejects missing, unknown or repeated names, capacity overflow, or a constituent
317    /// relationship left without a method, including inactive branches.
318    /// - Complexity: expected O(bc + w) time and O(k + h) space for b bindings,
319    ///   c root cells, and local checking work w across distinct (recipe, complete
320    ///   source mask) specializations. k counts specializations and their mask
321    ///   cells, and h is inclusion depth. Distinct masks can still grow exponentially.
322    ///
323    /// # Examples
324    /// ```
325    /// let mut builder = adam_rs::TemplateBuilder::new();
326    /// builder.parameter::<i32>("x")?;
327    /// builder.finish()?.check_source_bindings(&[("x", true)])?;
328    /// # Ok::<(), adam_rs::TemplateError>(())
329    /// ```
330    pub fn check_source_bindings(&self, bindings: &[(&str, bool)]) -> Result<(), TemplateError> {
331        self.expanded_size.ok_or(TemplateError::Capacity)?;
332        let mut sources = storage(self.cells.len(), false)?;
333        let mut seen = HashSet::new();
334        for &(name, source) in bindings {
335            let Some(index) = self
336                .cells
337                .iter()
338                .position(|cell| cell.make.is_none() && cell.name == name)
339            else {
340                return Err(TemplateError::InvalidDefinition {
341                    detail: "unknown source-binding parameter",
342                    slot: None,
343                });
344            };
345            if !seen.insert(index) {
346                return Err(TemplateError::InvalidDefinition {
347                    detail: "repeated source-binding parameter",
348                    slot: None,
349                });
350            }
351            sources[index] = source;
352        }
353        if seen.len() != self.parameter_names().count() {
354            return Err(TemplateError::InvalidDefinition {
355                detail: "missing source-binding parameter",
356                slot: None,
357            });
358        }
359        for (index, cell) in self.cells.iter().enumerate() {
360            sources[index] |= cell.kind == CellKind::Source;
361        }
362        self.preflight_source_mask(sources)
363    }
364
365    /// Checks each distinct recipe and complete source mask once by iterative DFS.
366    ///
367    /// - Precondition: The mask covers this recipe's local cells.
368    /// # Errors
369    /// Returns capacity failure or an emptied constituent relationship.
370    /// Successful completions alone enter the cache; failures retain their active
371    /// inclusion ancestry. Live expansion remains occurrence-specific.
372    ///
373    /// - Complexity: expected O(w) time and O(k + h) space, where w counts local
374    ///   cells, relationships, method outputs and child masks across distinct
375    ///   specializations, k counts specializations and their mask cells, and h is
376    ///   inclusion depth.
377    ///   Distinct complete masks can still grow exponentially.
378    fn preflight_source_mask(&self, sources: Vec<bool>) -> Result<(), TemplateError> {
379        debug_assert_eq!(sources.len(), self.cells.len());
380        let mut complete = HashSet::new();
381        let mut stack = Vec::new();
382        stack
383            .try_reserve(self.depth.ok_or(TemplateError::Capacity)?)
384            .map_err(|_| TemplateError::Capacity)?;
385        stack.push(SourceFrame {
386            recipe: self,
387            sources,
388            next: 0,
389            incoming: None,
390        });
391        while let Some(frame) = stack.last_mut() {
392            #[cfg(test)]
393            if frame.next == 0 {
394                SOURCE_PREFLIGHT_VISITS.with(|visits| visits.set(visits.get() + 1));
395            }
396            let Some(relationship) = frame.recipe.relationships.get(frame.next) else {
397                let frame = stack.pop().expect("active source frame");
398                complete
399                    .try_reserve(1)
400                    .map_err(|_| TemplateError::Capacity)?;
401                complete.insert((frame.recipe.id(), frame.sources));
402                continue;
403            };
404            frame.next += 1;
405            match relationship {
406                RelationshipRecipe::Methods(methods) => {
407                    if !methods
408                        .iter()
409                        .any(|m| m.outputs.iter().all(|s| !frame.sources[s.index]))
410                    {
411                        let template = frame.recipe.id();
412                        let site = super::ConstructionSite::Relationship(RelationshipSlot {
413                            owner: frame.recipe.owner.clone(),
414                            index: frame.next - 1,
415                        });
416                        return Err(TemplateError::Construction {
417                            template,
418                            site,
419                            inclusions: stack
420                                .iter()
421                                .filter_map(|frame| frame.incoming.clone())
422                                .collect(),
423                            source: Box::new(TemplateError::EmptyRelationship),
424                        });
425                    }
426                }
427                RelationshipRecipe::Out(_) => {}
428                RelationshipRecipe::Include { child, bindings } => {
429                    let mut sources = storage(child.cells.len(), false)?;
430                    for (i, cell) in child.cells.iter().enumerate() {
431                        sources[i] = cell.kind == CellKind::Source
432                            || bindings.get(&i).is_some_and(|s| frame.sources[s.index]);
433                    }
434                    let key = (child.id(), sources);
435                    if complete.contains(&key) {
436                        continue;
437                    }
438                    let incoming = Some(RelationshipSlot {
439                        owner: frame.recipe.owner.clone(),
440                        index: frame.next - 1,
441                    });
442                    stack.push(SourceFrame {
443                        recipe: child,
444                        sources: key.1,
445                        next: 0,
446                        incoming,
447                    });
448                }
449            }
450        }
451        Ok(())
452    }
453
454    /// Replays ordered registration using an explicit stack and contiguous group ranges.
455    ///
456    /// - Precondition: Source and binding preflight succeeded and instance storage is reserved.
457    /// - Complexity: O(d + c + r + e + Σ(m² × e)) time and O(d + c + r + h + e) space
458    ///   for expanded declarations d, cells c, relationships r, edges e and depth h,
459    ///   excluding factories.
460    fn expand(
461        &self,
462        destination: &mut Sheet,
463        bound: &HashMap<usize, CellId>,
464        instance: &mut Instance,
465    ) -> Result<(), TemplateError> {
466        let mut root = LiveFrame::new(self, None, 0)?;
467        for (&index, &actual) in bound {
468            root.cells[index] = actual;
469        }
470        instance.cells.resize(self.cells.len(), CellId::default());
471        let mut stack = Vec::new();
472        stack
473            .try_reserve(self.depth.ok_or(TemplateError::Capacity)?)
474            .map_err(|_| TemplateError::Capacity)?;
475        instance.occurrences.push(InstanceOccurrence {
476            id: OccurrenceId {
477                owner: instance._identity.clone(),
478                index: 0,
479            },
480            template: self.id(),
481            parent: None,
482            inclusion_site: None,
483            cells: 0..0,
484            groups: 0..0,
485            conditionals: Vec::new(),
486        });
487        stack.push(root);
488        while let Some(frame) = stack.last_mut() {
489            let Some(item) = frame.recipe.items.get(frame.next) else {
490                let finished = stack.pop().unwrap();
491                let cells_start = instance.local_cells.len();
492                instance.local_cells.extend_from_slice(&finished.cells);
493                let groups_start = instance.local_groups.len();
494                instance.local_groups.extend_from_slice(&finished.groups);
495                let record = &mut instance.occurrences[finished.occurrence];
496                record.cells = cells_start..instance.local_cells.len();
497                record.groups = groups_start..instance.local_groups.len();
498                if let Some(index) = finished.incoming {
499                    stack.last_mut().unwrap().groups[index].end = instance.relationships.len();
500                } else {
501                    instance.cells[..finished.cells.len()].copy_from_slice(&finished.cells);
502                }
503                continue;
504            };
505            frame.next += 1;
506            match item {
507                Item::Cell(index) => {
508                    if let Some(make) = &frame.recipe.cells[*index].make {
509                        frame.cells[*index] = make(destination)
510                            .map_err(|source| TemplateError::Factory {
511                                source,
512                                slot: CellSlot {
513                                    owner: frame.recipe.owner.clone(),
514                                    index: *index,
515                                },
516                            })
517                            .map_err(|error| {
518                                frame.construction_error(
519                                    error,
520                                    super::ConstructionSite::Cell(CellSlot {
521                                        owner: frame.recipe.owner.clone(),
522                                        index: *index,
523                                    }),
524                                    instance,
525                                )
526                            })?;
527                        if frame.incoming.is_some() {
528                            instance.cells.push(frame.cells[*index]);
529                        }
530                    }
531                }
532                Item::Policy(policy) => {
533                    let ids = edges(&policy.inputs, &frame.cells)?;
534                    (policy.make)(destination, frame.cells[policy.cell.index], &ids).map_err(
535                        |error| {
536                            frame.construction_error(
537                                error,
538                                super::ConstructionSite::Policy {
539                                    cell: policy.cell.clone(),
540                                    index: frame.recipe.items[..frame.next - 1]
541                                        .iter()
542                                        .filter(|item| {
543                                            matches!(item, Item::Policy(previous)
544                                        if previous.cell == policy.cell)
545                                        })
546                                        .count(),
547                                },
548                                instance,
549                            )
550                        },
551                    )?;
552                }
553                Item::Conditional(conditional) => {
554                    let ids = edges(&conditional.inputs, &frame.cells)?;
555                    instance.occurrences[frame.occurrence]
556                        .conditionals
557                        .try_reserve(1)
558                        .map_err(|_| TemplateError::Capacity)?;
559                    let id = (conditional.make)(
560                        destination,
561                        &ids,
562                        &frame.groups,
563                        &instance.relationships,
564                    )
565                    .map_err(|error| frame.conditional_error(error, instance))?;
566                    instance.occurrences[frame.occurrence].conditionals.push(id);
567                }
568                Item::Relationship(index) => {
569                    let start = instance.relationships.len();
570                    frame.groups[*index] = start..start;
571                    match &frame.recipe.relationships[*index] {
572                        RelationshipRecipe::Methods(recipes) => {
573                            let methods_start = instance.authored_methods.len();
574                            let mut methods = Vec::new();
575                            methods
576                                .try_reserve(recipes.len())
577                                .map_err(|_| TemplateError::Capacity)?;
578                            // Validate all authored factories before removing entire source-writing methods.
579                            for (method_index, recipe) in recipes.iter().enumerate() {
580                                methods.push(
581                                    recipe.instantiate(destination, &frame.cells).map_err(
582                                        |error| {
583                                            frame.construction_error(
584                                                error,
585                                                super::ConstructionSite::Method {
586                                                    relationship: RelationshipSlot {
587                                                        owner: frame.recipe.owner.clone(),
588                                                        index: *index,
589                                                    },
590                                                    index: method_index,
591                                                },
592                                                instance,
593                                            )
594                                        },
595                                    )?,
596                                );
597                            }
598                            let mut authored_index = 0;
599                            methods.retain(|method| {
600                                let survives = method
601                                    .outputs
602                                    .iter()
603                                    .all(|id| destination.cell_kind(*id) != Some(CellKind::Source));
604                                if survives {
605                                    instance.authored_methods.push(authored_index);
606                                }
607                                authored_index += 1;
608                                survives
609                            });
610                            debug_assert!(!methods.is_empty());
611                            instance.relationships.push(
612                                destination.add_relationship(methods).map_err(|error| {
613                                    frame.construction_error(
614                                        TemplateError::Runtime(error),
615                                        super::ConstructionSite::Relationship(RelationshipSlot {
616                                            owner: frame.recipe.owner.clone(),
617                                            index: *index,
618                                        }),
619                                        instance,
620                                    )
621                                })?,
622                            );
623                            instance.relationship_origins.push(RelationshipOrigin {
624                                occurrence: frame.occurrence,
625                                slot: *index,
626                                methods: methods_start..instance.authored_methods.len(),
627                            });
628                            frame.groups[*index].end = instance.relationships.len();
629                        }
630                        RelationshipRecipe::Out(writer) => {
631                            let method =
632                                writer
633                                    .instantiate(destination, &frame.cells)
634                                    .map_err(|error| {
635                                        frame.construction_error(
636                                            error,
637                                            super::ConstructionSite::Method {
638                                                relationship: RelationshipSlot {
639                                                    owner: frame.recipe.owner.clone(),
640                                                    index: *index,
641                                                },
642                                                index: 0,
643                                            },
644                                            instance,
645                                        )
646                                    })?;
647                            let relationship =
648                                destination.add_out_writer(method).map_err(|error| {
649                                    frame.construction_error(
650                                        TemplateError::Runtime(error),
651                                        super::ConstructionSite::Relationship(RelationshipSlot {
652                                            owner: frame.recipe.owner.clone(),
653                                            index: *index,
654                                        }),
655                                        instance,
656                                    )
657                                })?;
658                            instance.relationships.push(relationship);
659                            let methods_start = instance.authored_methods.len();
660                            instance.authored_methods.push(0);
661                            instance.relationship_origins.push(RelationshipOrigin {
662                                occurrence: frame.occurrence,
663                                slot: *index,
664                                methods: methods_start..instance.authored_methods.len(),
665                            });
666                            frame.groups[*index].end = instance.relationships.len();
667                        }
668                        RelationshipRecipe::Include { child, bindings } => {
669                            let occurrence = instance.occurrences.len();
670                            let mut next = LiveFrame::new(child, Some(*index), occurrence)?;
671                            for (i, slot) in bindings {
672                                next.cells[*i] = frame.cells[slot.index];
673                            }
674                            instance.occurrences.push(InstanceOccurrence {
675                                id: OccurrenceId {
676                                    owner: instance._identity.clone(),
677                                    index: occurrence,
678                                },
679                                template: child.id(),
680                                parent: Some(instance.occurrences[frame.occurrence].id.clone()),
681                                inclusion_site: Some(RelationshipSlot {
682                                    owner: frame.recipe.owner.clone(),
683                                    index: *index,
684                                }),
685                                cells: 0..0,
686                                groups: 0..0,
687                                conditionals: Vec::new(),
688                            });
689                            stack.push(next);
690                        }
691                    }
692                }
693            }
694        }
695        Ok(())
696    }
697}
698
699impl MethodRecipe {
700    /// Creates and verifies one fresh method against the recipe's positional edges and types.
701    ///
702    /// - Precondition: `cells` maps every local edge to a registered live cell.
703    /// - Complexity: O(i + o) time and space for input and output counts i and o.
704    fn instantiate(&self, sheet: &Sheet, cells: &[CellId]) -> Result<Method, TemplateError> {
705        debug_assert!(
706            self.inputs
707                .iter()
708                .chain(&self.outputs)
709                .all(|s| s.index < cells.len())
710        );
711        let inputs = edges(&self.inputs, cells)?;
712        let outputs = edges(&self.outputs, cells)?;
713        let method = (self.make)(&inputs, &outputs).map_err(|source| TemplateError::Factory {
714            source,
715            slot: self.outputs[0].clone(),
716        })?;
717        if method.inputs != inputs
718            || method.outputs != outputs
719            || !signature_matches(sheet, &inputs, &method.input_types)
720            || !signature_matches(sheet, &outputs, &method.output_types)
721        {
722            return Err(TemplateError::InvalidDefinition {
723                detail: "method factory edges or concrete types differ from its recipe",
724                slot: Some(self.outputs[0].clone()),
725            });
726        }
727        Ok(method)
728    }
729}
730
731#[cfg(test)]
732mod tests {
733    use super::SOURCE_PREFLIGHT_VISITS;
734    use crate::TemplateBuilder;
735    use std::rc::Rc;
736
737    /// Validates complete positional signatures, including empty, foreign and mismatched inputs.
738    #[test]
739    fn signatures_check_arity_liveness_and_concrete_types() {
740        use super::{CellId, Sheet, TypeId, signature_matches};
741        let mut sheet = Sheet::new();
742        let number = sheet.add_cell(3_i32);
743        let flag = sheet.add_cell(true);
744        assert!(signature_matches(&sheet, &[], &[]));
745        assert!(signature_matches(
746            &sheet,
747            &[number, flag],
748            &[TypeId::of::<i32>(), TypeId::of::<bool>()]
749        ));
750        assert!(!signature_matches(&sheet, &[number], &[]));
751        assert!(!signature_matches(&sheet, &[], &[TypeId::of::<i32>()]));
752        assert!(!signature_matches(
753            &sheet,
754            &[number],
755            &[TypeId::of::<bool>()]
756        ));
757        assert!(!signature_matches(
758            &sheet,
759            &[CellId::default()],
760            &[TypeId::of::<i32>()]
761        ));
762    }
763
764    /// Maps live edges and complete inclusion ranges in the caller's requested order.
765    #[test]
766    fn local_storage_and_positional_mapping_preserve_order() {
767        use super::{
768            CellId, LiveFrame, Method, MethodRecipe, Sheet, TemplateError, edges, group_members,
769            storage,
770        };
771        assert_eq!(storage(0, 7_u8).unwrap(), []);
772        assert_eq!(storage(2, 7_u8).unwrap(), [7, 7]);
773        assert!(matches!(
774            storage(usize::MAX, 0_u8),
775            Err(TemplateError::Capacity)
776        ));
777        let mut builder = TemplateBuilder::new();
778        let a = builder.parameter::<i32>("a").unwrap();
779        let b = builder.parameter::<i32>("b").unwrap();
780        let group = builder
781            .relationship(vec![
782                MethodRecipe::new(
783                    std::slice::from_ref(&a),
784                    std::slice::from_ref(&b),
785                    |i, o| Ok(Method::from_fn_1_1(i[0], o[0], |v: &i32| Ok(*v))),
786                )
787                .unwrap(),
788            ])
789            .unwrap();
790        let template = builder.finish().unwrap();
791        let frame = LiveFrame::new(&template, None, 0).unwrap();
792        assert_eq!(frame.cells, [CellId::default(); 2]);
793        assert_eq!(frame.groups.as_slice(), std::slice::from_ref(&(0..0)));
794        assert_eq!(frame.next, 0);
795        let mut sheet = Sheet::new();
796        let live_a = sheet.add_cell(3_i32);
797        let live_b = sheet.add_cell(0_i32);
798        let instance = template
799            .instantiate(&mut sheet, &[("a", live_a), ("b", live_b)])
800            .unwrap();
801        assert_eq!(edges(&[b, a], &[live_a, live_b]).unwrap(), [live_b, live_a]);
802        assert_eq!(
803            group_members(
804                &[group],
805                std::slice::from_ref(&(0..1)),
806                instance.relationships()
807            )
808            .unwrap(),
809            instance.relationships()
810        );
811        assert!(group_members(&[], &[], &[]).unwrap().is_empty());
812    }
813
814    /// Checks work by distinct immutable specializations rather than expanded occurrences.
815    #[test]
816    fn source_preflight_checks_shared_dag_specializations_once() {
817        let depth = 12;
818        let mut template = Rc::new(TemplateBuilder::new().finish().unwrap());
819        for _ in 0..depth {
820            let mut builder = TemplateBuilder::new();
821            builder.include(template.clone(), &[]).unwrap();
822            builder.include(template, &[]).unwrap();
823            template = Rc::new(builder.finish().unwrap());
824        }
825        SOURCE_PREFLIGHT_VISITS.with(|visits| visits.set(0));
826        template.check_source_bindings(&[]).unwrap();
827        SOURCE_PREFLIGHT_VISITS.with(|visits| {
828            assert!(
829                visits.get() <= 2 * (depth + 1),
830                "visited {} frames",
831                visits.get()
832            );
833        });
834    }
835}