1use std::{any::TypeId, collections::HashSet};
6
7use cel_parser::{Expr, ExprSpan, OpLookup, compile_expr};
8use cel_runtime::DynSegment;
9
10use super::*;
11use crate::{
12 TypeRegistry,
13 cell_value::{CellValue, RawFrame},
14 type_registry::TypeShape,
15};
16
17pub fn check_module(
48 module: &ModulePath,
49 modules: &ModuleSet,
50 types: &TypeRegistry,
51 lookup: &OpLookup,
52) -> Vec<ModuleDiagnostic> {
53 if !modules.indices.contains_key(module) {
54 return vec![crate::composition::diagnostic(format!(
55 "unknown requested module `{module}`"
56 ))];
57 }
58 match analyze(modules, types, lookup) {
59 Ok(_) => Vec::new(),
60 Err(errors) => errors,
61 }
62}
63
64#[derive(Clone, Copy, PartialEq, Eq)]
66pub(super) enum Kind {
67 Parameter,
68 Cell,
69 Source,
70 Out,
71}
72
73pub(super) struct Cell<'a> {
75 pub name: &'a str,
76 pub shape: TypeShape,
77 pub kind: Kind,
78 pub label: SourceLabel,
79}
80
81pub(super) struct Expression<'a> {
83 pub syntax: &'a Expr,
84 pub inputs: Vec<usize>,
85 pub candidate: Option<usize>,
86 pub shape: TypeShape,
87 pub descriptor: cel_runtime::ValueType,
88 pub label: SourceLabel,
89}
90
91impl Expression<'_> {
92 #[allow(
99 dead_code,
100 reason = "retains the borrowed checked-plan inspection/emission seam"
101 )]
102 pub(super) fn emit(
103 &self,
104 cells: &[Cell<'_>],
105 types: &TypeRegistry,
106 lookup: &OpLookup,
107 ) -> Result<DynSegment, ModuleDiagnostic> {
108 debug_assert!(
109 self.inputs
110 .iter()
111 .chain(&self.candidate)
112 .all(|cell| *cell < cells.len())
113 );
114 Emission {
115 syntax: self.syntax,
116 inputs: self
117 .inputs
118 .iter()
119 .map(|id| (cells[*id].name, types.value_type(&cells[*id].shape))),
120 candidate: self.candidate.map(|id| types.value_type(&cells[id].shape)),
121 descriptor: &self.descriptor,
122 label: &self.label,
123 }
124 .emit(types, lookup)
125 }
126}
127
128pub(super) struct Emission<'a, I> {
130 pub syntax: &'a Expr,
131 pub inputs: I,
132 pub candidate: Option<cel_runtime::ValueType>,
133 pub descriptor: &'a cel_runtime::ValueType,
134 pub label: &'a SourceLabel,
135}
136
137impl<'a, I> Emission<'a, I>
138where
139 I: Clone + Iterator<Item = (&'a str, cel_runtime::ValueType)>,
140{
141 pub(super) fn emit(
148 self,
149 types: &TypeRegistry,
150 lookup: &OpLookup,
151 ) -> Result<DynSegment, ModuleDiagnostic> {
152 let mut segment = compile_expr(
153 self.syntax,
154 lookup,
155 &types.cel_type_resolver(),
156 &mut |name, segment, _| {
157 if name == "_"
158 && let Some(candidate) = &self.candidate
159 {
160 segment.push_argument(0, candidate.clone())?;
161 return Ok(true);
162 }
163 if let Some((index, (_, descriptor))) = self
164 .inputs
165 .clone()
166 .enumerate()
167 .find(|(_, (input, _))| *input == name)
168 {
169 segment
170 .push_argument(index + usize::from(self.candidate.is_some()), descriptor)?;
171 return Ok(true);
172 }
173 Ok(false)
174 },
175 )
176 .map_err(|error| failure(error, self.label.clone()))?;
177 segment
178 .annotate_top(self.descriptor.clone())
179 .map_err(|error| failure(error, self.label.clone()))?;
180 Ok(segment)
181 }
182}
183
184pub(super) struct Item<'a> {
186 pub parent: Option<usize>,
187 pub label: SourceLabel,
188 pub body: Body<'a>,
189}
190
191pub(super) enum Body<'a> {
193 Cell {
194 cell: usize,
195 initializer: Option<Expression<'a>>,
196 },
197 Out {
198 cell: usize,
199 writer: Expression<'a>,
200 },
201 Filter {
202 cell: usize,
203 expression: Expression<'a>,
204 range: bool,
205 },
206 Requirement {
207 cell: usize,
208 name: Option<&'a str>,
209 expression: Expression<'a>,
210 },
211 Relationship {
212 methods: std::ops::Range<usize>,
213 },
214 Method {
215 outputs: Vec<usize>,
216 destructure: bool,
217 expression: Expression<'a>,
218 },
219 Use {
220 target: usize,
221 bindings: Vec<usize>,
222 syntax: &'a ast::RelationshipUse,
223 },
224 Conditional {
225 expression: Expression<'a>,
226 },
227 Branch {
228 syntax: Option<&'a ast::ConditionalBranch>,
229 },
230}
231
232pub(super) struct DefinitionPlan<'a> {
234 pub cells: Vec<Cell<'a>>,
235 pub names: HashMap<&'a str, usize>,
236 pub formals: usize,
237 pub items: Vec<Item<'a>>,
238 pub native: Option<&'a TypedTemplate>,
239 pub component: bool,
240 pub label: SourceLabel,
241 pub ineligible: Option<SourceLabel>,
242}
243
244pub(super) struct Checked<'a> {
246 pub resolved: resolve::Resolved<'a>,
247 pub definitions: Vec<DefinitionPlan<'a>>,
248 pub types: &'a TypeRegistry,
249 pub lookup: &'a OpLookup,
250}
251
252pub(super) fn analyze<'a>(
258 modules: &'a ModuleSet,
259 types: &'a TypeRegistry,
260 lookup: &'a OpLookup,
261) -> Result<Checked<'a>, Vec<ModuleDiagnostic>> {
262 let resolved = resolve::resolve(modules)?;
263 let mut errors = Vec::new();
264 let mut definitions = Vec::new();
265 for &(module, local) in &resolved.definitions {
267 let definition = &modules.entries[module].definitions[local];
268 let mut plan = DefinitionPlan {
269 cells: Vec::new(),
270 names: HashMap::new(),
271 formals: 0,
272 items: Vec::new(),
273 native: None,
274 component: true,
275 label: definition.label().clone(),
276 ineligible: None,
277 };
278 match &definition.body {
279 DefinitionBody::Native(template) => {
280 plan.native = Some(template);
281 for (name, shape) in template.parameters() {
282 if let Err(error) = types.validate_shape(shape) {
283 errors.push(failure(error, plan.label.clone()));
284 }
285 plan.cells.push(Cell {
286 name,
287 shape: shape.clone(),
288 kind: Kind::Parameter,
289 label: plan.label.clone(),
290 });
291 }
292 if !template.is_relationship_only() {
293 plan.ineligible = Some(plan.label.clone());
294 }
295 }
296 DefinitionBody::Parsed { sheet, .. } => {
297 plan.component = sheet.parameters.is_some();
298 let owner = modules.entries[module]
299 .source
300 .as_ref()
301 .expect("parsed source");
302 for formal in sheet.parameters.iter().flatten() {
303 let site = label(owner, formal.name_span, "formal parameter");
304 if plan.cells.iter().any(|cell| cell.name == formal.name) {
305 errors.push(failure("duplicate parameter name", site));
306 continue;
307 }
308 match types.resolve(&formal.type_name) {
309 Ok(shape) => {
310 if let Err(error) = types
311 .checked_value_type(&shape)
312 .and_then(|ty| CellValue::validate(&ty))
313 {
314 errors.push(failure(error, site.clone()));
315 }
316 plan.cells.push(Cell {
317 name: &formal.name,
318 shape,
319 kind: Kind::Parameter,
320 label: site,
321 });
322 }
323 Err((error, _)) => errors.push(failure(error, site)),
324 }
325 }
326 }
327 }
328 plan.formals = plan.cells.len();
329 plan.names.extend(
330 plan.cells
331 .iter()
332 .enumerate()
333 .map(|(id, cell)| (cell.name, id)),
334 );
335 definitions.push(plan);
336 }
337 if !errors.is_empty() {
338 return Err(errors);
339 }
340 for &id in &resolved.order {
342 let (module, local) = resolved.definitions[id];
343 if let DefinitionBody::Parsed { sheet, .. } =
344 &modules.entries[module].definitions[local].body
345 {
346 let owner = modules.entries[module]
347 .source
348 .as_ref()
349 .expect("parsed source");
350 let mut checker = Checker {
351 types,
352 lookup,
353 owner,
354 definitions: &mut definitions,
355 id,
356 edge: 0,
357 resolved: &resolved,
358 };
359 for item in &sheet.items {
360 if let Err(error) = checker.item(item) {
361 errors.push(error);
362 break;
363 }
364 }
365 }
366 }
367 if !errors.is_empty() {
368 return Err(errors);
369 }
370 let checked = Checked {
371 resolved,
372 definitions,
373 types,
374 lookup,
375 };
376 checked.sources()?;
377 Ok(checked)
378}
379
380struct Checker<'a, 'b> {
382 types: &'a TypeRegistry,
383 lookup: &'a OpLookup,
384 owner: &'a SourceId,
385 definitions: &'b mut [DefinitionPlan<'a>],
386 id: usize,
387 edge: usize,
388 resolved: &'b resolve::Resolved<'a>,
389}
390
391fn failure(message: impl fmt::Display, site: SourceLabel) -> ModuleDiagnostic {
395 ModuleDiagnostic {
396 cause: None,
397 message: message.to_string(),
398 labels: vec![site],
399 }
400}
401
402fn contextual_error(
406 mut error: ModuleDiagnostic,
407 context: &str,
408 site: SourceLabel,
409) -> ModuleDiagnostic {
410 error.message = format!("{context}: {}", error.message);
411 error.labels.insert(0, site);
412 error
413}
414
415fn shape_mismatch(expected: &TypeShape, actual: &TypeShape, types: &TypeRegistry) -> String {
420 debug_assert_ne!(expected, actual);
421 if let (TypeShape::Tuple(expected), TypeShape::Tuple(actual)) = (expected, actual) {
422 if expected.len() != actual.len() {
423 return format!(
424 "tuple arity mismatch: expected {}, got {}",
425 expected.len(),
426 actual.len()
427 );
428 }
429 if let Some((expected, actual)) = expected.iter().zip(actual).find(|(a, b)| a != b) {
430 return shape_mismatch(expected, actual, types);
431 }
432 }
433 format!(
434 "type mismatch: expected `{}`, got `{}`",
435 types.display_name(expected),
436 types.display_name(actual)
437 )
438}
439
440fn first_invalid_method(edges: &[(&[usize], &[usize])]) -> Option<usize> {
446 debug_assert!(adam_rs::template::check_relationship_edges(edges).is_err());
447 if edges.is_empty() {
448 return None;
449 }
450 let mut lower = 1;
451 let mut upper = edges.len();
452 while lower < upper {
453 let middle = lower + (upper - lower) / 2;
454 if adam_rs::template::check_relationship_edges(&edges[..middle]).is_ok() {
455 lower = middle + 1;
456 } else {
457 upper = middle;
458 }
459 }
460 Some(lower - 1)
461}
462
463impl<'a> Checker<'a, '_> {
464 fn relationship_error(
472 &self,
473 relationship: &ast::RelationshipDecl,
474 edges: &[(&[usize], &[usize])],
475 message: &str,
476 ) -> ModuleDiagnostic {
477 let Some(index) = first_invalid_method(edges) else {
478 return failure(
479 message,
480 label(self.owner, relationship.span, "relationship"),
481 );
482 };
483 let mut error = failure(
484 format!("invalid relationship methods/outputs: {message}"),
485 label(self.owner, relationship.bindings[index].span, "method"),
486 );
487 let claimed: HashSet<_> = edges[index].1.iter().collect();
488 for (prior, &method) in edges[..index].iter().enumerate() {
489 let collision = matches!(
490 adam_rs::template::check_relationship_edges(&[method, edges[index]]),
491 Err("relationship output sets are equal or nested")
492 );
493 error.labels.push(label(
494 self.owner,
495 relationship.bindings[prior].span,
496 if collision {
497 "collides with this earlier method"
498 } else {
499 "earlier method declared here"
500 },
501 ));
502 if collision {
503 for &id in method.1.iter().filter(|id| claimed.contains(id)) {
504 let cell = &self.definitions[self.id].cells[id];
505 let mut site = cell.label.clone();
506 site.label = format!("output cell `{}` is claimed more than once", cell.name);
507 error.labels.push(site);
508 }
509 }
510 }
511 error
512 }
513
514 fn push(&mut self, parent: Option<usize>, span: ExprSpan, body: Body<'a>) -> usize {
518 let items = &mut self.definitions[self.id].items;
519 let ordinal = items.len();
520 items.push(Item {
521 parent,
522 label: label(self.owner, span, "body item"),
523 body,
524 });
525 ordinal
526 }
527
528 fn cell(&self, name: &str, span: ExprSpan) -> Result<usize, ModuleDiagnostic> {
534 self.definitions[self.id]
535 .names
536 .get(name)
537 .copied()
538 .ok_or_else(|| {
539 failure(
540 format!("undeclared cell `{name}`"),
541 label(self.owner, span, "cell reference"),
542 )
543 })
544 }
545
546 fn expression(
552 &self,
553 syntax: &'a Expr,
554 capture: bool,
555 candidate: Option<usize>,
556 context: Option<(&str, SourceLabel)>,
557 ) -> Result<(Expression<'a>, DynSegment, bool), ModuleDiagnostic> {
558 let definition = &self.definitions[self.id];
559 let cells = &definition.cells;
560 let mut inputs = Vec::new();
561 let mut used = false;
562 let site = label(self.owner, syntax.span(), "expression");
563 let segment = compile_expr(
564 syntax,
565 self.lookup,
566 &self.types.cel_type_resolver(),
567 &mut |name, segment, _| {
568 if name == "_"
569 && let Some(cell) = candidate
570 {
571 used = true;
572 segment.push_argument(0, self.types.checked_value_type(&cells[cell].shape)?)?;
573 return Ok(true);
574 }
575 if !capture {
576 anyhow::ensure!(
577 !definition.names.contains_key(name),
578 "cell-dependent initializers are unsupported"
579 );
580 return Ok(false);
581 }
582 let Some(&cell) = definition.names.get(name) else {
583 return Ok(false);
584 };
585 let index = if let Some(index) = inputs.iter().position(|input| *input == cell) {
586 index
587 } else {
588 inputs.push(cell);
589 inputs.len() - 1
590 };
591 segment.push_argument(
592 index + usize::from(candidate.is_some()),
593 self.types.checked_value_type(&cells[cell].shape)?,
594 )?;
595 Ok(true)
596 },
597 )
598 .map_err(|error| parse_error(self.owner, &error))?;
599 let ty = segment
600 .peek_stack_infos(1)
601 .first()
602 .ok_or_else(|| failure("expression produces no value", site.clone()))?
603 .value_type
604 .clone();
605 let shape = if candidate.is_some()
606 && let Some(id) = segment.peek_output_type_id()
607 && self.types.range_entry(id).is_some()
608 {
609 TypeShape::Named(id)
610 } else {
611 self.types.shape_of(&ty).map_err(|error| {
612 let error = failure(error, site.clone());
613 if let Some((context, site)) = context {
614 contextual_error(error, context, site)
615 } else {
616 error
617 }
618 })?
619 };
620 Ok((
621 Expression {
622 syntax,
623 inputs,
624 candidate,
625 shape,
626 descriptor: ty,
627 label: site,
628 },
629 segment,
630 used,
631 ))
632 }
633
634 fn frame(
640 &self,
641 expression: &Expression<'_>,
642 segment: DynSegment,
643 result: &TypeShape,
644 ) -> Result<(), ModuleDiagnostic> {
645 let cells = &self.definitions[self.id].cells;
646 let inputs: Vec<_> = expression
647 .candidate
648 .iter()
649 .chain(&expression.inputs)
650 .map(|id| cells[*id].shape.clone())
651 .collect();
652 let result = self
653 .types
654 .checked_value_type(result)
655 .map_err(|error| failure(error, expression.label.clone()))?;
656 RawFrame::new(segment, &inputs, &result, self.types)
657 .map(|_| ())
658 .map_err(|error| failure(error, expression.label.clone()))
659 }
660
661 fn declaration(
667 &mut self,
668 name: &'a str,
669 span: ExprSpan,
670 annotation: Option<&ast::TypeExpr>,
671 initializer: Option<&'a Expr>,
672 kind: Kind,
673 ) -> Result<usize, ModuleDiagnostic> {
674 let site = label(self.owner, span, "cell declaration");
675 let context = format!(
676 "{} `{name}`",
677 match kind {
678 Kind::Source => "source",
679 Kind::Out => "out",
680 _ => "cell",
681 }
682 );
683 if self.definitions[self.id].names.contains_key(name) {
684 return Err(failure(format!("duplicate cell `{name}`"), site));
685 }
686 let compiled = initializer
687 .map(|syntax| {
688 self.expression(
689 syntax,
690 kind == Kind::Out,
691 None,
692 Some((&context, site.clone())),
693 )
694 })
695 .transpose()?;
696 let shape = if let Some(annotation) = annotation {
697 self.types
698 .resolve(annotation)
699 .map_err(|(error, _)| failure(error, site.clone()))?
700 } else {
701 compiled
702 .as_ref()
703 .expect("parsed declaration has type or initializer")
704 .0
705 .shape
706 .clone()
707 };
708 let ty = self
709 .types
710 .checked_value_type(&shape)
711 .map_err(|error| failure(error, site.clone()))?;
712 if let Some((expression, _, _)) = &compiled
713 && expression.shape != shape
714 {
715 return Err(failure(
716 format!(
717 "{context}: initializer type mismatch: expected `{}`, got `{}`",
718 self.types.display_name(&shape),
719 self.types.display_name(&expression.shape)
720 ),
721 site.clone(),
722 ));
723 }
724 if compiled.is_none() || kind == Kind::Out {
726 self.types
727 .validate_default(&shape)
728 .map_err(|error| failure(error, site.clone()))?;
729 }
730 CellValue::validate(&ty).map_err(|error| failure(error, site.clone()))?;
731 let id = self.definitions[self.id].cells.len();
732 self.definitions[self.id].cells.push(Cell {
733 name,
734 shape: shape.clone(),
735 kind,
736 label: site.clone(),
737 });
738 self.definitions[self.id].names.insert(name, id);
739 self.definitions[self.id].ineligible.get_or_insert(site);
740 let body = if let Some((expression, segment, _)) = compiled {
741 self.frame(&expression, segment, &shape)?;
742 if kind == Kind::Out {
743 Body::Out {
744 cell: id,
745 writer: expression,
746 }
747 } else {
748 Body::Cell {
749 cell: id,
750 initializer: Some(expression),
751 }
752 }
753 } else {
754 Body::Cell {
755 cell: id,
756 initializer: None,
757 }
758 };
759 self.push(None, span, body);
760 Ok(id)
761 }
762
763 fn policies(
769 &mut self,
770 cell: usize,
771 filter: Option<&'a ast::CellFilter>,
772 requirements: Option<&'a ast::RequireBlock>,
773 ) -> Result<(), ModuleDiagnostic> {
774 let shape = self.definitions[self.id].cells[cell].shape.clone();
775 if let Some(filter) = filter {
776 let (expression, segment, used) =
777 self.expression(&filter.body, true, Some(cell), None)?;
778 if expression.inputs.contains(&cell) {
779 return Err(failure(
780 "filter cannot depend on its own cell",
781 expression.label,
782 ));
783 }
784 let range = segment
785 .peek_output_type_id()
786 .and_then(|id| self.types.range_entry(id));
787 let is_range = range.is_some();
788 if let Some(range) = range {
789 if range.element_type_id != crate::cell_value::cell_type_id(&shape) {
790 return Err(failure(
791 format!(
792 "filter range bounds must be `{}`",
793 self.types.display_name(&shape)
794 ),
795 expression.label,
796 ));
797 }
798 let result = segment.peek_stack_infos(1)[0].value_type.clone();
799 let cells = &self.definitions[self.id].cells;
800 let inputs: Vec<_> = expression
801 .candidate
802 .iter()
803 .chain(&expression.inputs)
804 .map(|id| cells[*id].shape.clone())
805 .collect();
806 RawFrame::new(segment, &inputs, &result, self.types)
807 .map_err(|error| failure(error, expression.label.clone()))?;
808 } else {
809 if !used {
810 return Err(failure("value filter must reference `_`", expression.label));
811 }
812 self.frame(&expression, segment, &shape)?;
813 }
814 self.push(
815 None,
816 filter.span,
817 Body::Filter {
818 cell,
819 expression,
820 range: is_range,
821 },
822 );
823 }
824 let mut names = HashSet::new();
825 for requirement in requirements
826 .into_iter()
827 .flat_map(|block| &block.requirements)
828 {
829 if let Some(name) = &requirement.name
830 && !names.insert(name)
831 {
832 return Err(failure(
833 format!("duplicate requirement `{name}`"),
834 label(
835 self.owner,
836 requirement.name_span.unwrap_or(requirement.span),
837 "requirement",
838 ),
839 ));
840 }
841 let (expression, segment, _) = self.expression(&requirement.body, true, None, None)?;
842 self.frame(
843 &expression,
844 segment,
845 &TypeShape::Named(TypeId::of::<bool>()),
846 )?;
847 self.push(
848 None,
849 requirement.span,
850 Body::Requirement {
851 cell,
852 name: requirement.name.as_deref(),
853 expression,
854 },
855 );
856 }
857 Ok(())
858 }
859
860 fn relationship(
866 &mut self,
867 relationship: &'a ast::RelationshipDecl,
868 parent: Option<usize>,
869 ) -> Result<(), ModuleDiagnostic> {
870 let ordinal = self.push(
871 parent,
872 relationship.span,
873 Body::Relationship { methods: 0..0 },
874 );
875 let mut edges = Vec::new();
876 for binding in &relationship.bindings {
877 let outputs = binding
878 .outputs
879 .iter()
880 .map(|(name, span)| self.cell(name, *span))
881 .collect::<Result<Vec<_>, _>>()?;
882 if outputs
883 .iter()
884 .any(|id| self.definitions[self.id].cells[*id].kind == Kind::Out)
885 {
886 return Err(failure(
887 "out cell already has its sole writer",
888 label(self.owner, binding.span, "method"),
889 ));
890 }
891 let (name, span) = &binding.outputs[0];
892 let context = format!("output `{name}`");
893 let site = label(self.owner, *span, "output");
894 let (expression, segment, _) =
895 self.expression(&binding.body, true, None, Some((&context, site.clone())))?;
896 let shapes: Vec<_> = outputs
897 .iter()
898 .map(|id| self.definitions[self.id].cells[*id].shape.clone())
899 .collect();
900 let result = if binding.destructure {
901 TypeShape::Tuple(shapes)
902 } else {
903 shapes[0].clone()
904 };
905 if result != expression.shape {
906 return Err(failure(
907 format!(
908 "{context}: {}",
909 shape_mismatch(&result, &expression.shape, self.types)
910 ),
911 site,
912 ));
913 }
914 self.frame(&expression, segment, &result)?;
915 edges.push((expression.inputs.clone(), outputs.clone()));
916 self.push(
917 Some(ordinal),
918 binding.span,
919 Body::Method {
920 outputs,
921 destructure: binding.destructure,
922 expression,
923 },
924 );
925 }
926 let views: Vec<_> = edges
927 .iter()
928 .map(|(inputs, outputs)| (inputs.as_slice(), outputs.as_slice()))
929 .collect();
930 adam_rs::template::check_relationship_edges(&views)
931 .map_err(|error| self.relationship_error(relationship, &views, error))?;
932 let end = self.definitions[self.id].items.len();
933 self.definitions[self.id].items[ordinal].body = Body::Relationship {
934 methods: ordinal + 1..end,
935 };
936 Ok(())
937 }
938
939 fn invocation(
945 &mut self,
946 invocation: &'a ast::RelationshipUse,
947 parent: Option<usize>,
948 ) -> Result<(), ModuleDiagnostic> {
949 let target = self.resolved.edges[self.id][self.edge].target;
950 self.edge += 1;
951 let child = &self.definitions[target];
952 let site = label(self.owner, invocation.span, "component use");
953 if parent.is_some()
954 && let Some(ineligible) = &child.ineligible
955 {
956 return Err(ModuleDiagnostic {
957 cause: None,
958 message: "conditional use requires a relationship-only component".into(),
959 labels: vec![site, ineligible.clone()],
960 });
961 }
962 let mut bindings = vec![None; child.formals];
963 let mut actuals = HashSet::new();
964 for argument in &invocation.arguments {
965 let formal = child.cells[..child.formals]
966 .iter()
967 .position(|cell| cell.name == argument.name)
968 .ok_or_else(|| {
969 failure(
970 format!("unknown parameter `{}`", argument.name),
971 label(self.owner, argument.name_span, "named argument"),
972 )
973 })?;
974 let actual = self.cell(&argument.cell, argument.cell_span)?;
975 if bindings[formal].replace(actual).is_some() || !actuals.insert(actual) {
976 return Err(failure("repeated parameter or aliased actual cell", site));
977 }
978 let cell = &self.definitions[self.id].cells[actual];
979 if cell.kind == Kind::Out {
980 return Err(failure("out-cell arguments are unsupported", site));
981 }
982 if cell.shape != child.cells[formal].shape {
983 return Err(ModuleDiagnostic {
984 cause: None,
985 message: "component argument type mismatch".into(),
986 labels: vec![
987 label(self.owner, argument.cell_span, "actual cell"),
988 child.cells[formal].label.clone(),
989 ],
990 });
991 }
992 }
993 let bindings = bindings
994 .into_iter()
995 .collect::<Option<Vec<_>>>()
996 .ok_or_else(|| failure("missing component parameter", site.clone()))?;
997 let ineligible = child.ineligible.clone();
998 self.push(
999 parent,
1000 invocation.span,
1001 Body::Use {
1002 target,
1003 bindings,
1004 syntax: invocation,
1005 },
1006 );
1007 if let Some(ineligible) = ineligible {
1008 self.definitions[self.id]
1009 .ineligible
1010 .get_or_insert(ineligible);
1011 }
1012 Ok(())
1013 }
1014
1015 fn conditional(
1021 &mut self,
1022 conditional: &'a ast::ConditionalDecl,
1023 ) -> Result<(), ModuleDiagnostic> {
1024 let site = label(
1025 self.owner,
1026 conditional.match_expr.span(),
1027 "match expression",
1028 );
1029 let (expression, segment, _) = self.expression(
1030 &conditional.match_expr,
1031 true,
1032 None,
1033 Some(("match expression", site)),
1034 )?;
1035 let shape = expression.shape.clone();
1036 CellValue::validate(&self.types.value_type(&shape))
1037 .map_err(|error| failure(error, expression.label.clone()))?;
1038 self.frame(&expression, segment, &shape)?;
1039 let ordinal = self.push(None, conditional.span, Body::Conditional { expression });
1040 self.definitions[self.id]
1041 .ineligible
1042 .get_or_insert_with(|| label(self.owner, conditional.span, "conditional declaration"));
1043 for branch in &conditional.branches {
1044 let literal = cel_parser::literal_expr(&branch.literal).map_err(|error| {
1045 failure(error, label(self.owner, branch.literal_span, "branch key"))
1046 })?;
1047 let literal = if branch.negated {
1048 Expr::Op {
1049 name: "-".into(),
1050 operands: vec![literal],
1051 span: branch.literal_span,
1052 }
1053 } else {
1054 literal
1055 };
1056 let segment = compile_expr(
1058 &literal,
1059 self.lookup,
1060 &self.types.cel_type_resolver(),
1061 &mut |_, _, _| Ok(false),
1062 )
1063 .map_err(|error| {
1064 failure(error, label(self.owner, branch.literal_span, "branch key"))
1065 })?;
1066 let actual = self
1067 .types
1068 .shape_of(&segment.peek_stack_infos(1)[0].value_type)
1069 .map_err(|error| {
1070 failure(error, label(self.owner, branch.literal_span, "branch key"))
1071 })?;
1072 if actual != shape {
1073 return Err(failure(
1074 "conditional branch type mismatch",
1075 label(self.owner, branch.literal_span, "branch key"),
1076 ));
1077 }
1078 let branch_id = self.push(
1079 Some(ordinal),
1080 branch.span,
1081 Body::Branch {
1082 syntax: Some(branch),
1083 },
1084 );
1085 self.branch(&branch.relationships, branch_id)?;
1086 }
1087 if let Some(default) = &conditional.default {
1088 let branch_id = self.push(Some(ordinal), default.span, Body::Branch { syntax: None });
1089 self.branch(&default.relationships, branch_id)?;
1090 }
1091 Ok(())
1092 }
1093
1094 fn branch(
1100 &mut self,
1101 items: &'a [ast::BranchItem],
1102 parent: usize,
1103 ) -> Result<(), ModuleDiagnostic> {
1104 for item in items {
1105 match item {
1106 ast::BranchItem::Relationship(item) => self.relationship(item, Some(parent))?,
1107 ast::BranchItem::RelationshipUse(item) => self.invocation(item, Some(parent))?,
1108 }
1109 }
1110 Ok(())
1111 }
1112
1113 fn item(&mut self, item: &'a ast::SheetItem) -> Result<(), ModuleDiagnostic> {
1119 match item {
1120 ast::SheetItem::Cell(item) => {
1121 let cell = self.declaration(
1122 &item.name,
1123 item.name_span,
1124 item.type_name.as_ref(),
1125 item.initializer.as_ref(),
1126 Kind::Cell,
1127 )?;
1128 self.policies(cell, item.filter.as_ref(), item.require.as_ref())
1129 }
1130 ast::SheetItem::Source(item) => {
1131 let cell = self.declaration(
1132 &item.name,
1133 item.name_span,
1134 item.type_name.as_ref(),
1135 item.initializer.as_ref(),
1136 Kind::Source,
1137 )?;
1138 self.policies(cell, item.filter.as_ref(), item.require.as_ref())
1139 }
1140 ast::SheetItem::Out(item) => {
1141 let cell = self.declaration(
1142 &item.name,
1143 item.name_span,
1144 item.type_name.as_ref(),
1145 Some(&item.initializer),
1146 Kind::Out,
1147 )?;
1148 self.policies(cell, item.filter.as_ref(), item.require.as_ref())
1149 }
1150 ast::SheetItem::Relationship(item) => self.relationship(item, None),
1151 ast::SheetItem::RelationshipUse(item) => self.invocation(item, None),
1152 ast::SheetItem::Conditional(item) => self.conditional(item),
1153 ast::SheetItem::Error { span, .. } => Err(failure(
1154 "invalid body syntax",
1155 label(self.owner, *span, "body"),
1156 )),
1157 }
1158 }
1159}
1160
1161struct SourceVisit {
1163 definition: usize,
1164 parameters: Vec<bool>,
1165 parent: Option<usize>,
1166 site: SourceLabel,
1167}
1168
1169fn source_labels(
1174 visits: &[SourceVisit],
1175 mut visit: usize,
1176 terminal: SourceLabel,
1177) -> Vec<SourceLabel> {
1178 debug_assert!(visit < visits.len());
1179 let mut labels = Vec::new();
1180 loop {
1181 labels.push(visits[visit].site.clone());
1182 let Some(parent) = visits[visit].parent else {
1183 break;
1184 };
1185 debug_assert!(parent < visit);
1186 visit = parent;
1187 }
1188 labels.reverse();
1189 labels.push(terminal);
1190 labels
1191}
1192
1193impl Checked<'_> {
1194 fn sources(&self) -> Result<(), Vec<ModuleDiagnostic>> {
1200 let mut visits: Vec<_> = self
1201 .definitions
1202 .iter()
1203 .enumerate()
1204 .map(|(id, definition)| SourceVisit {
1205 definition: id,
1206 parameters: vec![false; definition.formals],
1207 parent: None,
1208 site: definition.label.clone(),
1209 })
1210 .collect();
1211 let mut seen = HashSet::new();
1212 let mut errors = Vec::new();
1213 let mut next = 0;
1214 while next < visits.len() {
1215 let visit = next;
1216 next += 1;
1217 let id = visits[visit].definition;
1218 let parameters = &visits[visit].parameters;
1219 if !seen.insert((id, parameters.clone())) {
1220 continue;
1221 }
1222 let definition = &self.definitions[id];
1223 if let Some(native) = definition.native {
1224 let bindings: Vec<_> = definition.cells[..definition.formals]
1225 .iter()
1226 .zip(parameters)
1227 .map(|(cell, source)| (cell.name, *source))
1228 .collect();
1229 if let Err(error) = native.check_source_bindings(&bindings) {
1230 errors.push(ModuleDiagnostic {
1231 cause: error.cause.clone(),
1232 message: error.to_string(),
1233 labels: source_labels(&visits, visit, definition.label.clone()),
1234 });
1235 }
1236
1237 continue;
1238 }
1239 let sources: Vec<_> = definition
1240 .cells
1241 .iter()
1242 .enumerate()
1243 .map(|(cell, declaration)| {
1244 declaration.kind == Kind::Source
1245 || parameters.get(cell).copied().unwrap_or(false)
1246 })
1247 .collect();
1248 for item in &definition.items {
1249 match &item.body {
1250 Body::Relationship { methods } => {
1251 let survives = definition.items[methods.clone()].iter().any(|method| {
1252 matches!(&method.body, Body::Method { outputs, .. } if outputs.iter().all(|cell| !sources[*cell]))
1253 });
1254 if !survives {
1255 errors.push(ModuleDiagnostic {
1256 cause: None,
1257 message: "source specialization leaves an empty relationship"
1258 .into(),
1259 labels: source_labels(&visits, visit, item.label.clone()),
1260 });
1261 }
1262 }
1263 Body::Use {
1264 target, bindings, ..
1265 } => {
1266 visits.push(SourceVisit {
1267 definition: *target,
1268 parameters: bindings.iter().map(|cell| sources[*cell]).collect(),
1269 parent: Some(visit),
1270 site: item.label.clone(),
1271 });
1272 }
1273 _ => {}
1274 }
1275 }
1276 }
1277 if errors.is_empty() {
1278 Ok(())
1279 } else {
1280 Err(errors)
1281 }
1282 }
1283}
1284
1285#[cfg(test)]
1286mod tests {
1287 #[test]
1289 fn partial_output_overlap_keeps_cell_set_diagnostics_neutral() {
1290 let prefix = "sheet app {\n\
1291 cell a: i32; cell b: i32; cell c: i32; cell d: i32;\n\
1292 relationship {\n\
1293 (a, b) := (c, c);\n\
1294 (b, c) := (a, a);\n";
1295 let path: ModulePath = "app".parse().unwrap();
1296 let mut valid = ModuleSet::new();
1297 valid
1298 .insert_source(path.clone(), "valid.adm2", &format!("{prefix}}}}}"))
1299 .unwrap();
1300 assert!(
1301 check_module(&path, &valid, &TypeRegistry::new(), &OpLookup::new()).is_empty(),
1302 "the first two alternatives are valid together"
1303 );
1304 let mut invalid = ModuleSet::new();
1305 invalid
1306 .insert_source(
1307 path.clone(),
1308 "invalid.adm2",
1309 &format!("{prefix}(b, d) := (a, a);\n}}}}"),
1310 )
1311 .unwrap();
1312 let errors = check_module(&path, &invalid, &TypeRegistry::new(), &OpLookup::new());
1313 assert_eq!(errors.len(), 1);
1314 let error = &errors[0];
1315 assert!(error.message.contains("different cell sets"));
1316 assert_eq!(
1317 error.labels[0].location.as_ref().unwrap().span.start.line,
1318 6
1319 );
1320 assert_eq!(error.labels.len(), 3);
1321 assert!(
1322 error.labels[1..]
1323 .iter()
1324 .all(|label| label.label == "earlier method declared here"),
1325 "{error:?}"
1326 );
1327 }
1328
1329 #[test]
1331 fn nested_output_overlap_labels_only_forbidden_pairs() {
1332 let path: ModulePath = "app".parse().unwrap();
1333 let mut modules = ModuleSet::new();
1334 modules
1335 .insert_source(
1336 path.clone(),
1337 "nested.adm2",
1338 "sheet app {
1339 cell a: i32; cell b: i32; cell c: i32; cell d: i32;
1340 relationship {
1341 (a, b, c) := (d, d, d);
1342 (b, c, d) := (a, a, a);
1343 (a, b) := (c, d);
1344 }
1345 }",
1346 )
1347 .unwrap();
1348 let errors = check_module(&path, &modules, &TypeRegistry::new(), &OpLookup::new());
1349 assert_eq!(errors.len(), 1);
1350 let error = &errors[0];
1351 assert!(error.message.contains("equal or nested"));
1352 assert_eq!(
1353 error
1354 .labels
1355 .iter()
1356 .filter(|label| label.label == "collides with this earlier method")
1357 .count(),
1358 1,
1359 "{error:?}"
1360 );
1361 assert_eq!(
1362 error
1363 .labels
1364 .iter()
1365 .filter(|label| label.label == "earlier method declared here")
1366 .count(),
1367 1,
1368 "{error:?}"
1369 );
1370 let claimed: Vec<_> = error
1371 .labels
1372 .iter()
1373 .filter(|label| label.label.contains("claimed more than once"))
1374 .map(|label| label.label.as_str())
1375 .collect();
1376 assert_eq!(
1377 claimed,
1378 [
1379 "output cell `a` is claimed more than once",
1380 "output cell `b` is claimed more than once"
1381 ]
1382 );
1383 }
1384
1385 #[test]
1387 fn invalid_prefix_selects_the_first_authored_failure() {
1388 let valid = (&[0usize][..], &[1usize][..]);
1389 let reverse = (&[1usize][..], &[0usize][..]);
1390 let invalid = (&[][..], &[2usize][..]);
1391 assert_eq!(first_invalid_method(&[]), None);
1392 assert_eq!(first_invalid_method(&[(&[][..], &[][..])]), Some(0));
1393 assert_eq!(first_invalid_method(&[valid, invalid, reverse]), Some(1));
1394 assert_eq!(first_invalid_method(&[valid, reverse, invalid]), Some(2));
1395 }
1396
1397 use super::*;
1398 use crate::cell_value::Frame;
1399
1400 #[test]
1402 fn checked_emission_preserves_filter_candidate_and_dependency_order() {
1403 let mut modules = ModuleSet::new();
1404 modules
1405 .insert_source(
1406 "filters".parse().unwrap(),
1407 "filters.adm2",
1408 "
1409 sheet filters {
1410 cell left: i32;
1411 cell right: i32;
1412 cell first: i32 filter right * 100 + _ * 10 + left + right;
1413 cell second: i32 filter left * 100 + right * 10 + _ + left;
1414 }
1415 ",
1416 )
1417 .unwrap();
1418 let types = TypeRegistry::new();
1419 let lookup = OpLookup::new();
1420 let checked =
1421 analyze(&modules, &types, &lookup).unwrap_or_else(|errors| panic!("{errors:?}"));
1422 let definition = &checked.definitions[0];
1423 let scalar = TypeShape::Named(TypeId::of::<i32>());
1424 let inputs = vec![scalar.clone(); 3];
1425 let mut filters = 0;
1426 for item in &definition.items {
1427 let Body::Filter {
1428 cell,
1429 expression,
1430 range,
1431 } = &item.body
1432 else {
1433 continue;
1434 };
1435 assert!(!range);
1436 assert_eq!(expression.candidate, Some(*cell));
1437 let (names, cases) = match definition.cells[*cell].name {
1438 "first" => (["right", "left"], [(3, 5, 7, 542), (11, 13, 17, 1440)]),
1439 "second" => (["left", "right"], [(3, 7, 5, 760), (11, 17, 13, 1858)]),
1440 name => panic!("unexpected filter {name}"),
1441 };
1442 assert_eq!(
1443 expression
1444 .inputs
1445 .iter()
1446 .map(|input| definition.cells[*input].name)
1447 .collect::<Vec<_>>(),
1448 names
1449 );
1450 let mut frame = Frame::new(
1451 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1452 &inputs,
1453 std::slice::from_ref(&scalar),
1454 false,
1455 &types,
1456 )
1457 .unwrap();
1458 for (candidate, first, second, expected) in cases {
1459 let result = frame
1460 .call(&[&candidate, &first, &second])
1461 .unwrap()
1462 .pop()
1463 .unwrap();
1464 assert_eq!(*result.downcast::<i32>().unwrap(), expected);
1465 }
1466 assert!(
1467 Frame::new(
1468 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1469 &inputs,
1470 &[TypeShape::Named(TypeId::of::<f64>())],
1471 false,
1472 &types
1473 )
1474 .is_err()
1475 );
1476 filters += 1;
1477 }
1478 assert_eq!(filters, 2);
1479 }
1480
1481 #[test]
1483 fn checked_emission_retains_exact_range_result() {
1484 let mut modules = ModuleSet::new();
1485 modules
1486 .insert_source(
1487 "ranges".parse().unwrap(),
1488 "ranges.adm2",
1489 "
1490 sheet ranges {
1491 cell left: i32;
1492 cell right: i32;
1493 cell bounded: i32 filter (right - _ + left - left)..=(left + right - left);
1494 }
1495 ",
1496 )
1497 .unwrap();
1498 let types = TypeRegistry::new();
1499 let lookup = OpLookup::new();
1500 let checked =
1501 analyze(&modules, &types, &lookup).unwrap_or_else(|errors| panic!("{errors:?}"));
1502 let definition = &checked.definitions[0];
1503 let (cell, expression, range) = definition
1504 .items
1505 .iter()
1506 .find_map(|item| match &item.body {
1507 Body::Filter {
1508 cell,
1509 expression,
1510 range,
1511 } => Some((*cell, expression, *range)),
1512 _ => None,
1513 })
1514 .unwrap();
1515 assert!(range);
1516 assert_eq!(expression.candidate, Some(cell));
1517 assert_eq!(
1518 expression
1519 .inputs
1520 .iter()
1521 .map(|input| definition.cells[*input].name)
1522 .collect::<Vec<_>>(),
1523 ["right", "left"]
1524 );
1525 let expected = cel_runtime::ValueType::cloneable_leaf::<std::ops::RangeInclusive<i32>>();
1526 let wrong = cel_runtime::ValueType::cloneable_leaf::<std::ops::RangeInclusive<f64>>();
1527 assert!(expression.descriptor.same_shape(&expected));
1528 assert!(!expression.descriptor.same_shape(&wrong));
1529 let inputs = vec![TypeShape::Named(TypeId::of::<i32>()); 3];
1530 let emitted = expression.emit(&definition.cells, &types, &lookup).unwrap();
1531 assert!(
1532 emitted.peek_stack_infos(1)[0]
1533 .value_type
1534 .same_shape(&expected)
1535 );
1536 let mut frame = RawFrame::new(emitted, &inputs, &expected, &types).unwrap();
1537 for (candidate, right, left, expected) in
1538 [(3i32, 20i32, 5i32, 17..=20), (7, 40, 11, 33..=40)]
1539 {
1540 let result = frame
1541 .call(&[&candidate, &right, &left])
1542 .unwrap()
1543 .try_into_typed::<cel_runtime::Leaf<std::ops::RangeInclusive<i32>>>()
1544 .unwrap();
1545 assert_eq!(result, expected);
1546 }
1547 assert!(
1548 RawFrame::new(
1549 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1550 &inputs,
1551 &wrong,
1552 &types
1553 )
1554 .is_err()
1555 );
1556 }
1557
1558 #[test]
1560 fn checked_emission_preserves_one_element_output_patterns() {
1561 let mut modules = ModuleSet::new();
1562 modules
1563 .insert_source(
1564 "patterns".parse().unwrap(),
1565 "patterns.adm2",
1566 "
1567 sheet patterns(a: i32, b: i32, whole: (i32,)) {
1568 relationship { (a,) := (b,); }
1569 relationship { whole := (b,); }
1570 }
1571 ",
1572 )
1573 .unwrap();
1574 let types = TypeRegistry::new();
1575 let lookup = OpLookup::new();
1576 let checked =
1577 analyze(&modules, &types, &lookup).unwrap_or_else(|errors| panic!("{errors:?}"));
1578 let definition = &checked.definitions[0];
1579 let scalar = TypeShape::Named(TypeId::of::<i32>());
1580 let mut methods = 0;
1581 for item in &definition.items {
1582 let Body::Method {
1583 outputs,
1584 destructure,
1585 expression,
1586 } = &item.body
1587 else {
1588 continue;
1589 };
1590 assert_eq!(outputs.len(), 1);
1591 let output = &definition.cells[outputs[0]];
1592 assert_eq!(*destructure, output.name == "a");
1593 let mut frame = Frame::new(
1594 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1595 std::slice::from_ref(&scalar),
1596 std::slice::from_ref(&output.shape),
1597 *destructure,
1598 &types,
1599 )
1600 .unwrap();
1601 for input in [19i32, 31] {
1602 let result = frame.call(&[&input]).unwrap().pop().unwrap();
1603 let actual = if *destructure {
1604 *result.downcast::<i32>().unwrap()
1605 } else {
1606 let compound = *result.downcast::<CellValue>().unwrap();
1607 let mut fields = compound.0.into_fields().unwrap();
1608 assert_eq!(fields.len(), 1);
1609 fields
1610 .pop()
1611 .unwrap()
1612 .try_into_typed::<cel_runtime::Leaf<i32>>()
1613 .unwrap()
1614 };
1615 assert_eq!(actual, input);
1616 }
1617 assert!(
1618 Frame::new(
1619 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1620 std::slice::from_ref(&scalar),
1621 std::slice::from_ref(&output.shape),
1622 !*destructure,
1623 &types
1624 )
1625 .is_err()
1626 );
1627 assert!(
1628 Frame::new(
1629 expression.emit(&definition.cells, &types, &lookup).unwrap(),
1630 std::slice::from_ref(&scalar),
1631 &[TypeShape::Named(TypeId::of::<f64>())],
1632 *destructure,
1633 &types
1634 )
1635 .is_err()
1636 );
1637 methods += 1;
1638 }
1639 assert_eq!(methods, 2);
1640 }
1641
1642 #[test]
1644 fn checked_plan_retains_factory_inputs_without_expansion() {
1645 let path: ModulePath = "plans".parse().unwrap();
1646 let mut modules = ModuleSet::new();
1647 modules.insert_source(path, "plans.adm2", "sheet copy(a: i32, b: i32) { relationship { a := b; b := a; } } sheet wrapper(x: i32, y: i32) { cell hidden: [i32]; relationship copy(b: y, a: x); conditional true { true => { relationship copy(a: x, b: y); } } }").unwrap();
1648 let types = TypeRegistry::new();
1649 let lookup = OpLookup::new();
1650 let checked =
1651 analyze(&modules, &types, &lookup).unwrap_or_else(|errors| panic!("{errors:?}"));
1652 assert!(std::ptr::eq(checked.types, &types));
1653 assert!(std::ptr::eq(checked.lookup, &lookup));
1654 assert_eq!(checked.definitions.len(), 2);
1655 assert_eq!(checked.resolved.order, [0, 1]);
1656 let copy = &checked.definitions[0];
1657 assert_eq!(
1658 copy.cells.iter().map(|cell| cell.name).collect::<Vec<_>>(),
1659 ["a", "b"]
1660 );
1661 let Body::Method {
1662 outputs,
1663 expression,
1664 ..
1665 } = ©.items[1].body
1666 else {
1667 panic!("method plan");
1668 };
1669 assert_eq!(outputs, &[0]);
1670 assert_eq!(expression.inputs, [1]);
1671 let emitted = expression.emit(©.cells, &types, &lookup).unwrap();
1672 assert!(
1673 emitted.peek_stack_infos(1)[0]
1674 .value_type
1675 .same_shape(&expression.descriptor)
1676 );
1677 let (module, local) = checked.resolved.definitions[0];
1678 let DefinitionBody::Parsed { sheet, .. } = &modules.entries[module].definitions[local].body
1679 else {
1680 panic!("parsed");
1681 };
1682 let ast::SheetItem::Relationship(relationship) = &sheet.items[0] else {
1683 panic!("relationship");
1684 };
1685 assert!(std::ptr::eq(
1686 expression.syntax,
1687 &relationship.bindings[0].body
1688 ));
1689 let wrapper = &checked.definitions[1];
1690 assert_eq!(wrapper.formals, 2);
1691 assert_eq!(wrapper.cells.len(), 3);
1692 let uses: Vec<_> = wrapper
1693 .items
1694 .iter()
1695 .filter_map(|item| match &item.body {
1696 Body::Use {
1697 target, bindings, ..
1698 } => Some((*target, bindings.as_slice())),
1699 _ => None,
1700 })
1701 .collect();
1702 assert_eq!(uses, [(0, &[0, 1][..]), (0, &[0, 1][..])]);
1703 assert!(
1704 wrapper
1705 .items
1706 .iter()
1707 .any(|item| item.parent.is_some() && matches!(item.body, Body::Use { .. }))
1708 );
1709 for definition in &checked.definitions {
1710 for site in definition
1711 .cells
1712 .iter()
1713 .map(|cell| &cell.label)
1714 .chain(definition.items.iter().map(|item| &item.label))
1715 {
1716 assert_eq!(
1717 site.location.as_ref().unwrap().source.file_name(),
1718 "plans.adm2"
1719 );
1720 assert_eq!(
1721 &site.location.as_ref().unwrap().source,
1722 modules.entries[module].source.as_ref().unwrap()
1723 );
1724 }
1725 }
1726 }
1727}