Skip to main content

adam_lang/module/
analysis.rs

1//! Checks retained bodies against a borrowed environment without executing their segments.
2//!
3//! The flat plan retains authored syntax and local ordinals for owning factory materialization.
4
5use 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
17/// Checks every registered definition using its declaring module and real source owner.
18///
19/// `module` must identify a registered module; checking includes unused definitions in
20/// other registered modules, so the same collection has the same semantic validity for
21/// every valid selection. Checks all authored directions before source specialization,
22/// including inactive branches. No initializer, default, method, filter, requirement,
23/// or match expression executes. Whole-graph planning and guard independence remain
24/// construction-time checks; this operation does not require isolated solvability.
25/// Checks default availability for every output's backing cell, even with a writer;
26/// initialized ordinary cells and sources do not require a default.
27///
28/// Returns diagnostics for unknown selection, names, complete types, bodies, bindings,
29/// branch eligibility and source-specialized empty relationships. Labels own genuine
30/// sources independently of `modules`.
31///
32/// - Complexity: O(n² + s) bookkeeping plus CEL compilation, registry resolution
33///   and native preflight, where n is authored syntax and s is body edges in
34///   distinct (definition, source-parameter-mask) specializations.
35///
36/// # Examples
37/// ```
38/// use adam_lang::{ModulePath, ModuleSet, TypeRegistry, check_module};
39/// let path: ModulePath = "example".parse()?;
40/// let mut modules = ModuleSet::new();
41/// modules.insert_source(path.clone(), "example.adm2",
42///     "sheet copy(a: i32, b: i32) { relationship { a := b; b := a; } }")?;
43/// assert!(check_module(&path, &modules, &TypeRegistry::new(),
44///     &cel_parser::OpLookup::new()).is_empty());
45/// # Ok::<(), adam_lang::ModuleDiagnostic>(())
46/// ```
47pub 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/// Distinguishes bound formals from allocating local declarations.
65#[derive(Clone, Copy, PartialEq, Eq)]
66pub(super) enum Kind {
67    Parameter,
68    Cell,
69    Source,
70    Out,
71}
72
73/// Retains one declaration's complete shape and genuine source label.
74pub(super) struct Cell<'a> {
75    pub name: &'a str,
76    pub shape: TypeShape,
77    pub kind: Kind,
78    pub label: SourceLabel,
79}
80
81/// Retains checked syntax, positional reads, and its complete result descriptor.
82pub(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    /// Emits fresh execution resources using checked positional bindings, without deducing names.
93    ///
94    /// - Precondition: `cells`, `types` and `lookup` belong to this expression's checked plan.
95    /// # Errors
96    /// Propagates checked operation emission failures without executing the result.
97    /// - Complexity: CEL compilation plus O(nr) identifier comparisons for n names and r reads.
98    #[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
128/// Borrows planned positional bindings for fresh emission without repeating body analysis.
129pub(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    /// Emits one fresh segment with the checked candidate and first-use positional order.
142    ///
143    /// - Precondition: Bindings and descriptors come from analysis in this environment.
144    /// # Errors
145    /// Propagates operation-emission errors without executing any callback.
146    /// - Complexity: CEL compilation plus O(nr) positional name comparisons.
147    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
184/// Retains a flat authored body node with its enclosing conditional/branch ordinal.
185pub(super) struct Item<'a> {
186    pub parent: Option<usize>,
187    pub label: SourceLabel,
188    pub body: Body<'a>,
189}
190
191/// Preserves item ordering, expression roles, and definition-local output ordinals.
192pub(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
232/// Owns one checked local body, or borrows an already validated native template.
233pub(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
244/// Owns flat definition plans while borrowing syntax and the immutable compiler environment.
245pub(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
252/// Analyzes all signatures and bodies once, then checks statically known source masks.
253///
254/// # Errors
255/// Returns authored diagnostics without executing retained expressions.
256/// - Complexity: As [`check_module`], with O(n + s) plan and memoization storage.
257pub(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    // Collect every complete signature before checking any body.
266    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    // Dependency postorder makes transitive branch classification available at each use.
341    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
380/// Borrows the evolving local namespace and completed dependency plans.
381struct 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
391/// Attaches a semantic failure to an owned genuine source label.
392///
393/// - Complexity: O(n) formatted message bytes.
394fn 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
402/// Adds declaration context while retaining the original inference site as secondary.
403///
404/// - Complexity: O(m + l) message bytes and labels.
405fn 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
415/// Describes the first differing complete type, including tuple arity.
416///
417/// - Precondition: `expected` and `actual` differ and contain registered leaves.
418/// - Complexity: O(n) compared type nodes and formatted name bytes.
419fn 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
440/// Returns the first invalid native method prefix, or no method for an empty relationship.
441///
442/// Binary search uses the monotone native structural-validity predicate.
443/// - Precondition: The complete edge list fails native validation.
444/// - Complexity: O(m²e log m) time and O(me) native scratch space for m methods of e edges.
445fn 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    /// Locates a rejected native relationship at its first invalid authored prefix.
465    ///
466    /// The native validator remains authoritative; this error-only pass adds
467    /// binding/declaration labels without constructing methods or another graph.
468    /// Only native equal/nested output-set rejections receive collision labels.
469    /// - Precondition: `edges` correspond to `relationship` and fail native validation.
470    /// - Complexity: O(m²e log m) error-only native validation and O(me) label work.
471    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    /// Appends a local item and returns its stable ordinal.
515    ///
516    /// - Complexity: Amortized O(1), plus label bytes.
517    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    /// Resolves an already declared local cell, without caller capture.
529    ///
530    /// # Errors
531    /// Rejects names outside the declaration-order namespace.
532    /// - Complexity: Expected O(n) name bytes.
533    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    /// Compiles a retained expression with complete borrowed descriptors, without calling it.
547    ///
548    /// # Errors
549    /// Rejects unknown names, invalid operations, and missing canonical capabilities.
550    /// - Complexity: CEL compilation plus O(c × r) name/read bookkeeping.
551    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    /// Validates positional reads and complete results using the ordinary canonical frame.
635    ///
636    /// # Errors
637    /// Rejects shape/capability mismatches without executing a frame.
638    /// - Complexity: O(n + r) shape nodes and reads.
639    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    /// Adds a checked declaration after compiling its initializer in the permitted namespace.
662    ///
663    /// # Errors
664    /// Rejects duplicate names, invalid shapes/defaults or initializer mismatches.
665    /// - Complexity: O(c + n) namespace and descriptor nodes, plus CEL compilation.
666    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        // Outputs allocate a default private cell before installing their writer.
725        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    /// Checks the ordinary filter and requirement contracts in declaration order.
764    ///
765    /// # Errors
766    /// Rejects missing filter candidates, mismatched ranges/results or non-bool requirements.
767    /// - Complexity: Expression compilation plus O(n) policy descriptors.
768    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    /// Checks a complete ordinary relationship before any source-based pruning.
861    ///
862    /// # Errors
863    /// Rejects invalid targets, shapes and ordinary relationship edge structure.
864    /// - Complexity: O(m²e) structural checks plus expression compilation.
865    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    /// Checks complete named bindings against a dependency's checked signature.
940    ///
941    /// # Errors
942    /// Rejects missing/unknown/repeated/aliased/out arguments, shapes and ineligible branches.
943    /// - Complexity: O(a × (f + c)) actuals, formals and caller cells.
944    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    /// Checks each branch key and every inactive or active branch body.
1016    ///
1017    /// # Errors
1018    /// Rejects match/key mismatches and invalid branch relationships or uses.
1019    /// - Complexity: Expression compilation and authored branch traversal.
1020    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            // Literal descriptors are checked without evaluating keys or user match code.
1057            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    /// Checks a branch's authored relationship order.
1095    ///
1096    /// # Errors
1097    /// Returns the first invalid branch item.
1098    /// - Complexity: Sum of branch-item checks.
1099    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    /// Checks one top-level item in its declaration-order namespace.
1114    ///
1115    /// # Errors
1116    /// Returns the item's semantic diagnostic.
1117    /// - Complexity: The selected body check's complexity.
1118    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
1161/// Retains one pending source specialization and an ordinal-only invocation ancestry.
1162struct SourceVisit {
1163    definition: usize,
1164    parameters: Vec<bool>,
1165    parent: Option<usize>,
1166    site: SourceLabel,
1167}
1168
1169/// Labels an outer-to-inner source-specialization path without recursively owned metadata.
1170///
1171/// - Precondition: Each parent ordinal precedes its child and `visit` is in bounds.
1172/// - Complexity: O(h) labels for invocation depth h.
1173fn 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    /// Checks every distinct source specialization with an iterative memoized worklist.
1195    ///
1196    /// # Errors
1197    /// Rejects whole-method pruning that empties any constituent, even in inactive branches.
1198    /// - Complexity: O(s) specialized body edges, plus native source preflight.
1199    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    /// Keeps legal partial output overlap neutral when a later method changes the cell set.
1288    #[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    /// Labels only equal/nested pairs when another earlier output set overlaps legally.
1330    #[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    /// Locates early, late and empty structural failures without accepting later methods.
1386    #[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    /// Emits candidate-first filters with distinct, first-use-ordered, repeated dependencies.
1401    #[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    /// Re-emits an exact range descriptor with candidate and repeated reordered input reads.
1482    #[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    /// Executes one-field destructuring separately from assigning a whole one-element tuple.
1559    #[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    /// Retains borrowed environments, local ordinals, syntax, direction order and real labels.
1643    #[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        } = &copy.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(&copy.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}