1use 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 static SOURCE_PREFLIGHT_VISITS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
19}
20
21fn 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
33fn 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
47pub(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
58pub(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
87struct SourceFrame<'a> {
89 recipe: &'a SheetTemplate,
90 sources: Vec<bool>,
91 next: usize,
92 incoming: Option<RelationshipSlot>,
93}
94
95struct 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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}