Skip to main content

zirql/
value.rs

1use std::cmp::Ordering;
2use std::collections::{BTreeMap, HashSet};
3use std::mem::size_of;
4
5pub(crate) mod execution;
6pub(crate) mod pattern;
7pub use pattern::{Pattern, PatternKind, copy_pattern, dispose_pattern, pattern_names};
8pub(crate) mod prepared;
9use prepared::{
10    Frame, Preparation, PreparedArgument, PreparedGenerator, PreparedKind, PreparedValue,
11    RuntimeValue,
12};
13
14use crate::runtime_types::{annotate, check_known_type, compatible_declarations, copy_declaration};
15use crate::{Bindings, Budget, DeclaredType, Located, Location, QueryError, QueryErrorCategory};
16
17/// Host-supplied enum identity, scoped to one immutable host context per request.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct NominalTypeId(pub u64);
20
21/// Host-supplied member identity within its nominal type.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct NominalMemberId(pub u64);
24
25/// A resolved member. Names and declaration order are retained host facts;
26/// semantic equality uses only type/member IDs. Budgeted entry points reject
27/// contradictory metadata wherever members are compared.
28#[derive(Debug)]
29pub struct NominalMember {
30    pub type_id: NominalTypeId,
31    pub member_id: NominalMemberId,
32    pub declaration_order: usize,
33    pub type_name: String,
34    pub member_name: String,
35}
36
37impl PartialEq for NominalMember {
38    fn eq(&self, other: &Self) -> bool {
39        self.type_id == other.type_id && self.member_id == other.member_id
40    }
41}
42impl Eq for NominalMember {}
43
44/// A single integer or resolved nominal dimension key.
45#[derive(Debug, PartialEq, Eq)]
46pub enum ValueKey {
47    Integer(i64),
48    Member(NominalMember),
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum ValueKeyType {
53    Integer,
54    Member(NominalTypeId),
55}
56
57/// An established dimension, including typed empty axes. Supplied key order is
58/// observable and need not be numeric or declaration order.
59#[derive(Debug, PartialEq, Eq)]
60pub struct KeyAxis {
61    pub key_type: ValueKeyType,
62    pub keys: Vec<ValueKey>,
63}
64
65/// One sparse cell. Native entries must be unique and in row-major axis order.
66#[derive(Debug, PartialEq)]
67pub struct KeyedEntry {
68    pub key: Vec<ValueKey>,
69    pub value: ConstructedValue,
70}
71
72/// Keyed facts retain rank/dimension identity and possible element types.
73/// `None` axes are deferred expression facts only; concrete data always has
74/// `Some` established, nonzero-rank axes.
75#[derive(Debug, PartialEq, Eq)]
76pub struct KeyedArrayType {
77    pub axes: Option<Vec<ValueKeyType>>,
78    pub elements: Vec<ValueType>,
79}
80
81/// Supported homogeneous set identities. Float identities use numeric equality,
82/// so positive and negative zero denote the same element.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum SetElementType {
85    Boolean,
86    Integer,
87    Float,
88    Member(NominalTypeId),
89}
90
91/// Owned constructed data. Arrays have positive one-based positions. Tuples and
92/// records retain field order; sets retain each first inserted element.
93/// Keyed arrays retain nonzero typed axes and sparse row-major entries; supplied
94/// axes need not be contiguous or cover a full Cartesian product.
95/// Public construction is checked by the budgeted entry points before use.
96/// Derived Rust equality compares stored representations. Language set and
97/// dictionary equality compares membership and associations respectively.
98/// There is deliberately no deep `Clone`; use [`copy_constructed_value`].
99#[derive(Debug, PartialEq)]
100pub enum ConstructedValue {
101    Absent,
102    /// An admitted immutable value with an explicit constructed declaration.
103    Declared {
104        value: Box<Self>,
105        declaration: DeclaredType<()>,
106    },
107    Boolean(bool),
108    Integer(i64),
109    Float(f64),
110    String(String),
111    Member(NominalMember),
112    KeyedArray {
113        axes: Vec<KeyAxis>,
114        entries: Vec<KeyedEntry>,
115    },
116    Array(Vec<Self>),
117    Tuple(Vec<Self>),
118    Record(Vec<(String, Self)>),
119    Set {
120        element_type: Option<SetElementType>,
121        elements: Vec<Self>,
122    },
123}
124
125/// Release owned payloads even when admission rejected their depth.
126pub(crate) fn dispose_constructed_value(value: ConstructedValue) {
127    let mut pending = vec![value];
128    while let Some(value) = pending.pop() {
129        match value {
130            ConstructedValue::Declared { value, declaration } => {
131                crate::functions::dispose_declaration(declaration);
132                pending.push(*value);
133            }
134            ConstructedValue::Array(values) | ConstructedValue::Tuple(values) => {
135                pending.extend(values);
136            }
137            ConstructedValue::Record(fields) => {
138                pending.extend(fields.into_iter().map(|(_, value)| value));
139            }
140            ConstructedValue::KeyedArray { entries, .. } => {
141                pending.extend(entries.into_iter().map(|entry| entry.value));
142            }
143            ConstructedValue::Set { elements, .. } => pending.extend(elements),
144            ConstructedValue::Absent
145            | ConstructedValue::Boolean(_)
146            | ConstructedValue::Integer(_)
147            | ConstructedValue::Float(_)
148            | ConstructedValue::String(_)
149            | ConstructedValue::Member(_) => {}
150        }
151    }
152}
153
154/// Concrete positional facts, including heterogeneous arrays. `Elements`
155/// describes possible element types, without claiming a length or positions.
156#[derive(Debug, PartialEq, Eq)]
157pub enum ArrayType {
158    Positions(Vec<ValueType>),
159    Elements(Vec<ValueType>),
160}
161
162/// Validation facts supplied by projections or discovered from actual data.
163/// `Unestablished` admits deferred checking, such as absent access or an empty
164/// source's element type. It is never a runtime value or Boolean truth.
165#[derive(Debug, PartialEq, Eq)]
166pub enum ValueType {
167    Absent,
168    /// Keep declaration constraints separate from the observed value facts.
169    Declared {
170        actual: Box<Self>,
171        declaration: DeclaredType<()>,
172    },
173    /// A present Boolean, integer, float or string whose primitive kind is unknown.
174    /// Preparation metadata only; actual values retain their precise primitive facts.
175    ScalarKind,
176    Boolean,
177    Integer,
178    Float,
179    String,
180    Member(NominalTypeId),
181    KeyedArray(KeyedArrayType),
182    Array(ArrayType),
183    /// Possible array elements with storage, positions and axes unestablished.
184    ArrayKind(Vec<Self>),
185    Tuple(Vec<Self>),
186    /// A tuple whose positions have not been inspected; no arity is known.
187    TupleKind,
188    Record(Vec<(String, Self)>),
189    /// A record whose stored fields have not been inspected; no schema is known.
190    RecordKind,
191    Set(Option<SetElementType>),
192    Unestablished,
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub enum ValueUnaryOperator {
197    Positive,
198    Negative,
199    Not,
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203pub enum ValueBinaryOperator {
204    Add,
205    Subtract,
206    Multiply,
207    Divide,
208    IntegerDivide,
209    Modulo,
210    Less,
211    LessEqual,
212    Greater,
213    GreaterEqual,
214    Equal,
215    NotEqual,
216    In,
217    And,
218    Or,
219    Concatenate,
220    /// Exact case-sensitive substring matching. Empty search strings match.
221    Contains,
222    /// Exact case-sensitive prefix matching. Empty prefixes match.
223    StartsWith,
224    /// Exact case-sensitive suffix matching. Empty suffixes match.
225    EndsWith,
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub enum ValueConstructor {
230    Array,
231    Set,
232}
233
234/// Closed summaries over fully evaluated positional or keyed arrays.
235/// Length counts present values; sum accepts integers and the quantifiers
236/// accept fixed Booleans. Empty identities are 0, 0, true and false.
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub enum ValueReducer {
239    Length,
240    Sum,
241    Forall,
242    Exists,
243}
244
245/// Select the first input value with the smallest or largest ordering key.
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum ValueExtremum {
248    Min,
249    Max,
250}
251
252/// Closed selectors for stable collection sorting.
253#[derive(Debug, PartialEq, Eq)]
254pub enum SortSelector {
255    /// Sort dictionary values directly.
256    Value,
257    /// Sort array rows or dictionary values by a static field.
258    Field(String),
259}
260
261pub type ValueExpression<Q = ()> = Located<ValueExpressionKind<Q>>;
262
263// This slot cannot be written as an ordinary language identifier.
264pub(crate) const PREDICATE_SUBJECT: &str = "\0predicate";
265
266/// One scoped body with an optional pattern for the current predicate subject.
267/// Predicates require a Boolean; existing domain leaf arguments retain their
268/// own scalar contract through `bind_value`. `_` reads the nearest subject frame,
269/// including inside ordinary guards.
270#[derive(Debug, PartialEq)]
271pub struct PredicateArgument<Q = ()> {
272    /// Match the subject once and install all successful names together.
273    /// `None` keeps only the implicit whole-subject `_` argument.
274    pub argument: Option<Pattern>,
275    pub body: Box<ValueExpression<Q>>,
276}
277impl<Q> PredicateArgument<Q> {
278    /// Bind one existing domain leaf's value argument to its predicate subject.
279    /// The leaf retains its own argument type check; the surrounding predicate
280    /// retains Boolean/Truth admission. This uses the same subject frame.
281    pub fn bind_value(self, subject: ValueExpression<Q>) -> ValueExpression<Q> {
282        let range = self.body.range.clone();
283        Located::new(
284            ValueExpressionKind::SubjectScope {
285                subject: Box::new(subject),
286                argument: self,
287            },
288            range,
289        )
290    }
291    /// Bind this body to one actual subject. Native adapters supply their
292    /// existing input expression; scalar hosts may supply a literal or name.
293    /// The resulting expression uses ordinary preflight and value execution.
294    pub fn bind(self, subject: ValueExpression<Q>) -> ValueExpression<Q> {
295        let range = self.body.range.clone();
296        Located::new(
297            ValueExpressionKind::PredicateScope {
298                subject: Box::new(subject),
299                predicate: self,
300            },
301            range,
302        )
303    }
304}
305
306/// A host-lowered pure expression. Every node retains its query byte range.
307/// Hosts reject unlisted surface forms and decimal spellings that round a
308/// nonzero literal to zero before creating this representation.
309#[derive(Debug, PartialEq)]
310pub enum ValueExpressionKind<Q = ()> {
311    Literal(ConstructedValue),
312    /// The immutable constructed input supplied for this evaluation invocation.
313    CurrentInput,
314    /// Computational FIFO. An explicit type supports empty native input.
315    Queue {
316        items: Box<ValueExpression<Q>>,
317        element: Option<Located<crate::SignatureType>>,
318    },
319    /// Optional native tuple `(item, remaining_queue)`; an empty queue is absent.
320    PopFront(Box<ValueExpression<Q>>),
321    PushBackAll {
322        queue: Box<ValueExpression<Q>>,
323        items: Box<ValueExpression<Q>>,
324    },
325    /// Empty computational set of one explicitly named native kind.
326    IdentitySet {
327        element: Located<crate::SignatureType>,
328    },
329    Insert {
330        set: Box<ValueExpression<Q>>,
331        item: Box<ValueExpression<Q>>,
332    },
333    /// A normalized native operand; it runs only when this node is selected.
334    NativeQuery(Box<Q>),
335    /// A pure native result, retained without scalar projection. Available in a
336    /// function request; ordinary value entry points reject this operand.
337    NativeValue(Box<Q>),
338    /// A named pure function, resolved before any body executes.
339    Call {
340        function: Located<String>,
341        arguments: Vec<ValueExpression<Q>>,
342    },
343    /// Apply a named pure step to an eager positional sequence in input order.
344    ///
345    /// ```
346    /// use zirql::{Bindings, Budget, ConstructedValue as V, DeclaredType as D,
347    ///     FunctionDeclaration, FunctionParameter, FunctionLimits, Located,
348    ///     ProgramLimits, ValueExpression as E, ValueExpressionKind as K,
349    ///     ValueBinaryOperator as B, evaluate_functions};
350    /// fn e(value: K) -> E { Located::new(value, 0..1) }
351    /// let step = FunctionDeclaration {
352    ///     name: Located::new("add".into(), 0..1),
353    ///     parameters: ["state", "item"].into_iter().map(|name| Located::new(
354    ///         FunctionParameter { name: Located::new(name.into(), 0..1),
355    ///                             declaration: D::Integer }, 0..1)).collect(),
356    ///     result: Located::new(D::Integer, 0..1), bindings: vec![],
357    ///     value: e(K::Binary { operator: B::Add,
358    ///         left: Box::new(e(K::Name("state".into()))),
359    ///         right: Box::new(e(K::Name("item".into()))) }),
360    /// };
361    /// let entry = e(K::Fold {
362    ///     items: Box::new(e(K::Literal(V::Array(vec![V::Integer(2), V::Integer(3)])))),
363    ///     initial: Box::new(e(K::Literal(V::Integer(0)))),
364    ///     step: Located::new("add".into(), 0..1),
365    /// });
366    /// let mut budget = Budget::for_program(ProgramLimits {
367    ///     work: 100_000, results: 10_000, nesting: 64, stream_items: 100,
368    /// });
369    /// let result = evaluate_functions(&[step], &entry, &Bindings::default(),
370    ///     &mut budget, FunctionLimits { active_depth: 1 }).unwrap();
371    /// assert_eq!(result.data(), &V::Integer(5));
372    /// ```
373    Fold {
374        items: Box<ValueExpression<Q>>,
375        initial: Box<ValueExpression<Q>>,
376        step: Located<String>,
377    },
378    /// Retain every post-step state, excluding the initial state.
379    Scan {
380        items: Box<ValueExpression<Q>>,
381        initial: Box<ValueExpression<Q>>,
382        step: Located<String>,
383    },
384    /// Extract exactly one positional child without copying it.
385    One(Box<ValueExpression<Q>>),
386    Name(String),
387    /// Read the nearest predicate subject. Ordinary bindings cannot supply it.
388    PredicateSubject,
389    /// Scope an existing native predicate leaf's non-Boolean value argument.
390    SubjectScope {
391        subject: Box<ValueExpression<Q>>,
392        argument: PredicateArgument<Q>,
393    },
394    /// Evaluate one Boolean body with a fresh unnamed subject and optional alias.
395    PredicateScope {
396        subject: Box<ValueExpression<Q>>,
397        predicate: PredicateArgument<Q>,
398    },
399    Occurs(Box<ValueExpression<Q>>),
400    Absent(Box<ValueExpression<Q>>),
401    Lookup {
402        value: Box<ValueExpression<Q>>,
403        key: Box<ValueExpression<Q>>,
404    },
405    HasKey {
406        value: Box<ValueExpression<Q>>,
407        key: Box<ValueExpression<Q>>,
408    },
409    Default {
410        value: Box<ValueExpression<Q>>,
411        fallback: Box<ValueExpression<Q>>,
412    },
413    Array(Vec<ValueExpression<Q>>),
414    Tuple(Vec<ValueExpression<Q>>),
415    Record(Vec<(Located<String>, ValueExpression<Q>)>),
416    /// Computed string keys run before their values and retain insertion order.
417    Dictionary(Vec<(ValueExpression<Q>, ValueExpression<Q>)>),
418    DictionaryComprehension {
419        clauses: Vec<Located<ValueGenerator<Q>>>,
420        key: Box<ValueExpression<Q>>,
421        head: Box<ValueExpression<Q>>,
422    },
423    Set(Vec<ValueExpression<Q>>),
424    /// Literal pairs run in written order and normalize to row-major order.
425    /// Empty literals require explicit typed axes; generic sparse data is valid.
426    KeyedArray {
427        axes: Option<Vec<KeyAxis>>,
428        /// Eager ordered sources in surrounding scope, exclusive with `axes`.
429        declared_axis_sources: Option<Vec<ValueExpression<Q>>>,
430        entries: Vec<(ValueExpression<Q>, ValueExpression<Q>)>,
431    },
432    /// Declared sources run before generators in the surrounding scope.
433    KeyedComprehension {
434        clauses: Vec<Located<ValueGenerator<Q>>>,
435        key: Box<ValueExpression<Q>>,
436        head: Box<ValueExpression<Q>>,
437        declared_axis_sources: Option<Vec<ValueExpression<Q>>>,
438        /// Lazy zero-cell fallback, exclusive with `declared_axis_sources`.
439        empty_axis_sources: Option<Vec<ValueExpression<Q>>>,
440    },
441    Unary {
442        operator: ValueUnaryOperator,
443        operand: Box<ValueExpression<Q>>,
444    },
445    Binary {
446        operator: ValueBinaryOperator,
447        left: Box<ValueExpression<Q>>,
448        right: Box<ValueExpression<Q>>,
449    },
450    Field {
451        value: Box<ValueExpression<Q>>,
452        field: Located<String>,
453    },
454    TupleAccess {
455        value: Box<ValueExpression<Q>>,
456        position: Located<usize>,
457    },
458    ArrayAccess {
459        array: Box<ValueExpression<Q>>,
460        index: Box<ValueExpression<Q>>,
461    },
462    IndexSet(Box<ValueExpression<Q>>),
463    /// Expose present keyed cells as positional `key`/`value` records. Rank-one
464    /// keys are scalars; higher-rank keys are tuples in dimension order.
465    Entries(Box<ValueExpression<Q>>),
466    /// Pair keyed arrays with identical ordered axes and present full keys.
467    /// Retain left axes/keys and move each pair into a two-element tuple cell.
468    ZipExact {
469        left: Box<ValueExpression<Q>>,
470        right: Box<ValueExpression<Q>>,
471    },
472    /// Evaluate the entire array before scanning its values in stored order.
473    Reduce {
474        operator: ValueReducer,
475        array: Box<ValueExpression<Q>>,
476    },
477    /// Retain positional elements whose scoped predicate is true, in input order.
478    Filter {
479        array: Box<ValueExpression<Q>>,
480        predicate: PredicateArgument<Q>,
481    },
482    /// Stably sort an array, set or string dictionary, retaining its kind.
483    /// Without a selector, arrays/sets use values and dictionaries use keys.
484    /// Descending reverses comparisons, preserving current order for ties.
485    /// Array and dictionary selector keys are present homogeneous integers,
486    /// finite floats, strings or nominal members. Selector failures use its range.
487    Sort {
488        array: Box<ValueExpression<Q>>,
489        selector: Option<Located<SortSelector>>,
490        descending: bool,
491    },
492    /// Retain each first occurrence in a homogeneous positional scalar array.
493    /// Supports Booleans, integers, finite floats, strings and one nominal type.
494    Unique(Box<ValueExpression<Q>>),
495    /// Count strings into a record whose labels follow their first occurrence.
496    Tally(Box<ValueExpression<Q>>),
497    /// Select one original array scalar or record, or absence for empty input.
498    /// Ties retain the first input value. Keys follow `Sort`'s array contract.
499    Extremum {
500        operator: ValueExtremum,
501        array: Box<ValueExpression<Q>>,
502        field: Option<Located<String>>,
503    },
504    /// Group positional records by one static field, preserving first-seen keys
505    /// and all rows in input order. Keys are homogeneous integers, strings or
506    /// members of one nominal type. Results use existing `key`/`items` records.
507    /// Missing fields are deferred until execution; known incompatible types
508    /// fail preflight. Selector/key errors use `field`'s range.
509    GroupBy {
510        rows: Box<ValueExpression<Q>>,
511        field: Located<String>,
512    },
513    /// Preflight both operands; run value only after a true fixed Boolean.
514    /// A false condition fails at this node with the literal message.
515    Require {
516        condition: Box<ValueExpression<Q>>,
517        value: Box<ValueExpression<Q>>,
518        message: Located<String>,
519    },
520    Range {
521        lower: Box<ValueExpression<Q>>,
522        upper: Box<ValueExpression<Q>>,
523    },
524    /// Conditions are checked in order. The final else is mandatory.
525    Conditional {
526        branches: Vec<(ValueExpression<Q>, ValueExpression<Q>)>,
527        otherwise: Box<ValueExpression<Q>>,
528    },
529    /// Initialize fresh state names in the outer scope, then repeat the body.
530    /// Every body path ends in `Break` or `Continue`. State contracts stay fixed;
531    /// each complete next state is evaluated against the old state and admitted
532    /// before any slot changes. Textual host syntax is a separate frontend step.
533    ///
534    /// This callable accumulator runs 10,000 transitions at active depth one:
535    ///
536    /// ```
537    /// use zirql::{Bindings, Budget, ConstructedValue as V, DeclaredType as D,
538    ///     FunctionDeclaration, FunctionLimits, Located, ProgramLimits,
539    ///     ValueBinaryOperator as B, ValueExpression as E, ValueExpressionKind as K};
540    /// fn node(kind: K) -> E { Located::new(kind, 0..1) }
541    /// fn int(value: i64) -> E { node(K::Literal(V::Integer(value))) }
542    /// fn name(value: &str) -> E { node(K::Name(value.into())) }
543    /// fn binary(operator: B, left: E, right: E) -> E {
544    ///     node(K::Binary { operator, left: Box::new(left), right: Box::new(right) })
545    /// }
546    /// let body = node(K::Loop {
547    ///     states: vec![(Located::new("n".into(), 0..1), int(10_000)),
548    ///                  (Located::new("total".into(), 0..1), int(0))],
549    ///     body: Box::new(node(K::Conditional {
550    ///         branches: vec![(binary(B::Equal, name("n"), int(0)),
551    ///                         node(K::Break(Box::new(name("total")))))],
552    ///         otherwise: Box::new(node(K::Continue(vec![
553    ///             binary(B::Subtract, name("n"), int(1)),
554    ///             binary(B::Add, name("total"), name("n")),
555    ///         ]))),
556    ///     })),
557    /// });
558    /// let functions = [FunctionDeclaration {
559    ///     name: Located::new("accumulate".into(), 0..1), parameters: vec![],
560    ///     result: Located::new(D::Integer, 0..1), bindings: vec![], value: body,
561    /// }];
562    /// let entry = node(K::Call { function: Located::new("accumulate".into(), 0..1), arguments: vec![] });
563    /// let mut budget = Budget::for_program(ProgramLimits {
564    ///     work: 20_000_000, results: 2_000_000, nesting: 64, stream_items: 100,
565    /// });
566    /// let result = zirql::evaluate_functions(&functions, &entry, &Bindings::default(),
567    ///     &mut budget, FunctionLimits { active_depth: 1 })?;
568    /// assert_eq!(result.data(), &V::Integer(50_005_000));
569    /// # Ok::<(), zirql::FunctionError<zirql::QueryError>>(())
570    /// ```
571    Loop {
572        states: Vec<(Located<String>, ValueExpression<Q>)>,
573        body: Box<ValueExpression<Q>>,
574    },
575    /// Return a value from the innermost loop in this function.
576    Break(Box<ValueExpression<Q>>),
577    /// Replace every state slot of the innermost loop simultaneously.
578    Continue(Vec<ValueExpression<Q>>),
579    /// Ordered lexical bindings and a final pure expression.
580    Block {
581        bindings: Vec<(Pattern, ValueExpression<Q>)>,
582        value: Box<ValueExpression<Q>>,
583    },
584    /// First successful pattern/guard, with one final unguarded wildcard.
585    ValueMatch {
586        subject: Box<ValueExpression<Q>>,
587        arms: Vec<ValueMatchArm<Q>>,
588    },
589    Comprehension {
590        constructor: ValueConstructor,
591        clauses: Vec<Located<ValueGenerator<Q>>>,
592        head: Box<ValueExpression<Q>>,
593    },
594}
595
596/// One ordered value-match arm; bindings are local to its guard and result.
597#[derive(Debug, PartialEq)]
598pub struct ValueMatchArm<Q = ()> {
599    pub pattern: Pattern,
600    pub guard: Option<ValueExpression<Q>>,
601    pub value: ValueExpression<Q>,
602}
603
604/// Ordered generator clauses with atomic pattern bindings.
605/// Grouped `In` evaluates its source once and varies the last binder fastest.
606#[derive(Debug, PartialEq)]
607pub enum ValueGenerator<Q = ()> {
608    In {
609        binders: Vec<Pattern>,
610        source: ValueExpression<Q>,
611        filter: Option<ValueExpression<Q>>,
612    },
613    Bind {
614        binder: Pattern,
615        value: ValueExpression<Q>,
616        filter: Option<ValueExpression<Q>>,
617    },
618}
619
620/// A selected argument keeps its immutable owner until the adapter consumes it.
621/// Inspecting the value does not copy it; materialization uses the same budget.
622#[derive(Debug)]
623pub struct ConstructedRead {
624    value: RuntimeValue,
625    location: Location,
626    declaration: Option<DeclaredType<()>>,
627}
628impl ConstructedRead {
629    pub(crate) fn from_runtime(
630        value: RuntimeValue,
631        location: Location,
632        declaration: Option<DeclaredType<()>>,
633    ) -> Self {
634        Self {
635            value,
636            location,
637            declaration,
638        }
639    }
640    pub fn value(&self) -> &ConstructedValue {
641        self.value.as_ref()
642    }
643    /// The selected function result's established contract, when available.
644    /// Borrowing data with `value` retains this metadata separately; materializing
645    /// with `into_owned` installs its transparent constructed annotation.
646    pub fn declared_type(&self) -> Option<&DeclaredType<()>> {
647        self.declaration
648            .as_ref()
649            .or_else(|| declaration(self.value.as_ref()))
650    }
651    /// Materialize an owned argument for a native payload that needs ownership.
652    ///
653    /// # Errors
654    /// Copy/result/nesting refusals retain this argument's source location.
655    pub fn into_owned(self, budget: &mut Budget) -> Result<ConstructedValue, QueryError> {
656        budget.within_evaluation(1, self.location.clone(), |budget| {
657            let value = self.value.materialize(budget, &self.location)?;
658            if let Some(declaration) = self.declaration {
659                annotate(value, declaration, budget, &self.location)
660            } else {
661                Ok(value)
662            }
663        })
664    }
665}
666
667/// Borrowed execution of argument bodies declared by a native query host.
668/// Implementations remain inside the current scalar evaluation invocation.
669pub trait ValueArguments<Q, E: From<QueryError>> {
670    fn evaluate(
671        &mut self,
672        expression: &ValueExpression<Q>,
673        queries: &mut dyn ValueQueryHost<Q, Error = E>,
674        budget: &mut Budget,
675    ) -> Result<ConstructedRead, E>;
676}
677
678/// Native leaves share scalar validation and runtime branch selection. Hosts
679/// supply only normalized-query facts/execution and explicit scalar binding reads.
680/// Generator locals are visible to the native operand under the same budget.
681pub trait ValueQueryHost<Q> {
682    type Error: From<QueryError>;
683
684    /// Facts for the immutable constructed input. Native input is separate.
685    /// Hosts without constructed input keep the located default refusal.
686    fn current_input_types(
687        &mut self,
688        range: &std::ops::Range<usize>,
689        _budget: &mut Budget,
690    ) -> Result<Vec<ValueType>, QueryError> {
691        Err(QueryError::query(
692            range.clone(),
693            "constructed current input is unavailable",
694        ))
695    }
696    /// Supply the constructed input once per invocation. Charge host scans and
697    /// copies to this budget; the evaluator admits and retains the result.
698    fn read_current_input(
699        &mut self,
700        range: &std::ops::Range<usize>,
701        _budget: &mut Budget,
702    ) -> Result<ConstructedValue, Self::Error> {
703        Err(QueryError::query(range.clone(), "constructed current input is unavailable").into())
704    }
705
706    /// Facts for an explicit host predicate frame. Ordinary value evaluations
707    /// have no unnamed argument, even if their bindings contain `_`.
708    fn predicate_subject_types(
709        &mut self,
710        range: &std::ops::Range<usize>,
711        _budget: &mut Budget,
712    ) -> Result<Vec<ValueType>, QueryError> {
713        Err(QueryError::query(
714            range.clone(),
715            "unnamed argument is only available in a predicate",
716        ))
717    }
718    fn read_predicate_subject(
719        &mut self,
720        range: &std::ops::Range<usize>,
721        _budget: &mut Budget,
722    ) -> Result<ConstructedValue, Self::Error> {
723        Err(QueryError::query(
724            range.clone(),
725            "unnamed argument is only available in a predicate",
726        )
727        .into())
728    }
729
730    /// Prepare contextual contracts before visiting an expression's operands.
731    /// Ordinary value hosts need no native computation metadata.
732    fn prepare_computation(
733        &mut self,
734        _expression: &ValueExpression<Q>,
735        _budget: &mut Budget,
736    ) -> Result<(), QueryError> {
737        Ok(())
738    }
739    /// Retain one resolved constructor contract in its prepared owner.
740    fn worklist_id(
741        &mut self,
742        expression: &ValueExpression<Q>,
743        _budget: &mut Budget,
744    ) -> Result<usize, QueryError> {
745        Err(QueryError::query(
746            expression.range.clone(),
747            "computational values require a native function host",
748        ))
749    }
750    /// Resolve selected computational constructors after their operands have
751    /// been checked. Constructed-only runners reject them during preflight.
752    fn worklist_types(
753        &mut self,
754        expression: &ValueExpression<Q>,
755        _budget: &mut Budget,
756    ) -> Result<Vec<ValueType>, QueryError> {
757        Err(QueryError::query(
758            expression.range.clone(),
759            "computational values require a native function host",
760        ))
761    }
762
763    /// Function requests resolve names to stable entries. Ordinary value hosts
764    /// retain their existing behavior and reject calls.
765    fn function(
766        &mut self,
767        name: &Located<String>,
768        _budget: &mut Budget,
769    ) -> Result<(usize, usize), QueryError> {
770        Err(QueryError::query(
771            name.range.clone(),
772            "named functions require a function request",
773        ))
774    }
775    fn function_types(
776        &mut self,
777        name: &Located<String>,
778        budget: &mut Budget,
779    ) -> Result<Vec<ValueType>, QueryError> {
780        self.function(name, budget)?;
781        Ok(vec![ValueType::Unestablished])
782    }
783    /// Resolve a closed named accumulator step and validate its signature.
784    /// Ordinary value requests reject function-dependent collection operations.
785    fn accumulator_function(
786        &mut self,
787        step: &Located<String>,
788        _budget: &mut Budget,
789    ) -> Result<(usize, Vec<ValueType>), QueryError> {
790        Err(QueryError::query(
791            step.range.clone(),
792            "named accumulators require a function request",
793        ))
794    }
795    fn native_value_types(
796        &mut self,
797        _query: &Q,
798        _locals: &[(&str, Vec<ValueType>)],
799        _budget: &mut Budget,
800        range: &std::ops::Range<usize>,
801    ) -> Result<Vec<ValueType>, QueryError> {
802        Err(QueryError::query(
803            range.clone(),
804            "native values require a function request",
805        ))
806    }
807    /// Save/restore host kind facts alongside lexical value scopes.
808    fn enter_scope(
809        &mut self,
810        _budget: &mut Budget,
811        _range: &std::ops::Range<usize>,
812    ) -> Result<(), QueryError> {
813        Ok(())
814    }
815    fn leave_scope(&mut self) {}
816    /// Retain explicitly exposed native child kinds for pattern names.
817    fn prepare_pattern(
818        &mut self,
819        _pattern: &Pattern,
820        _source: &ValueExpression<Q>,
821        _budget: &mut Budget,
822    ) -> Result<(), QueryError> {
823        Ok(())
824    }
825    fn prepare_local(
826        &mut self,
827        _name: &Located<String>,
828        _value: &ValueExpression<Q>,
829        _types: &[ValueType],
830        _budget: &mut Budget,
831    ) -> Result<(), QueryError> {
832        Ok(())
833    }
834    /// Preserve a native sequence's declared singleton kind in lexical binders,
835    /// including when runtime enumeration is empty. Ordinary value hosts need
836    /// no extra metadata beyond constructed element facts. `element` selects an
837    /// `In` source element; `false` binds the whole `Bind` initializer.
838    fn prepare_generator(
839        &mut self,
840        _binder: &Pattern,
841        _source: &ValueExpression<Q>,
842        _types: &[ValueType],
843        _element: bool,
844        _budget: &mut Budget,
845    ) -> Result<(), QueryError> {
846        Ok(())
847    }
848    /// Retain the subject's native kind while a predicate body is prepared.
849    /// `element` selects a source collection element for constructed filtering.
850    fn prepare_predicate(
851        &mut self,
852        _argument: Option<&Pattern>,
853        _source: &ValueExpression<Q>,
854        _types: &[ValueType],
855        _element: bool,
856        _budget: &mut Budget,
857    ) -> Result<(), QueryError> {
858        Ok(())
859    }
860    fn query_types(
861        &mut self,
862        query: &Q,
863        locals: &[(&str, Vec<ValueType>)],
864        budget: &mut Budget,
865        range: &std::ops::Range<usize>,
866    ) -> Result<Vec<ValueType>, QueryError>;
867    fn evaluate_query(
868        &mut self,
869        query: &Q,
870        locals: &[(&str, ConstructedValue)],
871        budget: &mut Budget,
872    ) -> Result<ConstructedValue, Self::Error>;
873    fn read_binding(
874        &mut self,
875        name: &str,
876        range: &std::ops::Range<usize>,
877        budget: &mut Budget,
878    ) -> Result<ConstructedValue, Self::Error>;
879    /// Execute a native operand with immutable argument owners. Existing hosts
880    /// receive admitted owned copies; hosts retaining shared values can override.
881    fn evaluate_query_with_shared_locals(
882        &mut self,
883        query: &Q,
884        locals: &[(&str, std::sync::Arc<ConstructedValue>)],
885        budget: &mut Budget,
886        range: &std::ops::Range<usize>,
887    ) -> Result<ConstructedValue, Self::Error> {
888        let location = Location::Query(range.clone());
889        let mut owned = metadata_vec(locals.len(), budget, &location)?;
890        for (name, value) in locals {
891            owned.push((
892                *name,
893                copy_constructed_value(value, budget, location.clone())?,
894            ));
895        }
896        self.evaluate_query(query, &owned, budget)
897    }
898    /// Expose embedded argument bodies for preparation, without executing them.
899    fn query_arguments<'q>(
900        &self,
901        _query: &'q Q,
902        _budget: &mut Budget,
903        _range: &std::ops::Range<usize>,
904    ) -> Result<Vec<&'q ValueExpression<Q>>, QueryError> {
905        Ok(Vec::new())
906    }
907    /// Execute a native operand with its borrowed, invocation-local argument runner.
908    fn evaluate_query_with_arguments(
909        &mut self,
910        query: &Q,
911        locals: &[(&str, std::sync::Arc<ConstructedValue>)],
912        _arguments: &mut dyn ValueArguments<Q, Self::Error>,
913        budget: &mut Budget,
914        range: &std::ops::Range<usize>,
915    ) -> Result<ConstructedValue, Self::Error> {
916        self.evaluate_query_with_shared_locals(query, locals, budget, range)
917    }
918    /// Share an immutable constructed binding when the host retains an owner.
919    /// The default keeps existing hosts' owned reads; selected shared data still
920    /// receives full runtime admission in this invocation.
921    fn read_shared_binding(
922        &mut self,
923        _name: &str,
924        _range: &std::ops::Range<usize>,
925        _budget: &mut Budget,
926    ) -> Result<Option<std::sync::Arc<ConstructedValue>>, Self::Error> {
927        Ok(None)
928    }
929    fn validate_name(
930        &mut self,
931        _name: &str,
932        _range: &std::ops::Range<usize>,
933        _budget: &mut Budget,
934    ) -> Result<(), QueryError> {
935        Ok(())
936    }
937}
938
939struct NoQueries<'a> {
940    bindings: Option<&'a Bindings<ConstructedValue>>,
941}
942impl ValueQueryHost<()> for NoQueries<'_> {
943    type Error = QueryError;
944    fn query_types(
945        &mut self,
946        _: &(),
947        _: &[(&str, Vec<ValueType>)],
948        _: &mut Budget,
949        range: &std::ops::Range<usize>,
950    ) -> Result<Vec<ValueType>, QueryError> {
951        Err(QueryError::query(
952            range.clone(),
953            "native queries are unavailable in this value expression",
954        ))
955    }
956    fn evaluate_query(
957        &mut self,
958        _: &(),
959        _: &[(&str, ConstructedValue)],
960        _: &mut Budget,
961    ) -> Result<ConstructedValue, QueryError> {
962        Err(QueryError::query(
963            0..0,
964            "native queries are unavailable in this value expression",
965        ))
966    }
967    fn read_shared_binding(
968        &mut self,
969        name: &str,
970        range: &std::ops::Range<usize>,
971        budget: &mut Budget,
972    ) -> Result<Option<std::sync::Arc<ConstructedValue>>, QueryError> {
973        budget.charge(1, Location::Query(range.clone()))?;
974        Ok(Some(
975            self.bindings
976                .expect("runtime value bindings")
977                .shared(name, range.clone())?,
978        ))
979    }
980
981    fn read_binding(
982        &mut self,
983        name: &str,
984        range: &std::ops::Range<usize>,
985        budget: &mut Budget,
986    ) -> Result<ConstructedValue, QueryError> {
987        copy_constructed_value(
988            self.bindings
989                .expect("runtime value bindings")
990                .get(name, range.clone())?,
991            budget,
992            Location::Query(range.clone()),
993        )
994    }
995}
996
997/// Infer every possible result type, including schemas with no runtime rows.
998///
999/// # Errors
1000/// Scope, type and budget refusals retain their expression locations.
1001pub fn value_expression_types(
1002    expression: &ValueExpression,
1003    binding_types: &BTreeMap<String, ValueType>,
1004    budget: &mut Budget,
1005) -> Result<Vec<ValueType>, QueryError> {
1006    Analysis::new(
1007        Globals::Types(binding_types),
1008        &mut NoQueries { bindings: None },
1009    )
1010    .walk(expression, budget)
1011}
1012
1013/// Infer shared value facts with registered native query leaves.
1014///
1015/// # Errors
1016/// All branches and local scopes are preflighted before native execution.
1017pub fn query_value_types<Q, H: ValueQueryHost<Q>>(
1018    expression: &ValueExpression<Q>,
1019    binding_types: &BTreeMap<String, Vec<ValueType>>,
1020    queries: &mut H,
1021    budget: &mut Budget,
1022) -> Result<Vec<ValueType>, QueryError> {
1023    Analysis::new(Globals::Facts(binding_types), queries).walk(expression, budget)
1024}
1025
1026/// Collect first-use value names while validating native leaves and local scopes.
1027///
1028/// # Errors
1029/// Returns located scope/type or resource refusals before execution.
1030pub fn free_query_value_names<Q, H: ValueQueryHost<Q>>(
1031    expression: &ValueExpression<Q>,
1032    binding_types: &BTreeMap<String, Vec<ValueType>>,
1033    queries: &mut H,
1034    budget: &mut Budget,
1035) -> Result<Vec<Located<String>>, QueryError> {
1036    let mut analysis = Analysis::new(Globals::Facts(binding_types), queries);
1037    analysis.walk(expression, budget)?;
1038    Ok(analysis.free)
1039}
1040
1041/// Preflight and evaluate with native leaves through the ordinary scalar runner.
1042///
1043/// # Errors
1044/// Concrete native failures and located shared refusals retain their owner.
1045pub fn evaluate_query_value<Q, H: ValueQueryHost<Q>>(
1046    expression: &ValueExpression<Q>,
1047    binding_types: &BTreeMap<String, Vec<ValueType>>,
1048    queries: &mut H,
1049    budget: &mut Budget,
1050) -> Result<ConstructedValue, H::Error> {
1051    let prepared = prepare_query_value(expression, binding_types, queries, budget)?;
1052    let mut frame = Frame::new(
1053        prepared.names,
1054        budget,
1055        &Location::Query(expression.range.clone()),
1056    )?;
1057    run(
1058        &prepared.expression,
1059        queries,
1060        &mut frame,
1061        budget,
1062        &mut |_, _, _| Ok(()),
1063    )
1064}
1065
1066pub(crate) struct PreparedQueryValue<'a, Q> {
1067    expression: PreparedValue<'a, Q>,
1068    names: Vec<&'a str>,
1069    pub types: Vec<ValueType>,
1070}
1071pub(crate) fn prepare_query_value<'a, Q, H: ValueQueryHost<Q>>(
1072    expression: &'a ValueExpression<Q>,
1073    binding_types: &BTreeMap<String, Vec<ValueType>>,
1074    queries: &mut H,
1075    budget: &mut Budget,
1076) -> Result<PreparedQueryValue<'a, Q>, QueryError> {
1077    let types = query_value_types(expression, binding_types, queries, budget)?;
1078    let mut preparation = Preparation::new();
1079    let expression = preparation.resolve(expression, queries, budget)?;
1080    Ok(PreparedQueryValue {
1081        expression,
1082        names: preparation.names,
1083        types,
1084    })
1085}
1086impl<'a, Q> PreparedQueryValue<'a, Q> {
1087    pub fn frame(&self, budget: &mut Budget) -> Result<Frame<'a>, QueryError> {
1088        let mut names = metadata_vec(
1089            self.names.len(),
1090            budget,
1091            &Location::Query(self.expression.range.clone()),
1092        )?;
1093        names.extend_from_slice(&self.names);
1094        Frame::new(
1095            names,
1096            budget,
1097            &Location::Query(self.expression.range.clone()),
1098        )
1099    }
1100    pub fn evaluate<H: ValueQueryHost<Q>>(
1101        &'a self,
1102        queries: &mut H,
1103        frame: &mut Frame<'a>,
1104        budget: &mut Budget,
1105    ) -> Result<ConstructedValue, H::Error> {
1106        run(&self.expression, queries, frame, budget, &mut |_, _, _| {
1107            Ok(())
1108        })
1109    }
1110}
1111
1112/// Extract one admitted child without copying it.
1113///
1114/// # Errors
1115/// Zero or multiple children fail at the supplied extraction range.
1116pub fn take_one<T>(mut values: Vec<T>, range: std::ops::Range<usize>) -> Result<T, QueryError> {
1117    if values.len() != 1 {
1118        return Err(QueryError::query(range, "one requires exactly one value"));
1119    }
1120    Ok(values.pop().expect("one child"))
1121}
1122
1123/// Convert a native count at the explicit constructed-value boundary.
1124///
1125/// # Errors
1126/// Counts beyond i64 or exhausted allowances fail before allocating a value.
1127pub fn count_value(
1128    count: usize,
1129    budget: &mut Budget,
1130    location: Location,
1131) -> Result<ConstructedValue, QueryError> {
1132    let count =
1133        i64::try_from(count).map_err(|_| error(&location, "count exceeds signed 64-bit range"))?;
1134    scalar(ConstructedValue::Integer(count), budget, &location)
1135}
1136
1137/// Check scopes and all supported type facts, including unchosen branches.
1138/// Missing access remains a runtime failure if that access is executed.
1139///
1140/// # Errors
1141/// Returns a located scope, type, malformed-value or active-budget refusal.
1142pub fn validate_value_expression(
1143    expression: &ValueExpression,
1144    binding_types: &BTreeMap<String, ValueType>,
1145    budget: &mut Budget,
1146) -> Result<(), QueryError> {
1147    Analysis::new(
1148        Globals::Types(binding_types),
1149        &mut NoQueries { bindings: None },
1150    )
1151    .walk(expression, budget)?;
1152    Ok(())
1153}
1154
1155/// Preflight scopes/types and collect distinct global names at their first-use
1156/// ranges. Local generator names and discarded binders are omitted.
1157///
1158/// # Errors
1159/// Returns the same refusals as [`validate_value_expression`]. Metadata and
1160/// copied name bytes spend the supplied budget before allocation.
1161pub fn free_value_names(
1162    expression: &ValueExpression,
1163    binding_types: &BTreeMap<String, ValueType>,
1164    budget: &mut Budget,
1165) -> Result<Vec<Located<String>>, QueryError> {
1166    let mut queries = NoQueries { bindings: None };
1167    let mut analysis = Analysis::new(Globals::Types(binding_types), &mut queries);
1168    analysis.walk(expression, budget)?;
1169    Ok(analysis.free)
1170}
1171
1172/// Discover exact concrete type facts while checking externally built values.
1173/// This includes finite floats, labels, nominal metadata, sets and keyed axes/order.
1174///
1175/// # Errors
1176/// Malformed values and budget exhaustion use the supplied location. Recursive
1177/// facts, metadata slots and copied labels charge work, even with legacy budgets.
1178pub fn constructed_value_type(
1179    value: &ConstructedValue,
1180    budget: &mut Budget,
1181    location: Location,
1182) -> Result<ValueType, QueryError> {
1183    budget.within_evaluation(1, location.clone(), |budget| {
1184        budget.charge(1, location.clone())?;
1185        match value {
1186            ConstructedValue::Absent => Ok(ValueType::Absent),
1187            ConstructedValue::Declared { value, declaration } => {
1188                crate::admit_constructed(value, declaration, budget, location.clone())?;
1189                Ok(ValueType::Declared {
1190                    actual: Box::new(constructed_value_type(value, budget, location.clone())?),
1191                    declaration: copy_declaration(declaration, budget, &location)?,
1192                })
1193            }
1194            ConstructedValue::Boolean(_) => Ok(ValueType::Boolean),
1195            ConstructedValue::Integer(_) => Ok(ValueType::Integer),
1196            ConstructedValue::Float(value) => {
1197                finite(*value, &location)?;
1198                Ok(ValueType::Float)
1199            }
1200            ConstructedValue::String(_) => Ok(ValueType::String),
1201            ConstructedValue::Member(member) => {
1202                member_bytes(member, budget, &location)?;
1203                Ok(ValueType::Member(member.type_id))
1204            }
1205            ConstructedValue::KeyedArray { axes, entries } => {
1206                check_keyed(axes, entries, budget, &location)?;
1207                let axes = axis_types(axes, budget, &location)?;
1208                let mut elements = metadata_vec(entries.len(), budget, &location)?;
1209                for entry in entries {
1210                    elements.push(constructed_value_type(
1211                        &entry.value,
1212                        budget,
1213                        location.clone(),
1214                    )?);
1215                }
1216                Ok(ValueType::KeyedArray(KeyedArrayType {
1217                    axes: Some(axes),
1218                    elements,
1219                }))
1220            }
1221            ConstructedValue::Array(values) | ConstructedValue::Tuple(values) => {
1222                let mut types = metadata_vec(values.len(), budget, &location)?;
1223                for value in values {
1224                    types.push(constructed_value_type(value, budget, location.clone())?);
1225                }
1226                Ok(if matches!(value, ConstructedValue::Array(_)) {
1227                    ValueType::Array(ArrayType::Positions(types))
1228                } else {
1229                    ValueType::Tuple(types)
1230                })
1231            }
1232            ConstructedValue::Record(fields) => {
1233                check_value_labels(fields, budget, &location)?;
1234                let mut types = metadata_vec(fields.len(), budget, &location)?;
1235                for (label, value) in fields {
1236                    budget.charge(label.len(), location.clone())?;
1237                    let ty = constructed_value_type(value, budget, location.clone())?;
1238                    types.push((label.to_owned(), ty));
1239                }
1240                Ok(ValueType::Record(types))
1241            }
1242            ConstructedValue::Set {
1243                element_type,
1244                elements,
1245            } => {
1246                check_set(*element_type, elements, budget, &location)?;
1247                Ok(ValueType::Set(*element_type))
1248            }
1249        }
1250    })
1251}
1252
1253/// Validate referenced binding types, then evaluate a pure expression. Default
1254/// fallbacks use available metadata until selected, without scanning their data.
1255/// All branches are validated before runtime choice. Execution evaluates only
1256/// needed Boolean operands and selected conditional branches. The host owns
1257/// source projections and subsequent rendering with this same budget.
1258///
1259/// # Errors
1260/// Returns located scope/type/access/arithmetic or budget refusals and no partial
1261/// value. Errors restore active nesting/call state without refunding spending.
1262pub fn evaluate_value(
1263    expression: &ValueExpression,
1264    bindings: &Bindings<ConstructedValue>,
1265    budget: &mut Budget,
1266) -> Result<ConstructedValue, QueryError> {
1267    evaluate_value_with_host_validation(expression, bindings, budget, |_, _, _| Ok(()))
1268}
1269
1270/// Evaluate with a trusted host's value policy before runtime parent consumption.
1271/// The check runs on each successful runtime value inside the active evaluation
1272/// guard, including source and binding copies. It can inspect nested native
1273/// values and must charge its work to the supplied budget. Runtime-skipped
1274/// branches do not run the check; all branches still receive static validation.
1275/// No check is retained after this evaluation, and generic value policy remains
1276/// unchanged when callers use [`evaluate_value`].
1277///
1278/// # Errors
1279/// Returns ordinary evaluation or host-check refusals with their locations and
1280/// no partial value. Nesting and local scopes restore without refunding spending.
1281pub fn evaluate_value_with_host_validation(
1282    expression: &ValueExpression,
1283    bindings: &Bindings<ConstructedValue>,
1284    budget: &mut Budget,
1285    mut check_value: impl FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
1286) -> Result<ConstructedValue, QueryError> {
1287    let mut queries = NoQueries {
1288        bindings: Some(bindings),
1289    };
1290    let mut analysis = Analysis::new(Globals::Available(bindings), &mut queries);
1291    analysis.walk(expression, budget)?;
1292    let mut types = BTreeMap::new();
1293    for name in analysis.free {
1294        let location = Location::Query(name.range.clone());
1295        let mut fallback_only = false;
1296        for candidate in &analysis.fallback_names {
1297            budget.charge(
1298                1 + candidate.value.len().min(name.value.len()),
1299                location.clone(),
1300            )?;
1301            if candidate.value == name.value {
1302                fallback_only = true;
1303                break;
1304            }
1305        }
1306        budget.charge(1, location.clone())?;
1307        admit_diagnostic("undefined binding: ", &name.value, budget, &location)?;
1308        let value = bindings.get(&name.value, name.range)?;
1309        let ty = if !fallback_only {
1310            constructed_value_type(value, budget, location)?
1311        } else {
1312            shallow_type(value, budget, &location)?
1313        };
1314        types.insert(name.value, ty);
1315    }
1316    validate_value_expression(expression, &types, budget)?;
1317    let mut preparation = Preparation::new();
1318    let prepared = preparation.resolve(expression, &mut queries, budget)?;
1319    let mut frame = Frame::new(
1320        preparation.names,
1321        budget,
1322        &Location::Query(expression.range.clone()),
1323    )?;
1324    run(
1325        &prepared,
1326        &mut NoQueries {
1327            bindings: Some(bindings),
1328        },
1329        &mut frame,
1330        budget,
1331        &mut check_value,
1332    )
1333}
1334
1335/// Copy a constructed binding or emission snapshot under the active allowance.
1336/// A recursive invariant/size scan admits all result units and copied bytes
1337/// before the allocation walk. Repeated snapshots spend again.
1338///
1339/// # Errors
1340/// Malformed values or budget exhaustion fail at `location`; there is no deep
1341/// uncharged copy and no refund of completed scan/admission work.
1342pub fn copy_constructed_value(
1343    value: &ConstructedValue,
1344    budget: &mut Budget,
1345    location: Location,
1346) -> Result<ConstructedValue, QueryError> {
1347    let (units, bytes) = copy_size(value, budget, &location)?;
1348    budget.charge(bytes, location.clone())?;
1349    budget.charge_results(units, location.clone())?;
1350    copy_admitted(value, budget, &location)
1351}
1352
1353impl ConstructedValue {
1354    /// Borrow data through its transparent declaration annotation.
1355    pub fn data(&self) -> &Self {
1356        let mut value = self;
1357        while let Self::Declared { value: inner, .. } = value {
1358            value = inner;
1359        }
1360        value
1361    }
1362
1363    /// Whether this value is present, including false, zero and empty data.
1364    pub fn occurs(&self) -> bool {
1365        !matches!(self.data(), Self::Absent)
1366    }
1367
1368    /// Consume transparent annotations when crossing an explicit data boundary.
1369    pub fn into_data(self) -> Self {
1370        let mut value = self;
1371        while let Self::Declared { value: inner, .. } = value {
1372            value = *inner;
1373        }
1374        value
1375    }
1376}
1377
1378fn declaration(value: &ConstructedValue) -> Option<&DeclaredType<()>> {
1379    match value {
1380        ConstructedValue::Declared { declaration, .. } => Some(declaration),
1381        _ => None,
1382    }
1383}
1384
1385fn underlying<K>(mut declaration: &DeclaredType<K>) -> &DeclaredType<K> {
1386    while let DeclaredType::Optional(child) = declaration {
1387        declaration = child;
1388    }
1389    declaration
1390}
1391
1392fn declared_facts(
1393    declaration: &DeclaredType<()>,
1394    budget: &mut Budget,
1395    location: &Location,
1396) -> Result<ValueType, QueryError> {
1397    budget.within_evaluation(1, location.clone(), |budget| {
1398        budget.charge(1, location.clone())?;
1399        Ok(match declaration {
1400            DeclaredType::Optional(child) => declared_facts(child, budget, location)?,
1401            DeclaredType::Boolean => ValueType::Boolean,
1402            DeclaredType::Integer => ValueType::Integer,
1403            DeclaredType::Float => ValueType::Float,
1404            DeclaredType::String => ValueType::String,
1405            DeclaredType::Member(id) => ValueType::Member(*id),
1406            DeclaredType::Set(element) => ValueType::Set(Some(*element)),
1407            DeclaredType::Array(child) => ValueType::ArrayKind(one_type(
1408                declared_fact(child, ValueType::Unestablished, budget, location)?,
1409                budget,
1410                location,
1411            )?),
1412            DeclaredType::Sequence(child) => ValueType::Array(ArrayType::Elements(one_type(
1413                declared_fact(child, ValueType::Unestablished, budget, location)?,
1414                budget,
1415                location,
1416            )?)),
1417            DeclaredType::Keyed(child) => ValueType::KeyedArray(KeyedArrayType {
1418                axes: None,
1419                elements: one_type(
1420                    declared_fact(child, ValueType::Unestablished, budget, location)?,
1421                    budget,
1422                    location,
1423                )?,
1424            }),
1425            DeclaredType::Tuple(children) => {
1426                let mut fields = metadata_vec(children.len(), budget, location)?;
1427                for child in children {
1428                    fields.push(declared_fact(
1429                        child,
1430                        ValueType::Unestablished,
1431                        budget,
1432                        location,
1433                    )?);
1434                }
1435                ValueType::Tuple(fields)
1436            }
1437            DeclaredType::Record(children) => {
1438                let mut fields = metadata_vec(children.len(), budget, location)?;
1439                for (label, child) in children {
1440                    budget.charge(label.len(), location.clone())?;
1441                    fields.push((
1442                        label.clone(),
1443                        declared_fact(child, ValueType::Unestablished, budget, location)?,
1444                    ));
1445                }
1446                ValueType::Record(fields)
1447            }
1448            DeclaredType::Native(()) | DeclaredType::Queue(_) | DeclaredType::IdentitySet(()) => {
1449                return Err(error(location, "native declaration requires a host"));
1450            }
1451        })
1452    })
1453}
1454
1455fn declared_fact(
1456    declaration: &DeclaredType<()>,
1457    actual: ValueType,
1458    budget: &mut Budget,
1459    location: &Location,
1460) -> Result<ValueType, QueryError> {
1461    budget.charge(1, location.clone())?;
1462    Ok(ValueType::Declared {
1463        actual: Box::new(actual),
1464        declaration: copy_declaration(declaration, budget, location)?,
1465    })
1466}
1467
1468// Spread a container's declaration onto selected values, without inventing a
1469// schema for unannotated structural data.
1470fn shape_type(
1471    ty: ValueType,
1472    budget: &mut Budget,
1473    location: &Location,
1474) -> Result<ValueType, QueryError> {
1475    let ValueType::Declared {
1476        actual,
1477        declaration,
1478    } = ty
1479    else {
1480        return Ok(ty);
1481    };
1482    let actual = match *actual {
1483        ValueType::Unestablished => return declared_facts(&declaration, budget, location),
1484        ValueType::ScalarKind
1485            if matches!(
1486                underlying(&declaration),
1487                DeclaredType::Boolean
1488                    | DeclaredType::Integer
1489                    | DeclaredType::Float
1490                    | DeclaredType::String
1491            ) =>
1492        {
1493            return declared_facts(&declaration, budget, location);
1494        }
1495        actual => shape_type(actual, budget, location)?,
1496    };
1497    Ok(match (actual, underlying(&declaration)) {
1498        (ValueType::Array(array), DeclaredType::Array(child) | DeclaredType::Sequence(child)) => {
1499            let positional = matches!(array, ArrayType::Positions(_));
1500            let (ArrayType::Positions(entries) | ArrayType::Elements(entries)) = array;
1501            let mut fields = metadata_vec(entries.len(), budget, location)?;
1502            for entry in entries {
1503                fields.push(declared_fact(child, entry, budget, location)?);
1504            }
1505            if fields.is_empty() {
1506                push_metadata(
1507                    &mut fields,
1508                    declared_fact(child, ValueType::Unestablished, budget, location)?,
1509                    budget,
1510                    location,
1511                )?;
1512                return Ok(ValueType::Array(ArrayType::Elements(fields)));
1513            }
1514            ValueType::Array(if positional {
1515                ArrayType::Positions(fields)
1516            } else {
1517                ArrayType::Elements(fields)
1518            })
1519        }
1520        (
1521            ValueType::KeyedArray(mut keyed),
1522            DeclaredType::Array(child) | DeclaredType::Keyed(child),
1523        ) => {
1524            let mut fields = metadata_vec(keyed.elements.len(), budget, location)?;
1525            for entry in keyed.elements {
1526                fields.push(declared_fact(child, entry, budget, location)?);
1527            }
1528            if fields.is_empty() {
1529                push_metadata(
1530                    &mut fields,
1531                    declared_fact(child, ValueType::Unestablished, budget, location)?,
1532                    budget,
1533                    location,
1534                )?;
1535            }
1536            keyed.elements = fields;
1537            ValueType::KeyedArray(keyed)
1538        }
1539        (ValueType::Tuple(entries), DeclaredType::Tuple(children)) => {
1540            let mut fields = metadata_vec(entries.len(), budget, location)?;
1541            for (entry, child) in entries.into_iter().zip(children) {
1542                fields.push(declared_fact(child, entry, budget, location)?);
1543            }
1544            ValueType::Tuple(fields)
1545        }
1546        (ValueType::Record(entries), DeclaredType::Record(children)) => {
1547            let mut fields = metadata_vec(entries.len(), budget, location)?;
1548            for (label, entry) in entries {
1549                let child = record_declaration(children, &label, budget, location)?;
1550                fields.push((label, declared_fact(child, entry, budget, location)?));
1551            }
1552            ValueType::Record(fields)
1553        }
1554        (actual, _) => actual,
1555    })
1556}
1557
1558fn record_declaration<'a, K>(
1559    fields: &'a [(String, DeclaredType<K>)],
1560    label: &str,
1561    budget: &mut Budget,
1562    location: &Location,
1563) -> Result<&'a DeclaredType<K>, QueryError> {
1564    for (candidate, declaration) in fields {
1565        budget.charge(1 + label.len().min(candidate.len()), location.clone())?;
1566        if label == candidate {
1567            return Ok(declaration);
1568        }
1569    }
1570    Err(error(location, "record label outside declared schema"))
1571}
1572
1573fn expose(
1574    value: ConstructedValue,
1575    budget: &mut Budget,
1576    location: &Location,
1577) -> Result<ConstructedValue, QueryError> {
1578    let ConstructedValue::Declared { value, declaration } = value else {
1579        return Ok(value);
1580    };
1581    let mut value = expose(*value, budget, location)?;
1582    match (&mut value, underlying(&declaration)) {
1583        (
1584            ConstructedValue::Array(values),
1585            DeclaredType::Array(child) | DeclaredType::Sequence(child),
1586        ) => {
1587            for value in values {
1588                annotate_child(value, child, budget, location)?;
1589            }
1590        }
1591        (ConstructedValue::Tuple(values), DeclaredType::Tuple(children)) => {
1592            for (value, child) in values.iter_mut().zip(children) {
1593                annotate_child(value, child, budget, location)?;
1594            }
1595        }
1596        (ConstructedValue::Record(values), DeclaredType::Record(children)) => {
1597            for (label, value) in values {
1598                annotate_child(
1599                    value,
1600                    record_declaration(children, label, budget, location)?,
1601                    budget,
1602                    location,
1603                )?;
1604            }
1605        }
1606        (
1607            ConstructedValue::KeyedArray { entries, .. },
1608            DeclaredType::Array(child) | DeclaredType::Keyed(child),
1609        ) => {
1610            for entry in entries {
1611                annotate_child(&mut entry.value, child, budget, location)?;
1612            }
1613        }
1614        _ => {}
1615    }
1616    Ok(value)
1617}
1618
1619fn annotate_child(
1620    value: &mut ConstructedValue,
1621    declaration: &DeclaredType<()>,
1622    budget: &mut Budget,
1623    location: &Location,
1624) -> Result<(), QueryError> {
1625    let declaration = copy_declaration(declaration, budget, location)?;
1626    let child = std::mem::replace(value, ConstructedValue::Absent);
1627    *value = annotate(child, declaration, budget, location)?;
1628    Ok(())
1629}
1630
1631fn shallow_type(
1632    value: &ConstructedValue,
1633    budget: &mut Budget,
1634    location: &Location,
1635) -> Result<ValueType, QueryError> {
1636    Ok(match value {
1637        ConstructedValue::Declared { declaration, .. } => {
1638            declared_fact(declaration, ValueType::Unestablished, budget, location)?
1639        }
1640        ConstructedValue::Absent => ValueType::Absent,
1641        ConstructedValue::Boolean(_) => ValueType::Boolean,
1642        ConstructedValue::Integer(_) => ValueType::Integer,
1643        ConstructedValue::Float(value) => {
1644            finite(*value, location)?;
1645            ValueType::Float
1646        }
1647        ConstructedValue::String(_) => ValueType::String,
1648        ConstructedValue::Member(member) => ValueType::Member(member.type_id),
1649        ConstructedValue::Array(_) => ValueType::Array(ArrayType::Elements(Vec::new())),
1650        ConstructedValue::Tuple(_) => ValueType::TupleKind,
1651        ConstructedValue::Record(_) => ValueType::RecordKind,
1652        ConstructedValue::Set { .. } => ValueType::Set(None),
1653        ConstructedValue::KeyedArray { .. } => ValueType::KeyedArray(KeyedArrayType {
1654            axes: None,
1655            elements: Vec::new(),
1656        }),
1657    })
1658}
1659
1660fn access_type(
1661    ty: ValueType,
1662    keys: &[ValueType],
1663    label: Option<&str>,
1664    optional: bool,
1665    budget: &mut Budget,
1666    location: &Location,
1667) -> Result<Vec<ValueType>, QueryError> {
1668    let mut child = None;
1669    if let ValueType::Declared { declaration, .. } = &ty {
1670        child = match underlying(declaration) {
1671            DeclaredType::Record(fields) => match label {
1672                Some(label) => Some(copy_declaration(
1673                    record_declaration(fields, label, budget, location)?,
1674                    budget,
1675                    location,
1676                )?),
1677                None => None,
1678            },
1679            DeclaredType::Array(element)
1680            | DeclaredType::Sequence(element)
1681            | DeclaredType::Keyed(element) => Some(copy_declaration(element, budget, location)?),
1682            _ => None,
1683        };
1684    }
1685    let mut result = Vec::new();
1686    match shape_type(ty, budget, location)? {
1687        record @ (ValueType::RecordKind | ValueType::Record(_)) => {
1688            for key in keys {
1689                let key = shape_type(copy_type(key, budget, location)?, budget, location)?;
1690                if !matches!(
1691                    key,
1692                    ValueType::String | ValueType::ScalarKind | ValueType::Unestablished
1693                ) {
1694                    return Err(error(location, "record lookup needs a string label"));
1695                }
1696            }
1697            if let ValueType::Record(fields) = record {
1698                for (candidate, ty) in fields {
1699                    budget.charge(
1700                        1 + label.map_or(0, |label| label.len().min(candidate.len())),
1701                        location.clone(),
1702                    )?;
1703                    if label.is_none_or(|label| label == candidate) {
1704                        push_metadata(&mut result, ty, budget, location)?;
1705                    }
1706                }
1707            } else {
1708                push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
1709            }
1710        }
1711        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements)) => {
1712            for key in keys {
1713                require_type(key, ScalarRequirement::Integer, location)?;
1714            }
1715            result = elements;
1716        }
1717        ValueType::ArrayKind(elements) => {
1718            let mut facts = None;
1719            merge_key_facts(&mut facts, keys, budget, location)?;
1720            result = elements;
1721        }
1722        ValueType::KeyedArray(keyed) => {
1723            let mut facts = None;
1724            merge_key_facts(&mut facts, keys, budget, location)?;
1725            if let Some(axes) = &keyed.axes {
1726                check_key_facts(&facts, axes, budget, location)?;
1727            }
1728            result = keyed.elements;
1729        }
1730        ValueType::Unestablished => {
1731            push_metadata(&mut result, ValueType::Unestablished, budget, location)?
1732        }
1733        _ => return Err(error(location, "lookup needs a record or array")),
1734    }
1735    if let Some(child) = child {
1736        return one_type(
1737            declared_fact(
1738                &if optional {
1739                    DeclaredType::Optional(Box::new(child))
1740                } else {
1741                    child
1742                },
1743                ValueType::Unestablished,
1744                budget,
1745                location,
1746            )?,
1747            budget,
1748            location,
1749        );
1750    }
1751    if optional {
1752        push_metadata(&mut result, ValueType::Absent, budget, location)?;
1753    }
1754    Ok(result)
1755}
1756
1757fn access_declaration<'a, K>(
1758    declaration: &'a DeclaredType<K>,
1759    key: &ConstructedValue,
1760    budget: &mut Budget,
1761    location: &Location,
1762) -> Result<Option<&'a DeclaredType<K>>, QueryError> {
1763    Ok(match underlying(declaration) {
1764        DeclaredType::Record(fields) => {
1765            let ConstructedValue::String(label) = key.data() else {
1766                return Err(error(location, "record lookup needs a string label"));
1767            };
1768            Some(record_declaration(fields, label, budget, location)?)
1769        }
1770        DeclaredType::Array(child) | DeclaredType::Sequence(child) | DeclaredType::Keyed(child) => {
1771            Some(child)
1772        }
1773        _ => None,
1774    })
1775}
1776
1777fn optional_position(
1778    value: &ConstructedValue,
1779    key: ConstructedValue,
1780    budget: &mut Budget,
1781    location: &Location,
1782) -> Result<Option<usize>, QueryError> {
1783    match value.data() {
1784        ConstructedValue::Record(fields) => {
1785            let ConstructedValue::String(label) = key.into_data() else {
1786                return Err(error(location, "record lookup needs a string label"));
1787            };
1788            for (index, (candidate, _)) in fields.iter().enumerate() {
1789                budget.charge(1 + label.len().min(candidate.len()), location.clone())?;
1790                if label == *candidate {
1791                    return Ok(Some(index));
1792                }
1793            }
1794            Ok(None)
1795        }
1796        ConstructedValue::Array(values) => {
1797            let position = integer(key, location)?;
1798            budget.charge(1, location.clone())?;
1799            Ok(usize::try_from(position)
1800                .ok()
1801                .and_then(|position| position.checked_sub(1))
1802                .filter(|&index| index < values.len()))
1803        }
1804        ConstructedValue::KeyedArray { axes, entries } => {
1805            let key = value_keys(key.into_data(), budget, location)?;
1806            if key.len() != axes.len() {
1807                return Err(error(location, "key rank mismatch"));
1808            }
1809            for (component, axis) in key.iter().zip(axes) {
1810                budget.charge(1, location.clone())?;
1811                if key_type(component) != axis.key_type {
1812                    return Err(error(location, "incompatible key type"));
1813                }
1814                // Validate nominal metadata even for a key outside the axis.
1815                for candidate in &axis.keys {
1816                    compare_keys(component, candidate, budget, location)?;
1817                }
1818            }
1819            for (index, entry) in entries.iter().enumerate() {
1820                budget.charge(1, location.clone())?;
1821                if equal_keys(&key, &entry.key, budget, location)? {
1822                    return Ok(Some(index));
1823                }
1824            }
1825            Ok(None)
1826        }
1827        _ => Err(error(location, "lookup needs a record or array")),
1828    }
1829}
1830
1831fn error(location: &Location, message: impl Into<String>) -> QueryError {
1832    QueryError {
1833        location: location.clone(),
1834        message: message.into(),
1835        category: QueryErrorCategory::Other,
1836    }
1837}
1838
1839// Availability checks can discard a Bindings::get error. Admit its potential
1840// diagnostic before lookup as well as messages that are actually returned.
1841fn admit_diagnostic(
1842    prefix: &str,
1843    value: &str,
1844    budget: &mut Budget,
1845    location: &Location,
1846) -> Result<(), QueryError> {
1847    let len = sum(prefix.len(), value.len(), location)?;
1848    allocation::<u8>(len, location)?;
1849    budget.charge(len, location.clone())
1850}
1851
1852fn sum(a: usize, b: usize, location: &Location) -> Result<usize, QueryError> {
1853    a.checked_add(b)
1854        .ok_or_else(|| error(location, "allocation size overflow"))
1855}
1856
1857pub(crate) fn allocation<T>(len: usize, location: &Location) -> Result<(), QueryError> {
1858    if len
1859        .checked_mul(size_of::<T>())
1860        .is_none_or(|size| size > isize::MAX as usize)
1861    {
1862        return Err(error(location, "allocation size overflow"));
1863    }
1864    Ok(())
1865}
1866
1867fn metadata_vec<T>(
1868    len: usize,
1869    budget: &mut Budget,
1870    location: &Location,
1871) -> Result<Vec<T>, QueryError> {
1872    allocation::<T>(len, location)?;
1873    budget.charge(len, location.clone())?;
1874    Ok(Vec::with_capacity(len))
1875}
1876
1877fn push_metadata<T>(
1878    values: &mut Vec<T>,
1879    value: T,
1880    budget: &mut Budget,
1881    location: &Location,
1882) -> Result<(), QueryError> {
1883    allocation::<T>(sum(values.len(), 1, location)?, location)?;
1884    budget.charge(1, location.clone())?;
1885    values.push(value);
1886    Ok(())
1887}
1888
1889fn admit_container<T>(
1890    len: usize,
1891    budget: &mut Budget,
1892    location: &Location,
1893) -> Result<Vec<T>, QueryError> {
1894    allocation::<T>(len, location)?;
1895    budget.charge(sum(len, 1, location)?, location.clone())?;
1896    budget.charge_results(sum(len, 1, location)?, location.clone())?;
1897    Ok(Vec::with_capacity(len))
1898}
1899
1900fn finite(value: f64, location: &Location) -> Result<(), QueryError> {
1901    if !value.is_finite() {
1902        return Err(error(location, "nonfinite float"));
1903    }
1904    Ok(())
1905}
1906
1907fn member_bytes(
1908    member: &NominalMember,
1909    budget: &mut Budget,
1910    location: &Location,
1911) -> Result<usize, QueryError> {
1912    allocation::<u8>(member.type_name.len(), location)?;
1913    allocation::<u8>(member.member_name.len(), location)?;
1914    let bytes = sum(member.type_name.len(), member.member_name.len(), location)?;
1915    budget.charge(sum(1, bytes, location)?, location.clone())?;
1916    Ok(bytes)
1917}
1918
1919pub(crate) fn check_member_pair(
1920    left: &NominalMember,
1921    right: &NominalMember,
1922    budget: &mut Budget,
1923    location: &Location,
1924) -> Result<(), QueryError> {
1925    budget.charge(1, location.clone())?;
1926    if left.type_id != right.type_id {
1927        return Ok(());
1928    }
1929    budget.charge(
1930        left.type_name.len().min(right.type_name.len()),
1931        location.clone(),
1932    )?;
1933    if left.type_name != right.type_name {
1934        return Err(error(location, "inconsistent nominal type name"));
1935    }
1936    if left.member_id == right.member_id {
1937        budget.charge(
1938            left.member_name.len().min(right.member_name.len()),
1939            location.clone(),
1940        )?;
1941        if left.declaration_order != right.declaration_order
1942            || left.member_name != right.member_name
1943        {
1944            return Err(error(location, "inconsistent nominal member metadata"));
1945        }
1946    } else if left.declaration_order == right.declaration_order {
1947        return Err(error(
1948            location,
1949            "distinct nominal members share declaration order",
1950        ));
1951    }
1952    Ok(())
1953}
1954
1955fn copy_member_admitted(member: &NominalMember) -> NominalMember {
1956    NominalMember {
1957        type_id: member.type_id,
1958        member_id: member.member_id,
1959        declaration_order: member.declaration_order,
1960        type_name: member.type_name.to_owned(),
1961        member_name: member.member_name.to_owned(),
1962    }
1963}
1964
1965fn key_value(key: ValueKey) -> ConstructedValue {
1966    match key {
1967        ValueKey::Integer(value) => ConstructedValue::Integer(value),
1968        ValueKey::Member(member) => ConstructedValue::Member(member),
1969    }
1970}
1971
1972fn key_type(key: &ValueKey) -> ValueKeyType {
1973    match key {
1974        ValueKey::Integer(_) => ValueKeyType::Integer,
1975        ValueKey::Member(member) => ValueKeyType::Member(member.type_id),
1976    }
1977}
1978
1979fn key_set_type(ty: ValueKeyType) -> SetElementType {
1980    match ty {
1981        ValueKeyType::Integer => SetElementType::Integer,
1982        ValueKeyType::Member(id) => SetElementType::Member(id),
1983    }
1984}
1985
1986fn compare_keys(
1987    left: &ValueKey,
1988    right: &ValueKey,
1989    budget: &mut Budget,
1990    location: &Location,
1991) -> Result<Ordering, QueryError> {
1992    budget.charge(1, location.clone())?;
1993    match (left, right) {
1994        (ValueKey::Integer(a), ValueKey::Integer(b)) => Ok(a.cmp(b)),
1995        (ValueKey::Member(a), ValueKey::Member(b)) if a.type_id == b.type_id => {
1996            check_member_pair(a, b, budget, location)?;
1997            Ok(if a.member_id == b.member_id {
1998                Ordering::Equal
1999            } else {
2000                a.declaration_order.cmp(&b.declaration_order)
2001            })
2002        }
2003        _ => Err(error(location, "incompatible keyed dimension type")),
2004    }
2005}
2006
2007fn keys_size(
2008    keys: &[ValueKey],
2009    budget: &mut Budget,
2010    location: &Location,
2011) -> Result<(usize, usize), QueryError> {
2012    allocation::<ValueKey>(keys.len(), location)?;
2013    let units = keys
2014        .len()
2015        .checked_mul(2)
2016        .ok_or_else(|| error(location, "key size overflow"))?;
2017    let mut bytes = 0;
2018    for key in keys {
2019        budget.charge(1, location.clone())?;
2020        if let ValueKey::Member(member) = key {
2021            bytes = sum(bytes, member_bytes(member, budget, location)?, location)?;
2022        }
2023    }
2024    Ok((units, bytes))
2025}
2026
2027fn copy_keys_admitted(
2028    keys: &[ValueKey],
2029    budget: &mut Budget,
2030    location: &Location,
2031) -> Result<Vec<ValueKey>, QueryError> {
2032    let mut copied = Vec::with_capacity(keys.len());
2033    for key in keys {
2034        budget.charge(1, location.clone())?;
2035        copied.push(match key {
2036            ValueKey::Integer(value) => ValueKey::Integer(*value),
2037            ValueKey::Member(member) => ValueKey::Member(copy_member_admitted(member)),
2038        });
2039    }
2040    Ok(copied)
2041}
2042
2043fn copy_key(
2044    key: &ValueKey,
2045    budget: &mut Budget,
2046    location: &Location,
2047) -> Result<ValueKey, QueryError> {
2048    budget.charge(1, location.clone())?;
2049    budget.charge_results(1, location.clone())?;
2050    Ok(match key {
2051        ValueKey::Integer(value) => ValueKey::Integer(*value),
2052        ValueKey::Member(member) => {
2053            let bytes = member_bytes(member, budget, location)?;
2054            budget.charge(bytes, location.clone())?;
2055            ValueKey::Member(copy_member_admitted(member))
2056        }
2057    })
2058}
2059
2060fn check_axes(
2061    axes: &[KeyAxis],
2062    budget: &mut Budget,
2063    location: &Location,
2064) -> Result<(), QueryError> {
2065    if axes.is_empty() {
2066        return Err(error(
2067            location,
2068            "keyed arrays need nonzero rank and established axes",
2069        ));
2070    }
2071    for axis in axes {
2072        budget.charge(1, location.clone())?;
2073        for (index, key) in axis.keys.iter().enumerate() {
2074            budget.charge(1, location.clone())?;
2075            if key_type(key) != axis.key_type {
2076                return Err(error(location, "axis key type mismatch"));
2077            }
2078            if let ValueKey::Member(member) = key {
2079                member_bytes(member, budget, location)?;
2080            }
2081            for previous in &axis.keys[..index] {
2082                if compare_keys(previous, key, budget, location)? == Ordering::Equal {
2083                    return Err(error(location, "duplicate axis key"));
2084                }
2085            }
2086        }
2087    }
2088    let members = axes
2089        .iter()
2090        .flat_map(|axis| &axis.keys)
2091        .filter_map(|key| match key {
2092            ValueKey::Member(member) => Some(member),
2093            ValueKey::Integer(_) => None,
2094        });
2095    for (index, member) in members.clone().enumerate() {
2096        for previous in members.clone().take(index) {
2097            check_member_pair(previous, member, budget, location)?;
2098        }
2099    }
2100    Ok(())
2101}
2102
2103fn copy_axes(
2104    axes: &[KeyAxis],
2105    budget: &mut Budget,
2106    location: &Location,
2107) -> Result<Vec<KeyAxis>, QueryError> {
2108    check_axes(axes, budget, location)?;
2109    allocation::<KeyAxis>(axes.len(), location)?;
2110    budget.charge(axes.len(), location.clone())?;
2111    budget.charge_results(axes.len(), location.clone())?;
2112    let mut copied = Vec::with_capacity(axes.len());
2113    for axis in axes {
2114        let (units, bytes) = keys_size(&axis.keys, budget, location)?;
2115        budget.charge(bytes, location.clone())?;
2116        budget.charge_results(units, location.clone())?;
2117        copied.push(KeyAxis {
2118            key_type: axis.key_type,
2119            keys: copy_keys_admitted(&axis.keys, budget, location)?,
2120        });
2121    }
2122    Ok(copied)
2123}
2124
2125fn copy_axis_types(
2126    types: &[ValueKeyType],
2127    budget: &mut Budget,
2128    location: &Location,
2129) -> Result<Vec<ValueKeyType>, QueryError> {
2130    if types.is_empty() {
2131        return Err(error(location, "keyed arrays need nonzero rank"));
2132    }
2133    let mut copied = metadata_vec(types.len(), budget, location)?;
2134    for ty in types {
2135        budget.charge(1, location.clone())?;
2136        copied.push(*ty);
2137    }
2138    Ok(copied)
2139}
2140
2141fn axis_types(
2142    axes: &[KeyAxis],
2143    budget: &mut Budget,
2144    location: &Location,
2145) -> Result<Vec<ValueKeyType>, QueryError> {
2146    let mut types = metadata_vec(axes.len(), budget, location)?;
2147    for axis in axes {
2148        budget.charge(1, location.clone())?;
2149        types.push(axis.key_type);
2150    }
2151    Ok(types)
2152}
2153
2154fn same_axis_types(
2155    left: &[ValueKeyType],
2156    right: &[ValueKeyType],
2157    budget: &mut Budget,
2158    location: &Location,
2159) -> Result<bool, QueryError> {
2160    budget.charge(1, location.clone())?;
2161    if left.len() != right.len() {
2162        return Ok(false);
2163    }
2164    for (a, b) in left.iter().zip(right) {
2165        budget.charge(1, location.clone())?;
2166        if a != b {
2167            return Ok(false);
2168        }
2169    }
2170    Ok(true)
2171}
2172
2173fn key_positions(
2174    key: &[ValueKey],
2175    axes: &[KeyAxis],
2176    budget: &mut Budget,
2177    location: &Location,
2178) -> Result<Vec<usize>, QueryError> {
2179    if key.len() != axes.len() {
2180        return Err(error(location, "key rank mismatch"));
2181    }
2182    let mut positions = metadata_vec(key.len(), budget, location)?;
2183    for (key, axis) in key.iter().zip(axes) {
2184        budget.charge(1, location.clone())?;
2185        if key_type(key) != axis.key_type {
2186            return Err(error(location, "incompatible keyed dimension type"));
2187        }
2188        let mut position = None;
2189        // Visit every key so contradictory copies cannot hide beyond a match.
2190        for (index, existing) in axis.keys.iter().enumerate() {
2191            if compare_keys(key, existing, budget, location)? == Ordering::Equal {
2192                position = Some(index);
2193            }
2194        }
2195        positions
2196            .push(position.ok_or_else(|| error(location, "key is absent from established axis"))?);
2197    }
2198    Ok(positions)
2199}
2200
2201fn compare_positions(
2202    left: &[usize],
2203    right: &[usize],
2204    budget: &mut Budget,
2205    location: &Location,
2206) -> Result<Ordering, QueryError> {
2207    for (a, b) in left.iter().zip(right) {
2208        budget.charge(1, location.clone())?;
2209        let order = a.cmp(b);
2210        if order != Ordering::Equal {
2211            return Ok(order);
2212        }
2213    }
2214    Ok(Ordering::Equal)
2215}
2216
2217fn equal_keys(
2218    left: &[ValueKey],
2219    right: &[ValueKey],
2220    budget: &mut Budget,
2221    location: &Location,
2222) -> Result<bool, QueryError> {
2223    budget.charge(1, location.clone())?;
2224    if left.len() != right.len() {
2225        return Ok(false);
2226    }
2227    for (a, b) in left.iter().zip(right) {
2228        if compare_keys(a, b, budget, location)? != Ordering::Equal {
2229            return Ok(false);
2230        }
2231    }
2232    Ok(true)
2233}
2234
2235fn check_keyed(
2236    axes: &[KeyAxis],
2237    entries: &[KeyedEntry],
2238    budget: &mut Budget,
2239    location: &Location,
2240) -> Result<(), QueryError> {
2241    check_axes(axes, budget, location)?;
2242    let mut previous: Option<Vec<usize>> = None;
2243    for entry in entries {
2244        budget.charge(1, location.clone())?;
2245        let positions = key_positions(&entry.key, axes, budget, location)?;
2246        if let Some(previous) = previous
2247            && compare_positions(&previous, &positions, budget, location)? != Ordering::Less
2248        {
2249            return Err(error(
2250                location,
2251                "keyed entries must be unique and in row-major order",
2252            ));
2253        }
2254        previous = Some(positions);
2255    }
2256    let members = axes
2257        .iter()
2258        .flat_map(|axis| &axis.keys)
2259        .chain(entries.iter().flat_map(|entry| &entry.key))
2260        .filter_map(|key| match key {
2261            ValueKey::Member(member) => Some(member),
2262            ValueKey::Integer(_) => None,
2263        });
2264    for (index, member) in members.clone().enumerate() {
2265        for previous in members.clone().take(index) {
2266            check_member_pair(previous, member, budget, location)?;
2267        }
2268    }
2269    Ok(())
2270}
2271
2272fn value_keys(
2273    value: ConstructedValue,
2274    budget: &mut Budget,
2275    location: &Location,
2276) -> Result<Vec<ValueKey>, QueryError> {
2277    let value = expose(value, budget, location)?;
2278    let values = match value {
2279        ConstructedValue::Tuple(values) if !values.is_empty() => values,
2280        value @ (ConstructedValue::Integer(_) | ConstructedValue::Member(_)) => {
2281            let mut values = metadata_vec(1, budget, location)?;
2282            values.push(value);
2283            values
2284        }
2285        _ => {
2286            return Err(error(
2287                location,
2288                "key needs an integer, member or nonempty flat tuple",
2289            ));
2290        }
2291    };
2292    allocation::<ValueKey>(values.len(), location)?;
2293    budget.charge(values.len(), location.clone())?;
2294    budget.charge_results(values.len(), location.clone())?;
2295    let mut keys = Vec::with_capacity(values.len());
2296    for value in values {
2297        budget.charge(1, location.clone())?;
2298        keys.push(match value.into_data() {
2299            ConstructedValue::Integer(value) => ValueKey::Integer(value),
2300            ConstructedValue::Member(member) => ValueKey::Member(member),
2301            _ => return Err(error(location, "key components need integers or members")),
2302        });
2303    }
2304    Ok(keys)
2305}
2306
2307fn infer_axes(
2308    entries: &[Located<KeyedEntry>],
2309    budget: &mut Budget,
2310    location: &Location,
2311) -> Result<Vec<KeyAxis>, QueryError> {
2312    let Some(first) = entries.first() else {
2313        return Err(error(location, "unestablished empty keyed axes"));
2314    };
2315    if first.value.key.is_empty() {
2316        return Err(error(location, "keyed arrays need nonzero rank"));
2317    }
2318    allocation::<KeyAxis>(first.value.key.len(), location)?;
2319    budget.charge(first.value.key.len(), location.clone())?;
2320    budget.charge_results(first.value.key.len(), location.clone())?;
2321    let mut axes = Vec::with_capacity(first.value.key.len());
2322    for key in &first.value.key {
2323        axes.push(KeyAxis {
2324            key_type: key_type(key),
2325            keys: Vec::new(),
2326        });
2327    }
2328    for entry in entries {
2329        let key_location = Location::Query(entry.range.clone());
2330        if entry.value.key.len() != axes.len() {
2331            return Err(error(&key_location, "key rank mismatch"));
2332        }
2333        for (key, axis) in entry.value.key.iter().zip(&mut axes) {
2334            if key_type(key) != axis.key_type {
2335                return Err(error(&key_location, "incompatible keyed dimension type"));
2336            }
2337            let mut position = axis.keys.len();
2338            let mut found = false;
2339            for (index, existing) in axis.keys.iter().enumerate() {
2340                match compare_keys(key, existing, budget, &key_location)? {
2341                    Ordering::Equal => found = true,
2342                    Ordering::Less if position == axis.keys.len() => position = index,
2343                    _ => {}
2344                }
2345            }
2346            if !found {
2347                let key = copy_key(key, budget, &key_location)?;
2348                allocation::<ValueKey>(sum(axis.keys.len(), 1, &key_location)?, &key_location)?;
2349                budget.charge(
2350                    sum(1, axis.keys.len() - position, &key_location)?,
2351                    key_location.clone(),
2352                )?;
2353                budget.charge_results(1, key_location.clone())?;
2354                axis.keys.insert(position, key);
2355            }
2356        }
2357    }
2358    Ok(axes)
2359}
2360
2361fn finish_keyed(
2362    axes: Option<Vec<KeyAxis>>,
2363    mut entries: Vec<Located<KeyedEntry>>,
2364    budget: &mut Budget,
2365    location: &Location,
2366) -> Result<ConstructedValue, QueryError> {
2367    let axes = match axes {
2368        Some(axes) => {
2369            check_axes(&axes, budget, location)?;
2370            axes
2371        }
2372        None => infer_axes(&entries, budget, location)?,
2373    };
2374    let mut positions = metadata_vec(entries.len(), budget, location)?;
2375    for entry in &entries {
2376        positions.push(key_positions(
2377            &entry.value.key,
2378            &axes,
2379            budget,
2380            &Location::Query(entry.range.clone()),
2381        )?);
2382    }
2383    // Insertion sorting admits every comparison and movement; sparse values need
2384    // no Cartesian cardinality or flattened index arithmetic.
2385    for index in 1..entries.len() {
2386        let mut cursor = index;
2387        while cursor > 0 {
2388            let key_location = Location::Query(entries[cursor].range.clone());
2389            match compare_positions(
2390                &positions[cursor - 1],
2391                &positions[cursor],
2392                budget,
2393                &key_location,
2394            )? {
2395                Ordering::Equal => return Err(error(&key_location, "duplicate full key")),
2396                Ordering::Less => break,
2397                Ordering::Greater => {
2398                    budget.charge(2, key_location)?;
2399                    entries.swap(cursor - 1, cursor);
2400                    positions.swap(cursor - 1, cursor);
2401                    cursor -= 1;
2402                }
2403            }
2404        }
2405    }
2406    let mut values = metadata_vec(entries.len(), budget, location)?;
2407    for entry in entries {
2408        values.push(entry.value);
2409    }
2410    check_keyed(&axes, &values, budget, location)?;
2411    Ok(ConstructedValue::KeyedArray {
2412        axes,
2413        entries: values,
2414    })
2415}
2416
2417fn source_axis(
2418    value: ConstructedValue,
2419    budget: &mut Budget,
2420    location: &Location,
2421) -> Result<KeyAxis, QueryError> {
2422    let empty = match value.data() {
2423        ConstructedValue::Array(values)
2424        | ConstructedValue::Set {
2425            elements: values, ..
2426        } => values.is_empty(),
2427        ConstructedValue::KeyedArray { entries, .. } => entries.is_empty(),
2428        _ => false,
2429    };
2430    let declared = if empty && let Some(declaration) = declaration(&value) {
2431        let facts = declared_facts(declaration, budget, location)?;
2432        source_key_type(std::slice::from_ref(&facts), budget, location)?
2433    } else {
2434        None
2435    };
2436    let value = expose(value, budget, location)?;
2437    let (declared, values) = match value {
2438        ConstructedValue::Array(values) => (declared, values),
2439        ConstructedValue::KeyedArray { entries, .. } => {
2440            let mut values = metadata_vec(entries.len(), budget, location)?;
2441            for entry in entries {
2442                budget.charge(1, location.clone())?;
2443                values.push(entry.value);
2444            }
2445            (declared, values)
2446        }
2447        ConstructedValue::Set {
2448            element_type,
2449            elements,
2450        } => {
2451            let ty = match element_type {
2452                Some(SetElementType::Integer) => Some(ValueKeyType::Integer),
2453                Some(SetElementType::Member(id)) => Some(ValueKeyType::Member(id)),
2454                _ => None,
2455            };
2456            (ty.or(declared), elements)
2457        }
2458        _ => {
2459            return Err(error(
2460                location,
2461                "axis source needs an integer or member array/set",
2462            ));
2463        }
2464    };
2465    // The source list admits its axis slots before allocating the axis vector.
2466    // This conversion admits only the new key slots; values move by ownership.
2467    allocation::<ValueKey>(values.len(), location)?;
2468    budget.charge(values.len(), location.clone())?;
2469    budget.charge_results(values.len(), location.clone())?;
2470    let mut keys = Vec::with_capacity(values.len());
2471    let mut ty = declared;
2472    for value in values {
2473        budget.charge(1, location.clone())?;
2474        let key = match value.into_data() {
2475            ConstructedValue::Integer(value) => ValueKey::Integer(value),
2476            ConstructedValue::Member(member) => ValueKey::Member(member),
2477            _ => {
2478                return Err(error(
2479                    location,
2480                    "axis source needs integer or member elements",
2481                ));
2482            }
2483        };
2484        if ty.is_some_and(|ty| ty != key_type(&key)) {
2485            return Err(error(location, "incompatible axis source type"));
2486        }
2487        ty = Some(key_type(&key));
2488        keys.push(key);
2489    }
2490    let axis = KeyAxis {
2491        key_type: ty.ok_or_else(|| error(location, "unestablished empty keyed axis"))?,
2492        keys,
2493    };
2494    check_axes(std::slice::from_ref(&axis), budget, location)?;
2495    Ok(axis)
2496}
2497
2498fn scalar_set_type(value: &ConstructedValue) -> Option<SetElementType> {
2499    match value.data() {
2500        ConstructedValue::Boolean(_) => Some(SetElementType::Boolean),
2501        ConstructedValue::Integer(_) => Some(SetElementType::Integer),
2502        ConstructedValue::Float(_) => Some(SetElementType::Float),
2503        ConstructedValue::Member(member) => Some(SetElementType::Member(member.type_id)),
2504        _ => None,
2505    }
2506}
2507
2508fn compare_set_values(
2509    left: &ConstructedValue,
2510    right: &ConstructedValue,
2511    budget: &mut Budget,
2512    location: &Location,
2513) -> Result<Ordering, QueryError> {
2514    budget.charge(1, location.clone())?;
2515    match (left.data(), right.data()) {
2516        (ConstructedValue::Boolean(a), ConstructedValue::Boolean(b)) => Ok(a.cmp(b)),
2517        (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => Ok(a.cmp(b)),
2518        (ConstructedValue::Float(a), ConstructedValue::Float(b)) => {
2519            finite(*a, location)?;
2520            finite(*b, location)?;
2521            Ok(a.partial_cmp(b).expect("finite floats are ordered"))
2522        }
2523        (ConstructedValue::Member(a), ConstructedValue::Member(b)) => {
2524            check_member_pair(a, b, budget, location)?;
2525            if a.type_id != b.type_id {
2526                return Err(error(location, "incompatible nominal types"));
2527            }
2528            Ok(if a.member_id == b.member_id {
2529                Ordering::Equal
2530            } else {
2531                a.declaration_order.cmp(&b.declaration_order)
2532            })
2533        }
2534        _ => Err(error(
2535            location,
2536            "set elements must have one supported scalar type",
2537        )),
2538    }
2539}
2540
2541fn check_set(
2542    element_type: Option<SetElementType>,
2543    elements: &[ConstructedValue],
2544    budget: &mut Budget,
2545    location: &Location,
2546) -> Result<(), QueryError> {
2547    if element_type.is_none() && !elements.is_empty() {
2548        return Err(error(location, "a nonempty set needs its element type"));
2549    }
2550    for (index, value) in elements.iter().enumerate() {
2551        budget.charge(1, location.clone())?;
2552        if scalar_set_type(value) != element_type {
2553            return Err(error(location, "set element type mismatch"));
2554        }
2555        match value.data() {
2556            ConstructedValue::Float(value) => finite(*value, location)?,
2557            ConstructedValue::Member(member) => {
2558                member_bytes(member, budget, location)?;
2559            }
2560            _ => {}
2561        }
2562        for previous in &elements[..index] {
2563            if compare_set_values(previous, value, budget, location)? == Ordering::Equal {
2564                return Err(error(location, "set elements must be unique"));
2565            }
2566        }
2567    }
2568    Ok(())
2569}
2570
2571fn check_value_labels<T>(
2572    fields: &[(String, T)],
2573    budget: &mut Budget,
2574    location: &Location,
2575) -> Result<(), QueryError> {
2576    for (index, (label, _)) in fields.iter().enumerate() {
2577        for (previous, _) in &fields[..index] {
2578            budget.charge(
2579                sum(1, label.len().min(previous.len()), location)?,
2580                location.clone(),
2581            )?;
2582            if label == previous {
2583                return Err(error(location, "duplicate record label"));
2584            }
2585        }
2586    }
2587    Ok(())
2588}
2589
2590fn copy_size(
2591    value: &ConstructedValue,
2592    budget: &mut Budget,
2593    location: &Location,
2594) -> Result<(usize, usize), QueryError> {
2595    budget.within_evaluation(1, location.clone(), |budget| {
2596        budget.charge(1, location.clone())?;
2597        let mut units = 1;
2598        let mut bytes = 0;
2599        match value {
2600            ConstructedValue::Absent
2601            | ConstructedValue::Boolean(_)
2602            | ConstructedValue::Integer(_) => {}
2603            ConstructedValue::Declared { value, declaration } => {
2604                crate::admit_constructed(value, declaration, budget, location.clone())?;
2605                let (child_units, child_bytes) = copy_size(value, budget, location)?;
2606                units = sum(units, child_units, location)?;
2607                bytes = child_bytes;
2608                // Declarations are copied with their own metadata charges below.
2609            }
2610            ConstructedValue::Float(value) => finite(*value, location)?,
2611            ConstructedValue::Member(member) => {
2612                bytes = member_bytes(member, budget, location)?;
2613            }
2614            ConstructedValue::KeyedArray { axes, entries } => {
2615                check_keyed(axes, entries, budget, location)?;
2616                allocation::<KeyAxis>(axes.len(), location)?;
2617                allocation::<KeyedEntry>(entries.len(), location)?;
2618                units = sum(units, sum(axes.len(), entries.len(), location)?, location)?;
2619                for axis in axes {
2620                    let (key_units, key_bytes) = keys_size(&axis.keys, budget, location)?;
2621                    units = sum(units, key_units, location)?;
2622                    bytes = sum(bytes, key_bytes, location)?;
2623                }
2624                for entry in entries {
2625                    let (key_units, key_bytes) = keys_size(&entry.key, budget, location)?;
2626                    let (child_units, child_bytes) = copy_size(&entry.value, budget, location)?;
2627                    units = sum(units, sum(key_units, child_units, location)?, location)?;
2628                    bytes = sum(bytes, sum(key_bytes, child_bytes, location)?, location)?;
2629                }
2630            }
2631            ConstructedValue::String(value) => {
2632                allocation::<u8>(value.len(), location)?;
2633                bytes = value.len();
2634            }
2635            ConstructedValue::Array(values)
2636            | ConstructedValue::Tuple(values)
2637            | ConstructedValue::Set {
2638                elements: values, ..
2639            } => {
2640                allocation::<ConstructedValue>(values.len(), location)?;
2641                if let ConstructedValue::Set { element_type, .. } = value {
2642                    check_set(*element_type, values, budget, location)?;
2643                }
2644                units = sum(units, values.len(), location)?;
2645                for value in values {
2646                    let (child_units, child_bytes) = copy_size(value, budget, location)?;
2647                    units = sum(units, child_units, location)?;
2648                    bytes = sum(bytes, child_bytes, location)?;
2649                }
2650            }
2651            ConstructedValue::Record(fields) => {
2652                allocation::<(String, ConstructedValue)>(fields.len(), location)?;
2653                check_value_labels(fields, budget, location)?;
2654                units = sum(units, fields.len(), location)?;
2655                for (label, value) in fields {
2656                    allocation::<u8>(label.len(), location)?;
2657                    let (child_units, child_bytes) = copy_size(value, budget, location)?;
2658                    units = sum(units, child_units, location)?;
2659                    bytes = sum(bytes, sum(label.len(), child_bytes, location)?, location)?;
2660                }
2661            }
2662        }
2663        Ok((units, bytes))
2664    })
2665}
2666
2667fn copy_admitted(
2668    value: &ConstructedValue,
2669    budget: &mut Budget,
2670    location: &Location,
2671) -> Result<ConstructedValue, QueryError> {
2672    budget.within_evaluation(1, location.clone(), |budget| {
2673        budget.charge(1, location.clone())?;
2674        Ok(match value {
2675            ConstructedValue::Absent => ConstructedValue::Absent,
2676            ConstructedValue::Declared { value, declaration } => ConstructedValue::Declared {
2677                value: Box::new(copy_admitted(value, budget, location)?),
2678                declaration: copy_declaration(declaration, budget, location)?,
2679            },
2680            ConstructedValue::Boolean(value) => ConstructedValue::Boolean(*value),
2681            ConstructedValue::Integer(value) => ConstructedValue::Integer(*value),
2682            ConstructedValue::Float(value) => ConstructedValue::Float(*value),
2683            ConstructedValue::String(value) => ConstructedValue::String(value.to_owned()),
2684            ConstructedValue::Member(member) => {
2685                ConstructedValue::Member(copy_member_admitted(member))
2686            }
2687            ConstructedValue::KeyedArray { axes, entries } => {
2688                let mut copied_axes = Vec::with_capacity(axes.len());
2689                for axis in axes {
2690                    budget.charge(1, location.clone())?;
2691                    copied_axes.push(KeyAxis {
2692                        key_type: axis.key_type,
2693                        keys: copy_keys_admitted(&axis.keys, budget, location)?,
2694                    });
2695                }
2696                let mut copied_entries = Vec::with_capacity(entries.len());
2697                for entry in entries {
2698                    budget.charge(1, location.clone())?;
2699                    copied_entries.push(KeyedEntry {
2700                        key: copy_keys_admitted(&entry.key, budget, location)?,
2701                        value: copy_admitted(&entry.value, budget, location)?,
2702                    });
2703                }
2704                ConstructedValue::KeyedArray {
2705                    axes: copied_axes,
2706                    entries: copied_entries,
2707                }
2708            }
2709            ConstructedValue::Array(values)
2710            | ConstructedValue::Tuple(values)
2711            | ConstructedValue::Set {
2712                elements: values, ..
2713            } => {
2714                let mut copied = Vec::with_capacity(values.len());
2715                for value in values {
2716                    copied.push(copy_admitted(value, budget, location)?);
2717                }
2718                match value {
2719                    ConstructedValue::Array(_) => ConstructedValue::Array(copied),
2720                    ConstructedValue::Tuple(_) => ConstructedValue::Tuple(copied),
2721                    ConstructedValue::Set { element_type, .. } => ConstructedValue::Set {
2722                        element_type: *element_type,
2723                        elements: copied,
2724                    },
2725                    _ => unreachable!("matched collection"),
2726                }
2727            }
2728            ConstructedValue::Record(fields) => {
2729                let mut copied = Vec::with_capacity(fields.len());
2730                for (label, value) in fields {
2731                    copied.push((label.to_owned(), copy_admitted(value, budget, location)?));
2732                }
2733                ConstructedValue::Record(copied)
2734            }
2735        })
2736    })
2737}
2738
2739fn copy_type(
2740    ty: &ValueType,
2741    budget: &mut Budget,
2742    location: &Location,
2743) -> Result<ValueType, QueryError> {
2744    budget.within_evaluation(1, location.clone(), |budget| {
2745        budget.charge(1, location.clone())?;
2746        Ok(match ty {
2747            ValueType::Absent => ValueType::Absent,
2748            ValueType::Declared {
2749                actual,
2750                declaration,
2751            } => ValueType::Declared {
2752                actual: Box::new(copy_type(actual, budget, location)?),
2753                declaration: copy_declaration(declaration, budget, location)?,
2754            },
2755            ValueType::ScalarKind => ValueType::ScalarKind,
2756            ValueType::Boolean => ValueType::Boolean,
2757            ValueType::Integer => ValueType::Integer,
2758            ValueType::Float => ValueType::Float,
2759            ValueType::String => ValueType::String,
2760            ValueType::Member(id) => ValueType::Member(*id),
2761            ValueType::KeyedArray(keyed) => {
2762                let axes = match &keyed.axes {
2763                    Some(axes) => Some(copy_axis_types(axes, budget, location)?),
2764                    None => None,
2765                };
2766                ValueType::KeyedArray(KeyedArrayType {
2767                    axes,
2768                    elements: copy_types(&keyed.elements, budget, location)?,
2769                })
2770            }
2771            ValueType::Unestablished => ValueType::Unestablished,
2772            ValueType::TupleKind => ValueType::TupleKind,
2773            ValueType::RecordKind => ValueType::RecordKind,
2774            ValueType::Set(element_type) => ValueType::Set(*element_type),
2775            ValueType::Array(array) => {
2776                let (ArrayType::Positions(entries) | ArrayType::Elements(entries)) = array;
2777                let entries = copy_types(entries, budget, location)?;
2778                ValueType::Array(match array {
2779                    ArrayType::Positions(_) => ArrayType::Positions(entries),
2780                    ArrayType::Elements(_) => ArrayType::Elements(entries),
2781                })
2782            }
2783            ValueType::ArrayKind(elements) => {
2784                ValueType::ArrayKind(copy_types(elements, budget, location)?)
2785            }
2786            ValueType::Tuple(entries) => ValueType::Tuple(copy_types(entries, budget, location)?),
2787            ValueType::Record(fields) => {
2788                check_value_labels(fields, budget, location)?;
2789                let mut copied = metadata_vec(fields.len(), budget, location)?;
2790                for (label, ty) in fields {
2791                    budget.charge(label.len(), location.clone())?;
2792                    copied.push((label.to_owned(), copy_type(ty, budget, location)?));
2793                }
2794                ValueType::Record(copied)
2795            }
2796        })
2797    })
2798}
2799
2800fn copy_types(
2801    types: &[ValueType],
2802    budget: &mut Budget,
2803    location: &Location,
2804) -> Result<Vec<ValueType>, QueryError> {
2805    let mut copied = metadata_vec(types.len(), budget, location)?;
2806    for ty in types {
2807        copied.push(copy_type(ty, budget, location)?);
2808    }
2809    Ok(copied)
2810}
2811
2812fn one_type(
2813    ty: ValueType,
2814    budget: &mut Budget,
2815    location: &Location,
2816) -> Result<Vec<ValueType>, QueryError> {
2817    let mut types = metadata_vec(1, budget, location)?;
2818    types.push(ty);
2819    Ok(types)
2820}
2821
2822fn append_types(
2823    target: &mut Vec<ValueType>,
2824    types: Vec<ValueType>,
2825    budget: &mut Budget,
2826    location: &Location,
2827) -> Result<(), QueryError> {
2828    allocation::<ValueType>(sum(target.len(), types.len(), location)?, location)?;
2829    budget.charge(types.len(), location.clone())?;
2830    target.extend(types);
2831    Ok(())
2832}
2833
2834enum Globals<'a> {
2835    Types(&'a BTreeMap<String, ValueType>),
2836    Facts(&'a BTreeMap<String, Vec<ValueType>>),
2837    Available(&'a Bindings<ConstructedValue>),
2838}
2839
2840/// Only selected terminal control positions may transfer to their loop.
2841fn check_loop_terminal<Q>(
2842    kind: &ValueExpressionKind<Q>,
2843    transfer: Option<usize>,
2844    location: &Location,
2845) -> Result<(), QueryError> {
2846    match (kind, transfer) {
2847        (ValueExpressionKind::Break(_), Some(_)) => Ok(()),
2848        (ValueExpressionKind::Continue(values), Some(arity)) if values.len() == arity => Ok(()),
2849        (ValueExpressionKind::Continue(_), Some(_)) => {
2850            Err(error(location, "loop state argument count mismatch"))
2851        }
2852        (ValueExpressionKind::Break(_) | ValueExpressionKind::Continue(_), None) => Err(error(
2853            location,
2854            "loop transfer requires a terminal loop body position",
2855        )),
2856        (
2857            ValueExpressionKind::Block { .. }
2858            | ValueExpressionKind::Conditional { .. }
2859            | ValueExpressionKind::ValueMatch { .. },
2860            Some(_),
2861        )
2862        | (_, None) => Ok(()),
2863        (_, Some(_)) => Err(error(
2864            location,
2865            "loop body path must end in break or continue",
2866        )),
2867    }
2868}
2869
2870struct Analysis<'a, 'h, Q, H> {
2871    queries: &'h mut H,
2872    query: std::marker::PhantomData<Q>,
2873    globals: Globals<'a>,
2874    locals: Vec<(&'a str, Vec<ValueType>)>,
2875    free: Vec<Located<String>>,
2876    fallback_names: Vec<Located<String>>,
2877    fallback: bool,
2878}
2879
2880impl<'a, 'h, Q: 'a, H: ValueQueryHost<Q>> Analysis<'a, 'h, Q, H> {
2881    fn new(globals: Globals<'a>, queries: &'h mut H) -> Self {
2882        Self {
2883            globals,
2884            queries,
2885            query: std::marker::PhantomData,
2886            locals: Vec::new(),
2887            free: Vec::new(),
2888            fallback_names: Vec::new(),
2889            fallback: false,
2890        }
2891    }
2892
2893    fn global_exists(
2894        &self,
2895        name: &str,
2896        range: std::ops::Range<usize>,
2897        budget: &mut Budget,
2898    ) -> Result<bool, QueryError> {
2899        admit_diagnostic(
2900            "undefined binding: ",
2901            name,
2902            budget,
2903            &Location::Query(range.clone()),
2904        )?;
2905        Ok(match self.globals {
2906            Globals::Types(types) => types.contains_key(name),
2907            Globals::Facts(types) => types.contains_key(name),
2908            Globals::Available(bindings) => bindings.get(name, range).is_ok(),
2909        })
2910    }
2911
2912    fn name(
2913        &mut self,
2914        name: &str,
2915        expression: &ValueExpression<Q>,
2916        budget: &mut Budget,
2917    ) -> Result<Vec<ValueType>, QueryError> {
2918        let location = Location::Query(expression.range.clone());
2919        if name == "_" {
2920            return Err(error(&location, "discard binder cannot be read"));
2921        }
2922        for (local, types) in self.locals.iter().rev() {
2923            budget.charge(
2924                sum(1, name.len().min(local.len()), &location)?,
2925                location.clone(),
2926            )?;
2927            if name == *local {
2928                return copy_types(types, budget, &location);
2929            }
2930        }
2931        self.queries
2932            .validate_name(name, &expression.range, budget)?;
2933        admit_diagnostic("undefined binding: ", name, budget, &location)?;
2934        let types = match self.globals {
2935            Globals::Types(types) => one_type(
2936                copy_type(
2937                    types
2938                        .get(name)
2939                        .ok_or_else(|| error(&location, format!("undefined binding: {name}")))?,
2940                    budget,
2941                    &location,
2942                )?,
2943                budget,
2944                &location,
2945            )?,
2946            Globals::Facts(types) => copy_types(
2947                types
2948                    .get(name)
2949                    .ok_or_else(|| error(&location, format!("undefined binding: {name}")))?,
2950                budget,
2951                &location,
2952            )?,
2953            Globals::Available(bindings) => {
2954                let value = bindings.get(name, expression.range.clone())?;
2955                let ty = if !self.fallback
2956                    && matches!(
2957                        value,
2958                        ConstructedValue::Array(_)
2959                            | ConstructedValue::Tuple(_)
2960                            | ConstructedValue::Record(_)
2961                            | ConstructedValue::Set { .. }
2962                            | ConstructedValue::KeyedArray { .. }
2963                    ) {
2964                    ValueType::Unestablished
2965                } else {
2966                    shallow_type(value, budget, &location)?
2967                };
2968                one_type(ty, budget, &location)?
2969            }
2970        };
2971        let mut found = false;
2972        for previous in &self.free {
2973            budget.charge(
2974                sum(1, name.len().min(previous.value.len()), &location)?,
2975                location.clone(),
2976            )?;
2977            if previous.value == name {
2978                found = true;
2979                break;
2980            }
2981        }
2982        if self.fallback && !found {
2983            budget.charge(name.len(), location.clone())?;
2984            push_metadata(
2985                &mut self.fallback_names,
2986                Located::new(name.to_owned(), expression.range.clone()),
2987                budget,
2988                &location,
2989            )?;
2990        } else if !self.fallback {
2991            let mut selected = None;
2992            for (index, previous) in self.fallback_names.iter().enumerate() {
2993                budget.charge(1 + name.len().min(previous.value.len()), location.clone())?;
2994                if previous.value == name {
2995                    selected = Some(index);
2996                    break;
2997                }
2998            }
2999            if let Some(index) = selected {
3000                self.fallback_names.remove(index);
3001            }
3002        }
3003        if !found {
3004            budget.charge(name.len(), location.clone())?;
3005            push_metadata(
3006                &mut self.free,
3007                Located::new(name.to_owned(), expression.range.clone()),
3008                budget,
3009                &location,
3010            )?;
3011        }
3012        Ok(types)
3013    }
3014
3015    fn walk(
3016        &mut self,
3017        expression: &'a ValueExpression<Q>,
3018        budget: &mut Budget,
3019    ) -> Result<Vec<ValueType>, QueryError> {
3020        self.walk_control(expression, budget, None)
3021    }
3022
3023    fn walk_control(
3024        &mut self,
3025        expression: &'a ValueExpression<Q>,
3026        budget: &mut Budget,
3027        transfer: Option<usize>,
3028    ) -> Result<Vec<ValueType>, QueryError> {
3029        let location = Location::Query(expression.range.clone());
3030        budget.within_evaluation(1, location.clone(), |budget| {
3031            budget.charge(1, location.clone())?;
3032            check_loop_terminal(&expression.value, transfer, &location)?;
3033            self.node(expression, budget, &location, transfer)
3034        })
3035    }
3036
3037    fn node(
3038        &mut self,
3039        expression: &'a ValueExpression<Q>,
3040        budget: &mut Budget,
3041        location: &Location,
3042        transfer: Option<usize>,
3043    ) -> Result<Vec<ValueType>, QueryError> {
3044        self.queries.prepare_computation(expression, budget)?;
3045        match &expression.value {
3046            ValueExpressionKind::Queue { items, .. } => {
3047                self.walk(items, budget)?;
3048                self.queries.worklist_types(expression, budget)
3049            }
3050            ValueExpressionKind::IdentitySet { .. } => {
3051                self.queries.worklist_types(expression, budget)
3052            }
3053            ValueExpressionKind::PopFront(queue) => {
3054                self.walk(queue, budget)?;
3055                self.queries.worklist_types(expression, budget)
3056            }
3057            ValueExpressionKind::PushBackAll { queue, items } => {
3058                self.walk(queue, budget)?;
3059                self.walk(items, budget)?;
3060                self.queries.worklist_types(expression, budget)
3061            }
3062            ValueExpressionKind::Insert { set, item } => {
3063                self.walk(set, budget)?;
3064                self.walk(item, budget)?;
3065                self.queries.worklist_types(expression, budget)
3066            }
3067            ValueExpressionKind::Call {
3068                function,
3069                arguments,
3070            } => {
3071                let (_, arity) = self.queries.function(function, budget)?;
3072                if arguments.len() != arity {
3073                    return Err(error(location, "function argument count mismatch"));
3074                }
3075                for argument in arguments {
3076                    self.walk(argument, budget)?;
3077                }
3078                self.queries.function_types(function, budget)
3079            }
3080            ValueExpressionKind::Fold {
3081                items,
3082                initial,
3083                step,
3084            }
3085            | ValueExpressionKind::Scan {
3086                items,
3087                initial,
3088                step,
3089            } => {
3090                let (_, state) = self.queries.accumulator_function(step, budget)?;
3091                let types = self.walk(items, budget)?;
3092                for ty in types {
3093                    if !matches!(
3094                        shape_type(ty, budget, location)?,
3095                        ValueType::Array(_) | ValueType::ArrayKind(_) | ValueType::Unestablished
3096                    ) {
3097                        return Err(error(
3098                            &Location::Query(items.range.clone()),
3099                            "named accumulator needs a positional sequence",
3100                        ));
3101                    }
3102                }
3103                self.walk(initial, budget)?;
3104                if matches!(expression.value, ValueExpressionKind::Scan { .. }) {
3105                    one_type(
3106                        ValueType::Array(ArrayType::Elements(state)),
3107                        budget,
3108                        location,
3109                    )
3110                } else {
3111                    Ok(state)
3112                }
3113            }
3114            ValueExpressionKind::NativeValue(query) => {
3115                self.queries
3116                    .native_value_types(query, &self.locals, budget, &expression.range)
3117            }
3118            ValueExpressionKind::Literal(value) => {
3119                let ty = if self.fallback {
3120                    shallow_type(value, budget, location)?
3121                } else {
3122                    constructed_value_type(value, budget, location.clone())?
3123                };
3124                one_type(ty, budget, location)
3125            }
3126            ValueExpressionKind::Occurs(value) | ValueExpressionKind::Absent(value) => {
3127                self.walk(value, budget)?;
3128                one_type(ValueType::Boolean, budget, location)
3129            }
3130            ValueExpressionKind::Lookup { value, key }
3131            | ValueExpressionKind::HasKey { value, key } => {
3132                let values = self.walk(value, budget)?;
3133                let keys = self.walk(key, budget)?;
3134                let label = match &key.value {
3135                    ValueExpressionKind::Literal(ConstructedValue::String(label)) => {
3136                        Some(label.as_str())
3137                    }
3138                    _ => None,
3139                };
3140                let mut result = Vec::new();
3141                for value in values {
3142                    append_types(
3143                        &mut result,
3144                        access_type(value, &keys, label, true, budget, location)?,
3145                        budget,
3146                        location,
3147                    )?;
3148                }
3149                if matches!(expression.value, ValueExpressionKind::HasKey { .. }) {
3150                    one_type(ValueType::Boolean, budget, location)
3151                } else {
3152                    Ok(result)
3153                }
3154            }
3155            ValueExpressionKind::Default { value, fallback } => {
3156                let left = self.walk(value, budget)?;
3157                let saved = self.fallback;
3158                self.fallback = true;
3159                let right = self.walk(fallback, budget);
3160                self.fallback = saved;
3161                let right = right?;
3162                for a in &left {
3163                    for b in &right {
3164                        if !compatible_types(a, b, budget, location)? {
3165                            return Err(error(
3166                                location,
3167                                "default needs compatible underlying types",
3168                            ));
3169                        }
3170                    }
3171                }
3172                let mut result = Vec::new();
3173                for ty in left.into_iter().chain(right) {
3174                    let ty = match ty {
3175                        ValueType::Absent => continue,
3176                        ValueType::Declared {
3177                            actual,
3178                            declaration,
3179                        } => {
3180                            let declaration =
3181                                copy_declaration(underlying(&declaration), budget, location)?;
3182                            ValueType::Declared {
3183                                actual: Box::new(if matches!(*actual, ValueType::Absent) {
3184                                    ValueType::Unestablished
3185                                } else {
3186                                    *actual
3187                                }),
3188                                declaration,
3189                            }
3190                        }
3191                        ty => ty,
3192                    };
3193                    push_metadata(&mut result, ty, budget, location)?;
3194                }
3195                if result.is_empty() {
3196                    one_type(ValueType::Absent, budget, location)
3197                } else {
3198                    Ok(result)
3199                }
3200            }
3201            ValueExpressionKind::CurrentInput => {
3202                self.queries.current_input_types(&expression.range, budget)
3203            }
3204            ValueExpressionKind::Name(name) => self.name(name, expression, budget),
3205            ValueExpressionKind::PredicateSubject => {
3206                if !self
3207                    .locals
3208                    .iter()
3209                    .any(|(name, _)| *name == PREDICATE_SUBJECT)
3210                {
3211                    return self
3212                        .queries
3213                        .predicate_subject_types(&expression.range, budget);
3214                }
3215                self.name(PREDICATE_SUBJECT, expression, budget)
3216            }
3217            ValueExpressionKind::SubjectScope { subject, argument } => {
3218                let types = self.walk(subject, budget)?;
3219                self.scoped_types(argument, subject, &types, false, budget)
3220            }
3221            ValueExpressionKind::PredicateScope { subject, predicate } => {
3222                let types = self.walk(subject, budget)?;
3223                let result = self.scoped_types(predicate, subject, &types, false, budget)?;
3224                predicate_types(&result, budget, &predicate.body.range)?;
3225                one_type(ValueType::Boolean, budget, location)
3226            }
3227            ValueExpressionKind::NativeQuery(query) => {
3228                self.queries
3229                    .query_types(query, &self.locals, budget, &expression.range)
3230            }
3231            ValueExpressionKind::One(array) => {
3232                let mut result = Vec::new();
3233                for ty in self.walk(array, budget)? {
3234                    match shape_type(ty, budget, location)? {
3235                        ValueType::Array(
3236                            ArrayType::Positions(types) | ArrayType::Elements(types),
3237                        )
3238                        | ValueType::ArrayKind(types) => {
3239                            append_types(&mut result, types, budget, location)?
3240                        }
3241                        ValueType::Unestablished => {
3242                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?
3243                        }
3244                        _ => return Err(error(location, "one needs a positional array")),
3245                    }
3246                }
3247                if result.is_empty() {
3248                    push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3249                }
3250                Ok(result)
3251            }
3252            ValueExpressionKind::Array(children) => {
3253                let mut entries = metadata_vec(children.len(), budget, location)?;
3254                let mut exact = true;
3255                for child in children {
3256                    let types = self.walk(child, budget)?;
3257                    exact &= types.len() == 1;
3258                    entries.push(types);
3259                }
3260                let mut types = Vec::new();
3261                for entries in entries {
3262                    append_types(&mut types, entries, budget, location)?;
3263                }
3264                one_type(
3265                    ValueType::Array(if exact {
3266                        ArrayType::Positions(types)
3267                    } else {
3268                        ArrayType::Elements(types)
3269                    }),
3270                    budget,
3271                    location,
3272                )
3273            }
3274            ValueExpressionKind::Tuple(children) => {
3275                let positions = self.positions(children.iter(), budget, location)?;
3276                let mut types = metadata_vec(positions.len(), budget, location)?;
3277                for position in positions {
3278                    types.push(ValueType::Tuple(position));
3279                }
3280                Ok(types)
3281            }
3282            ValueExpressionKind::Record(fields) => {
3283                for (index, (label, _)) in fields.iter().enumerate() {
3284                    for (previous, _) in &fields[..index] {
3285                        budget.charge(
3286                            sum(1, label.value.len().min(previous.value.len()), location)?,
3287                            location.clone(),
3288                        )?;
3289                        if label.value == previous.value {
3290                            return Err(QueryError::query(
3291                                label.range.clone(),
3292                                "duplicate record label",
3293                            ));
3294                        }
3295                    }
3296                }
3297                let positions =
3298                    self.positions(fields.iter().map(|(_, value)| value), budget, location)?;
3299                let mut types = metadata_vec(positions.len(), budget, location)?;
3300                for position in positions {
3301                    let mut result = metadata_vec(fields.len(), budget, location)?;
3302                    for ((label, _), ty) in fields.iter().zip(position) {
3303                        budget.charge(label.value.len(), Location::Query(label.range.clone()))?;
3304                        result.push((label.value.to_owned(), ty));
3305                    }
3306                    types.push(ValueType::Record(result));
3307                }
3308                Ok(types)
3309            }
3310            ValueExpressionKind::Dictionary(entries) => {
3311                for (key, value) in entries {
3312                    for ty in self.walk(key, budget)? {
3313                        require_type(
3314                            &ty,
3315                            ScalarRequirement::String,
3316                            &Location::Query(key.range.clone()),
3317                        )?;
3318                    }
3319                    self.walk(value, budget)?;
3320                }
3321                one_type(ValueType::RecordKind, budget, location)
3322            }
3323            ValueExpressionKind::DictionaryComprehension { clauses, key, head } => {
3324                let saved = self.locals.len();
3325                let result = (|| {
3326                    self.clauses(clauses, budget)?;
3327                    for ty in self.walk(key, budget)? {
3328                        require_type(
3329                            &ty,
3330                            ScalarRequirement::String,
3331                            &Location::Query(key.range.clone()),
3332                        )?;
3333                    }
3334                    self.walk(head, budget)?;
3335                    one_type(ValueType::RecordKind, budget, location)
3336                })();
3337                self.locals.truncate(saved);
3338                result
3339            }
3340            ValueExpressionKind::Set(children) => {
3341                let mut element_type = None;
3342                for child in children {
3343                    merge_set_types(
3344                        &mut element_type,
3345                        &self.walk(child, budget)?,
3346                        budget,
3347                        location,
3348                    )?;
3349                }
3350                one_type(ValueType::Set(element_type), budget, location)
3351            }
3352            ValueExpressionKind::KeyedArray {
3353                axes,
3354                declared_axis_sources,
3355                entries,
3356            } => {
3357                if axes.is_some() && declared_axis_sources.is_some() {
3358                    return Err(error(
3359                        location,
3360                        "conflicting concrete axes and declared axis sources",
3361                    ));
3362                }
3363                let source_facts = declared_axis_sources
3364                    .as_ref()
3365                    .map(|sources| self.axis_sources(sources, budget, location))
3366                    .transpose()?;
3367                let mut facts = None;
3368                let established = match axes {
3369                    Some(axes) => {
3370                        check_axes(axes, budget, location)?;
3371                        Some(axis_types(axes, budget, location)?)
3372                    }
3373                    None => None,
3374                };
3375                let mut elements = Vec::new();
3376                for (key, value) in entries {
3377                    let key_location = Location::Query(key.range.clone());
3378                    merge_key_facts(&mut facts, &self.walk(key, budget)?, budget, &key_location)?;
3379                    if let Some(axes) = &established {
3380                        check_key_facts(&facts, axes, budget, &key_location)?;
3381                    }
3382                    if let Some(sources) = &source_facts {
3383                        check_source_facts(&facts, sources, budget, &key_location)?;
3384                    }
3385                    append_types(&mut elements, self.walk(value, budget)?, budget, location)?;
3386                }
3387                let axes = match established {
3388                    Some(axes) => Some(axes),
3389                    None => established_facts(source_facts.or(facts), budget, location)?,
3390                };
3391                if entries.is_empty() && axes.is_none() && declared_axis_sources.is_none() {
3392                    return Err(error(location, "unestablished empty keyed axes"));
3393                }
3394                one_type(
3395                    ValueType::KeyedArray(KeyedArrayType { axes, elements }),
3396                    budget,
3397                    location,
3398                )
3399            }
3400            ValueExpressionKind::KeyedComprehension {
3401                clauses,
3402                key,
3403                head,
3404                declared_axis_sources,
3405                empty_axis_sources,
3406            } => {
3407                if declared_axis_sources.is_some() && empty_axis_sources.is_some() {
3408                    return Err(error(
3409                        location,
3410                        "conflicting declared and fallback axis sources",
3411                    ));
3412                }
3413                // Both source lists belong to the scope before our binders.
3414                let sources = declared_axis_sources
3415                    .as_ref()
3416                    .or(empty_axis_sources.as_ref());
3417                let source_facts = sources
3418                    .map(|sources| self.axis_sources(sources, budget, location))
3419                    .transpose()?;
3420                let saved = self.locals.len();
3421                let result = (|| {
3422                    self.clauses(clauses, budget)?;
3423                    let mut facts = None;
3424                    merge_key_facts(
3425                        &mut facts,
3426                        &self.walk(key, budget)?,
3427                        budget,
3428                        &Location::Query(key.range.clone()),
3429                    )?;
3430                    let elements = self.walk(head, budget)?;
3431                    if let Some(source_facts) = &source_facts {
3432                        check_source_facts(
3433                            &facts,
3434                            source_facts,
3435                            budget,
3436                            &Location::Query(key.range.clone()),
3437                        )?;
3438                    }
3439                    let axes = if declared_axis_sources.is_some() {
3440                        established_facts(source_facts, budget, location)?
3441                    } else {
3442                        established_facts(facts, budget, location)?
3443                    };
3444                    one_type(
3445                        ValueType::KeyedArray(KeyedArrayType { axes, elements }),
3446                        budget,
3447                        location,
3448                    )
3449                })();
3450                self.locals.truncate(saved);
3451                result
3452            }
3453            ValueExpressionKind::Unary { operator, operand } => {
3454                let types = self.walk(operand, budget)?;
3455                let mut result = metadata_vec(types.len(), budget, location)?;
3456                for ty in types {
3457                    budget.charge(1, location.clone())?;
3458                    match operator {
3459                        ValueUnaryOperator::Not => {
3460                            require_type(&ty, ScalarRequirement::Boolean, location)?;
3461                            result.push(ValueType::Boolean);
3462                        }
3463                        ValueUnaryOperator::Positive | ValueUnaryOperator::Negative => {
3464                            require_type(&ty, ScalarRequirement::Numeric, location)?;
3465                            result.push(ty);
3466                        }
3467                    }
3468                }
3469                Ok(result)
3470            }
3471            ValueExpressionKind::Binary {
3472                operator,
3473                left,
3474                right,
3475            } => {
3476                let left = self.walk(left, budget)?;
3477                let right = self.walk(right, budget)?;
3478                let mut result = Vec::new();
3479                for a in &left {
3480                    for b in &right {
3481                        budget.charge(1, location.clone())?;
3482                        let ty = binary_type(*operator, a, b, budget, location)?;
3483                        push_metadata(&mut result, ty, budget, location)?;
3484                    }
3485                }
3486                Ok(result)
3487            }
3488            ValueExpressionKind::Field { value, field } => {
3489                let types = self.walk(value, budget)?;
3490                let mut result = Vec::new();
3491                for ty in types {
3492                    if let ValueType::Declared { declaration, .. } = &ty
3493                        && let DeclaredType::Record(fields) = underlying(declaration)
3494                    {
3495                        let selected = record_declaration(fields, &field.value, budget, location)?;
3496                        push_metadata(
3497                            &mut result,
3498                            declared_fact(selected, ValueType::Unestablished, budget, location)?,
3499                            budget,
3500                            location,
3501                        )?;
3502                        continue;
3503                    }
3504                    let selected = match shape_type(ty, budget, location)? {
3505                        ValueType::Record(fields) => {
3506                            let mut selected = None;
3507                            for (label, ty) in fields {
3508                                budget.charge(
3509                                    sum(1, label.len().min(field.value.len()), location)?,
3510                                    location.clone(),
3511                                )?;
3512                                if label == field.value {
3513                                    selected = Some(ty);
3514                                }
3515                            }
3516                            selected.unwrap_or(ValueType::Unestablished)
3517                        }
3518                        ValueType::Unestablished | ValueType::RecordKind => {
3519                            ValueType::Unestablished
3520                        }
3521                        _ => return Err(error(location, "field access needs a record")),
3522                    };
3523                    push_metadata(&mut result, selected, budget, location)?;
3524                }
3525                Ok(result)
3526            }
3527            ValueExpressionKind::TupleAccess { value, position } => {
3528                let types = self.walk(value, budget)?;
3529                let mut result = Vec::new();
3530                for ty in types {
3531                    budget.charge(1, location.clone())?;
3532                    let selected = match shape_type(ty, budget, location)? {
3533                        ValueType::Tuple(entries) => {
3534                            budget.charge(entries.len().min(position.value), location.clone())?;
3535                            position
3536                                .value
3537                                .checked_sub(1)
3538                                .and_then(|index| entries.into_iter().nth(index))
3539                                .unwrap_or(ValueType::Unestablished)
3540                        }
3541                        ValueType::Unestablished | ValueType::TupleKind => ValueType::Unestablished,
3542                        _ => return Err(error(location, "tuple access needs a tuple")),
3543                    };
3544                    push_metadata(&mut result, selected, budget, location)?;
3545                }
3546                Ok(result)
3547            }
3548            ValueExpressionKind::ArrayAccess { array, index } => {
3549                let arrays = self.walk(array, budget)?;
3550                let indices = self.walk(index, budget)?;
3551                let constant = match index.value {
3552                    ValueExpressionKind::Literal(ConstructedValue::Integer(value)) => Some(value),
3553                    _ => None,
3554                };
3555                let mut result = Vec::new();
3556                for array in arrays {
3557                    if matches!(&array, ValueType::Record(_) | ValueType::RecordKind)
3558                        || matches!(&array, ValueType::Declared { declaration, .. }
3559                            if matches!(underlying(declaration), DeclaredType::Record(_)))
3560                    {
3561                        let label = match &index.value {
3562                            ValueExpressionKind::Literal(ConstructedValue::String(label)) => {
3563                                Some(label.as_str())
3564                            }
3565                            _ => None,
3566                        };
3567                        append_types(
3568                            &mut result,
3569                            access_type(array, &indices, label, false, budget, location)?,
3570                            budget,
3571                            location,
3572                        )?;
3573                        continue;
3574                    }
3575                    match shape_type(array, budget, location)? {
3576                        ValueType::ArrayKind(elements) => {
3577                            let mut facts = None;
3578                            merge_key_facts(&mut facts, &indices, budget, location)?;
3579                            append_types(&mut result, elements, budget, location)?;
3580                        }
3581                        ValueType::KeyedArray(keyed) => {
3582                            let mut facts = None;
3583                            merge_key_facts(&mut facts, &indices, budget, location)?;
3584                            if let Some(axes) = &keyed.axes {
3585                                check_key_facts(&facts, axes, budget, location)?;
3586                            }
3587                            append_types(&mut result, keyed.elements, budget, location)?;
3588                        }
3589                        ValueType::Array(array) => {
3590                            for ty in &indices {
3591                                require_type(ty, ScalarRequirement::Integer, location)?;
3592                            }
3593                            match array {
3594                                _ if constant.is_some_and(|position| position <= 0) => {
3595                                    push_metadata(
3596                                        &mut result,
3597                                        ValueType::Unestablished,
3598                                        budget,
3599                                        location,
3600                                    )?;
3601                                }
3602                                ArrayType::Positions(entries) if constant.is_some() => {
3603                                    let position =
3604                                        usize::try_from(constant.expect("constant checked")).ok();
3605                                    budget.charge(
3606                                        entries.len().min(position.unwrap_or(0)),
3607                                        location.clone(),
3608                                    )?;
3609                                    let ty = position
3610                                        .and_then(|position| position.checked_sub(1))
3611                                        .and_then(|index| entries.into_iter().nth(index))
3612                                        .unwrap_or(ValueType::Unestablished);
3613                                    push_metadata(&mut result, ty, budget, location)?;
3614                                }
3615                                ArrayType::Positions(entries) | ArrayType::Elements(entries) => {
3616                                    append_types(&mut result, entries, budget, location)?
3617                                }
3618                            }
3619                        }
3620                        ValueType::Unestablished => {
3621                            for ty in &indices {
3622                                if matches!(ty, ValueType::String) {
3623                                    continue;
3624                                }
3625                                let mut facts = None;
3626                                merge_key_facts(
3627                                    &mut facts,
3628                                    std::slice::from_ref(ty),
3629                                    budget,
3630                                    location,
3631                                )?;
3632                            }
3633                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3634                        }
3635                        _ => return Err(error(location, "array access needs an array or record")),
3636                    }
3637                }
3638                if result.is_empty() {
3639                    push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3640                }
3641                Ok(result)
3642            }
3643            ValueExpressionKind::IndexSet(array) => {
3644                let mut result = Vec::new();
3645                for ty in self.walk(array, budget)? {
3646                    budget.charge(1, location.clone())?;
3647                    let element_type = match shape_type(ty, budget, location)? {
3648                        ValueType::Array(_) => Some(SetElementType::Integer),
3649                        ValueType::KeyedArray(keyed) => match keyed.axes {
3650                            Some(axes) if axes.len() == 1 => Some(key_set_type(axes[0])),
3651                            Some(_) => return Err(error(location, "index_set needs rank one")),
3652                            None => None,
3653                        },
3654                        ValueType::ArrayKind(_) | ValueType::Unestablished => None,
3655                        _ => return Err(error(location, "index_set needs an array")),
3656                    };
3657                    push_metadata(&mut result, ValueType::Set(element_type), budget, location)?;
3658                }
3659                Ok(result)
3660            }
3661            ValueExpressionKind::Entries(array) => {
3662                let mut result = Vec::new();
3663                for ty in self.walk(array, budget)? {
3664                    budget.charge(1, location.clone())?;
3665                    let keyed = match shape_type(ty, budget, location)? {
3666                        ValueType::KeyedArray(keyed) => keyed,
3667                        ValueType::ArrayKind(elements) => KeyedArrayType {
3668                            axes: None,
3669                            elements,
3670                        },
3671                        ValueType::Unestablished => {
3672                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3673                            continue;
3674                        }
3675                        _ => return Err(error(location, "entries needs a keyed array")),
3676                    };
3677                    let key = match keyed.axes {
3678                        Some(axes) => {
3679                            let mut components = metadata_vec(axes.len(), budget, location)?;
3680                            for axis in axes {
3681                                budget.charge(1, location.clone())?;
3682                                components.push(match axis {
3683                                    ValueKeyType::Integer => ValueType::Integer,
3684                                    ValueKeyType::Member(id) => ValueType::Member(id),
3685                                });
3686                            }
3687                            if components.len() == 1 {
3688                                components.pop().expect("rank one")
3689                            } else {
3690                                ValueType::Tuple(components)
3691                            }
3692                        }
3693                        None => ValueType::Unestablished,
3694                    };
3695                    let mut records = metadata_vec(keyed.elements.len(), budget, location)?;
3696                    for value in keyed.elements {
3697                        let mut fields = metadata_vec(2, budget, location)?;
3698                        budget.charge("key".len() + "value".len(), location.clone())?;
3699                        fields.push(("key".into(), copy_type(&key, budget, location)?));
3700                        fields.push(("value".into(), value));
3701                        records.push(ValueType::Record(fields));
3702                    }
3703                    push_metadata(
3704                        &mut result,
3705                        ValueType::Array(ArrayType::Elements(records)),
3706                        budget,
3707                        location,
3708                    )?;
3709                }
3710                Ok(result)
3711            }
3712            ValueExpressionKind::ZipExact { left, right } => {
3713                let left = self.walk(left, budget)?;
3714                let right = self.walk(right, budget)?;
3715                let mut result = Vec::new();
3716                for a in &left {
3717                    for b in &right {
3718                        let ty = zip_exact_type(a, b, budget, location)?;
3719                        push_metadata(&mut result, ty, budget, location)?;
3720                    }
3721                }
3722                Ok(result)
3723            }
3724            ValueExpressionKind::Reduce { operator, array } => {
3725                for ty in self.walk(array, budget)? {
3726                    budget.charge(1, location.clone())?;
3727                    let elements = match shape_type(ty, budget, location)? {
3728                        ValueType::Array(
3729                            ArrayType::Positions(elements) | ArrayType::Elements(elements),
3730                        )
3731                        | ValueType::ArrayKind(elements) => elements,
3732                        ValueType::KeyedArray(keyed) => keyed.elements,
3733                        ValueType::Unestablished => continue,
3734                        _ => return Err(error(location, "reducer needs an array")),
3735                    };
3736                    if let Some(requirement) = match operator {
3737                        ValueReducer::Length => None,
3738                        ValueReducer::Sum => Some(ScalarRequirement::Integer),
3739                        ValueReducer::Forall | ValueReducer::Exists => {
3740                            Some(ScalarRequirement::Boolean)
3741                        }
3742                    } {
3743                        for element in elements {
3744                            budget.charge(1, location.clone())?;
3745                            require_type(&element, requirement, location)?;
3746                        }
3747                    }
3748                }
3749                one_type(
3750                    match operator {
3751                        ValueReducer::Length | ValueReducer::Sum => ValueType::Integer,
3752                        ValueReducer::Forall | ValueReducer::Exists => ValueType::Boolean,
3753                    },
3754                    budget,
3755                    location,
3756                )
3757            }
3758            ValueExpressionKind::Filter { array, predicate } => {
3759                let mut elements = Vec::new();
3760                for ty in self.walk(array, budget)? {
3761                    append_types(
3762                        &mut elements,
3763                        filter_elements(ty, budget, location)?,
3764                        budget,
3765                        location,
3766                    )?;
3767                }
3768                if elements.is_empty() {
3769                    push_metadata(&mut elements, ValueType::Unestablished, budget, location)?;
3770                }
3771                let result = self.scoped_types(predicate, array, &elements, true, budget)?;
3772                predicate_types(&result, budget, &predicate.body.range)?;
3773                one_type(
3774                    ValueType::Array(ArrayType::Elements(elements)),
3775                    budget,
3776                    location,
3777                )
3778            }
3779            ValueExpressionKind::Sort {
3780                array, selector, ..
3781            } => {
3782                let mut result = Vec::new();
3783                for ty in self.walk(array, budget)? {
3784                    let ty = sort_type(ty, selector.as_ref(), budget, location)?;
3785                    push_metadata(&mut result, ty, budget, location)?;
3786                }
3787                Ok(result)
3788            }
3789            ValueExpressionKind::Extremum { array, field, .. } => {
3790                let mut result = Vec::new();
3791                for ty in self.walk(array, budget)? {
3792                    let elements = ordering_elements(
3793                        ty,
3794                        field
3795                            .as_ref()
3796                            .map(|field| Located::new(field.value.as_str(), field.range.clone())),
3797                        budget,
3798                        location,
3799                    )?;
3800                    append_types(&mut result, elements, budget, location)?;
3801                }
3802                push_metadata(&mut result, ValueType::Absent, budget, location)?;
3803                Ok(result)
3804            }
3805            ValueExpressionKind::Unique(array) | ValueExpressionKind::Tally(array) => {
3806                let tally = matches!(expression.value, ValueExpressionKind::Tally(_));
3807                let mut result = Vec::new();
3808                for ty in self.walk(array, budget)? {
3809                    let retained = if !tally {
3810                        match &ty {
3811                            ValueType::Declared { declaration, .. } => {
3812                                Some(copy_declaration(declaration, budget, location)?)
3813                            }
3814                            _ => None,
3815                        }
3816                    } else {
3817                        None
3818                    };
3819                    let elements = scalar_pipeline_elements(ty, tally, budget, location)?;
3820                    let mut ty = if tally {
3821                        if elements.is_empty() {
3822                            ValueType::Record(Vec::new())
3823                        } else {
3824                            ValueType::RecordKind
3825                        }
3826                    } else {
3827                        ValueType::Array(ArrayType::Elements(elements))
3828                    };
3829                    if let Some(declaration) = retained {
3830                        ty = ValueType::Declared {
3831                            actual: Box::new(ty),
3832                            declaration,
3833                        };
3834                    }
3835                    push_metadata(&mut result, ty, budget, location)?;
3836                }
3837                Ok(result)
3838            }
3839            ValueExpressionKind::GroupBy { rows, field } => {
3840                let selector = Location::Query(field.range.clone());
3841                let mut result = Vec::new();
3842                for ty in self.walk(rows, budget)? {
3843                    budget.charge(1, location.clone())?;
3844                    let elements = match shape_type(ty, budget, location)? {
3845                        ValueType::Array(
3846                            ArrayType::Positions(elements) | ArrayType::Elements(elements),
3847                        )
3848                        | ValueType::ArrayKind(elements) => elements,
3849                        ValueType::Unestablished => {
3850                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3851                            continue;
3852                        }
3853                        _ => return Err(error(location, "group_by needs a positional array")),
3854                    };
3855                    let mut key_type = None;
3856                    for row in &elements {
3857                        budget.charge(1, location.clone())?;
3858                        let normalized = if matches!(row, ValueType::Declared { .. }) {
3859                            Some(shape_type(
3860                                copy_type(row, budget, location)?,
3861                                budget,
3862                                location,
3863                            )?)
3864                        } else {
3865                            None
3866                        };
3867                        let fields = match normalized.as_ref().unwrap_or(row) {
3868                            ValueType::Record(fields) => fields,
3869                            ValueType::Unestablished | ValueType::RecordKind => continue,
3870                            _ => return Err(error(location, "group_by needs record rows")),
3871                        };
3872                        for (label, ty) in fields {
3873                            budget.charge(
3874                                sum(1, label.len().min(field.value.len()), &selector)?,
3875                                selector.clone(),
3876                            )?;
3877                            if label != &field.value || matches!(ty, ValueType::Unestablished) {
3878                                continue;
3879                            }
3880                            if !matches!(
3881                                ty,
3882                                ValueType::Integer | ValueType::String | ValueType::Member(_)
3883                            ) {
3884                                return Err(error(
3885                                    &selector,
3886                                    "group_by keys need integers, strings or nominal members",
3887                                ));
3888                            }
3889                            if key_type.as_ref().is_some_and(|previous| previous != ty) {
3890                                return Err(error(
3891                                    &selector,
3892                                    "group_by needs homogeneous key types",
3893                                ));
3894                            }
3895                            if key_type.is_none() {
3896                                key_type = Some(copy_type(ty, budget, &selector)?);
3897                            }
3898                        }
3899                    }
3900                    let mut groups = Vec::new();
3901                    if !elements.is_empty() {
3902                        let mut fields = metadata_vec(2, budget, location)?;
3903                        budget.charge("key".len() + "items".len(), location.clone())?;
3904                        fields.push(("key".into(), key_type.unwrap_or(ValueType::Unestablished)));
3905                        fields.push((
3906                            "items".into(),
3907                            ValueType::Array(ArrayType::Elements(elements)),
3908                        ));
3909                        push_metadata(&mut groups, ValueType::Record(fields), budget, location)?;
3910                    }
3911                    push_metadata(
3912                        &mut result,
3913                        ValueType::Array(ArrayType::Elements(groups)),
3914                        budget,
3915                        location,
3916                    )?;
3917                }
3918                Ok(result)
3919            }
3920            ValueExpressionKind::Require {
3921                condition,
3922                value,
3923                message,
3924            } => {
3925                self.condition(condition, budget)?;
3926                let types = self.walk(value, budget)?;
3927                allocation::<u8>(message.value.len(), location)?;
3928                budget.charge(message.value.len(), Location::Query(message.range.clone()))?;
3929                Ok(types)
3930            }
3931            ValueExpressionKind::Range { lower, upper } => {
3932                for expression in [lower, upper] {
3933                    for ty in self.walk(expression, budget)? {
3934                        require_type(&ty, ScalarRequirement::Integer, location)?;
3935                    }
3936                }
3937                one_type(
3938                    ValueType::Set(Some(SetElementType::Integer)),
3939                    budget,
3940                    location,
3941                )
3942            }
3943            ValueExpressionKind::Conditional {
3944                branches,
3945                otherwise,
3946            } => {
3947                let mut types = Vec::new();
3948                for (condition, branch) in branches {
3949                    self.condition(condition, budget)?;
3950                    append_types(
3951                        &mut types,
3952                        self.walk_control(branch, budget, transfer)?,
3953                        budget,
3954                        location,
3955                    )?;
3956                }
3957                append_types(
3958                    &mut types,
3959                    self.walk_control(otherwise, budget, transfer)?,
3960                    budget,
3961                    location,
3962                )?;
3963                Ok(types)
3964            }
3965            ValueExpressionKind::Loop { states, body } => {
3966                let mut initial = metadata_vec(states.len(), budget, location)?;
3967                for (_, value) in states {
3968                    initial.push(self.walk(value, budget)?);
3969                }
3970                let saved = self.locals.len();
3971                self.queries.enter_scope(budget, &expression.range)?;
3972                let result = (|| {
3973                    for ((name, value), facts) in states.iter().zip(initial) {
3974                        function_local(name, &self.locals, budget)?;
3975                        if self.global_exists(&name.value, name.range.clone(), budget)? {
3976                            return Err(error(
3977                                &Location::Query(name.range.clone()),
3978                                "binding shadows an existing source or let",
3979                            ));
3980                        }
3981                        self.queries.prepare_local(name, value, &facts, budget)?;
3982                        push_metadata(
3983                            &mut self.locals,
3984                            (name.value.as_str(), facts),
3985                            budget,
3986                            location,
3987                        )?;
3988                    }
3989                    self.queries.prepare_computation(expression, budget)?;
3990                    self.walk_control(body, budget, Some(states.len()))
3991                })();
3992                self.locals.truncate(saved);
3993                self.queries.leave_scope();
3994                result
3995            }
3996            ValueExpressionKind::Break(value) => self.walk(value, budget),
3997            ValueExpressionKind::Continue(values) => {
3998                for value in values {
3999                    self.walk(value, budget)?;
4000                }
4001                Ok(Vec::new())
4002            }
4003            ValueExpressionKind::Block { bindings, value } => {
4004                let saved = self.locals.len();
4005                self.queries.enter_scope(budget, &expression.range)?;
4006                let result = (|| {
4007                    for (pattern, value) in bindings {
4008                        let types = self.walk(value, budget)?;
4009                        self.pattern(pattern, &types, false, budget)?;
4010                        self.queries.prepare_pattern(pattern, value, budget)?;
4011                    }
4012                    self.walk_control(value, budget, transfer)
4013                })();
4014                self.locals.truncate(saved);
4015                self.queries.leave_scope();
4016                result
4017            }
4018            ValueExpressionKind::ValueMatch { subject, arms } => {
4019                if arms.is_empty()
4020                    || !matches!(
4021                        arms.last().map(|a| (&a.pattern.value, &a.guard)),
4022                        Some((PatternKind::Wildcard, None))
4023                    )
4024                {
4025                    return Err(error(
4026                        location,
4027                        "value match needs a final unguarded wildcard",
4028                    ));
4029                }
4030                let facts = self.walk(subject, budget)?;
4031                let mut types = Vec::new();
4032                for (index, arm) in arms.iter().enumerate() {
4033                    if index + 1 < arms.len()
4034                        && matches!(arm.pattern.value, PatternKind::Wildcard)
4035                        && arm.guard.is_none()
4036                    {
4037                        return Err(error(
4038                            &Location::Query(arm.pattern.range.clone()),
4039                            "unguarded wildcard must be the final arm",
4040                        ));
4041                    }
4042                    let saved = self.locals.len();
4043                    self.queries.enter_scope(budget, &arm.pattern.range)?;
4044                    let result = (|| {
4045                        self.pattern(&arm.pattern, &facts, false, budget)?;
4046                        self.queries
4047                            .prepare_pattern(&arm.pattern, subject, budget)?;
4048                        if let Some(guard) = &arm.guard {
4049                            self.condition(guard, budget)?;
4050                        }
4051                        self.walk_control(&arm.value, budget, transfer)
4052                    })();
4053                    self.locals.truncate(saved);
4054                    self.queries.leave_scope();
4055                    append_types(&mut types, result?, budget, location)?;
4056                }
4057                Ok(types)
4058            }
4059            ValueExpressionKind::Comprehension {
4060                constructor,
4061                clauses,
4062                head,
4063            } => {
4064                let saved = self.locals.len();
4065                let result = (|| {
4066                    self.clauses(clauses, budget)?;
4067                    let types = self.walk(head, budget)?;
4068                    let ty = match constructor {
4069                        ValueConstructor::Array => ValueType::Array(ArrayType::Elements(types)),
4070                        ValueConstructor::Set => {
4071                            let mut element_type = None;
4072                            merge_set_types(&mut element_type, &types, budget, location)?;
4073                            ValueType::Set(element_type)
4074                        }
4075                    };
4076                    one_type(ty, budget, location)
4077                })();
4078                self.locals.truncate(saved);
4079                result
4080            }
4081        }
4082    }
4083
4084    fn scoped_types(
4085        &mut self,
4086        predicate: &'a PredicateArgument<Q>,
4087        source: &'a ValueExpression<Q>,
4088        types: &[ValueType],
4089        element: bool,
4090        budget: &mut Budget,
4091    ) -> Result<Vec<ValueType>, QueryError> {
4092        let range = &predicate.body.range;
4093        let location = Location::Query(range.clone());
4094        let saved = self.locals.len();
4095        self.queries.enter_scope(budget, range)?;
4096        let result = (|| {
4097            push_metadata(
4098                &mut self.locals,
4099                (PREDICATE_SUBJECT, copy_types(types, budget, &location)?),
4100                budget,
4101                &location,
4102            )?;
4103            if let Some(pattern) = &predicate.argument {
4104                self.pattern(pattern, types, true, budget)?;
4105            }
4106            self.queries.prepare_predicate(
4107                predicate.argument.as_ref(),
4108                source,
4109                types,
4110                element,
4111                budget,
4112            )?;
4113            self.walk(&predicate.body, budget)
4114        })();
4115        self.locals.truncate(saved);
4116        self.queries.leave_scope();
4117        result
4118    }
4119
4120    fn pattern(
4121        &mut self,
4122        pattern: &'a Pattern,
4123        facts: &[ValueType],
4124        required: bool,
4125        budget: &mut Budget,
4126    ) -> Result<(), QueryError> {
4127        let names = if required {
4128            pattern::required_facts(pattern, facts, budget)?
4129        } else {
4130            pattern::facts(pattern, facts, budget)?
4131        };
4132        for (name, facts) in names {
4133            let location = Location::Query(name.range.clone());
4134            function_local(name, &self.locals, budget)?;
4135            if self.global_exists(&name.value, name.range.clone(), budget)? {
4136                return Err(error(
4137                    &location,
4138                    "binding shadows an existing source or let",
4139                ));
4140            }
4141            push_metadata(
4142                &mut self.locals,
4143                (name.value.as_str(), facts),
4144                budget,
4145                &location,
4146            )?;
4147        }
4148        Ok(())
4149    }
4150
4151    fn clauses(
4152        &mut self,
4153        clauses: &'a [Located<ValueGenerator<Q>>],
4154        budget: &mut Budget,
4155    ) -> Result<(), QueryError> {
4156        for clause in clauses {
4157            let clause_location = Location::Query(clause.range.clone());
4158            budget.charge(1, clause_location.clone())?;
4159            match &clause.value {
4160                ValueGenerator::In {
4161                    binders,
4162                    source,
4163                    filter,
4164                } => {
4165                    if binders.is_empty() {
4166                        return Err(error(&clause_location, "in generator needs a binder"));
4167                    }
4168                    let source_types = self.walk(source, budget)?;
4169                    let mut elements = Vec::new();
4170                    for ty in source_types {
4171                        match shape_type(ty, budget, &clause_location)? {
4172                            ValueType::Array(
4173                                ArrayType::Positions(entries) | ArrayType::Elements(entries),
4174                            )
4175                            | ValueType::ArrayKind(entries) => {
4176                                append_types(&mut elements, entries, budget, &clause_location)?
4177                            }
4178                            ValueType::KeyedArray(keyed) => append_types(
4179                                &mut elements,
4180                                keyed.elements,
4181                                budget,
4182                                &clause_location,
4183                            )?,
4184                            ValueType::Set(element_type) => push_metadata(
4185                                &mut elements,
4186                                set_value_type(element_type),
4187                                budget,
4188                                &clause_location,
4189                            )?,
4190                            ValueType::Unestablished => push_metadata(
4191                                &mut elements,
4192                                ValueType::Unestablished,
4193                                budget,
4194                                &clause_location,
4195                            )?,
4196                            _ => {
4197                                return Err(error(
4198                                    &Location::Query(source.range.clone()),
4199                                    "in source needs an array or supported set",
4200                                ));
4201                            }
4202                        }
4203                    }
4204                    if elements.is_empty() {
4205                        push_metadata(
4206                            &mut elements,
4207                            ValueType::Unestablished,
4208                            budget,
4209                            &clause_location,
4210                        )?;
4211                    }
4212                    for binder in binders {
4213                        self.pattern(binder, &elements, false, budget)?;
4214                        self.queries
4215                            .prepare_generator(binder, source, &elements, true, budget)?;
4216                    }
4217                    if let Some(filter) = filter {
4218                        self.condition(filter, budget)?;
4219                    }
4220                }
4221                ValueGenerator::Bind {
4222                    binder,
4223                    value,
4224                    filter,
4225                } => {
4226                    let types = self.walk(value, budget)?;
4227                    self.pattern(binder, &types, false, budget)?;
4228                    self.queries
4229                        .prepare_generator(binder, value, &types, false, budget)?;
4230                    if let Some(filter) = filter {
4231                        self.condition(filter, budget)?;
4232                    }
4233                }
4234            }
4235        }
4236        Ok(())
4237    }
4238
4239    fn condition(
4240        &mut self,
4241        condition: &'a ValueExpression<Q>,
4242        budget: &mut Budget,
4243    ) -> Result<(), QueryError> {
4244        let location = Location::Query(condition.range.clone());
4245        for ty in self.walk(condition, budget)? {
4246            budget.charge(1, location.clone())?;
4247            require_type(&ty, ScalarRequirement::Boolean, &location)?;
4248        }
4249        Ok(())
4250    }
4251
4252    fn axis_sources(
4253        &mut self,
4254        sources: &'a [ValueExpression<Q>],
4255        budget: &mut Budget,
4256        location: &Location,
4257    ) -> Result<Vec<Option<ValueKeyType>>, QueryError> {
4258        if sources.is_empty() {
4259            return Err(error(location, "axis sources need nonzero rank"));
4260        }
4261        let mut facts = metadata_vec(sources.len(), budget, location)?;
4262        for source in sources {
4263            let types = self.walk(source, budget)?;
4264            facts.push(source_key_type(
4265                &types,
4266                budget,
4267                &Location::Query(source.range.clone()),
4268            )?);
4269        }
4270        Ok(facts)
4271    }
4272
4273    // Tuple and record positions retain each concrete branch possibility. Admit
4274    // the bounded product before materializing it; no unknown slot hides a known
4275    // incompatible type in a later access or recursive equality.
4276    fn positions(
4277        &mut self,
4278        children: impl Iterator<Item = &'a ValueExpression<Q>>,
4279        budget: &mut Budget,
4280        location: &Location,
4281    ) -> Result<Vec<Vec<ValueType>>, QueryError> {
4282        let mut positions = metadata_vec(1, budget, location)?;
4283        positions.push(Vec::new());
4284        for child in children {
4285            let types = self.walk(child, budget)?;
4286            positions = extend_type_positions(&positions, &types, budget, location)?;
4287        }
4288        Ok(positions)
4289    }
4290}
4291
4292fn zip_exact_type(
4293    left: &ValueType,
4294    right: &ValueType,
4295    budget: &mut Budget,
4296    location: &Location,
4297) -> Result<ValueType, QueryError> {
4298    if matches!(left, ValueType::Declared { .. }) || matches!(right, ValueType::Declared { .. }) {
4299        let left = shape_type(copy_type(left, budget, location)?, budget, location)?;
4300        let right = shape_type(copy_type(right, budget, location)?, budget, location)?;
4301        return zip_exact_type(&left, &right, budget, location);
4302    }
4303    let deferred = [ValueType::Unestablished];
4304    let [left, right] = [left, right].map(|ty| match ty {
4305        ValueType::KeyedArray(keyed) => Ok((keyed.axes.as_deref(), keyed.elements.as_slice())),
4306        ValueType::ArrayKind(elements) => Ok((None, elements.as_slice())),
4307        ValueType::Unestablished => Ok((None, deferred.as_slice())),
4308        _ => Err(error(location, "zip_exact needs keyed arrays")),
4309    });
4310    let (left_axes, a) = left?;
4311    let (right_axes, b) = right?;
4312    if let (Some(a), Some(b)) = (left_axes, right_axes)
4313        && !same_axis_types(a, b, budget, location)?
4314    {
4315        return Err(error(
4316            location,
4317            "zip_exact needs identical axis rank and types",
4318        ));
4319    }
4320    let axes = match left_axes.or(right_axes) {
4321        Some(axes) => Some(copy_axis_types(axes, budget, location)?),
4322        None => None,
4323    };
4324    let mut elements = Vec::new();
4325    for a in a {
4326        for b in b {
4327            let mut pair = metadata_vec(2, budget, location)?;
4328            pair.push(copy_type(a, budget, location)?);
4329            pair.push(copy_type(b, budget, location)?);
4330            push_metadata(&mut elements, ValueType::Tuple(pair), budget, location)?;
4331        }
4332    }
4333    Ok(ValueType::KeyedArray(KeyedArrayType { axes, elements }))
4334}
4335
4336// Keep known component facts even when one deferred component prevents a public
4337// complete axis description. This catches rank/identity conflicts in all arms.
4338fn key_component_type(
4339    ty: &ValueType,
4340    location: &Location,
4341) -> Result<Option<ValueKeyType>, QueryError> {
4342    match ty {
4343        ValueType::Declared {
4344            actual,
4345            declaration,
4346        } => {
4347            if !matches!(
4348                actual.as_ref(),
4349                ValueType::Unestablished | ValueType::ScalarKind
4350            ) {
4351                return key_component_type(actual, location);
4352            }
4353            match underlying(declaration) {
4354                DeclaredType::Integer => Ok(Some(ValueKeyType::Integer)),
4355                DeclaredType::Member(id) if !matches!(actual.as_ref(), ValueType::ScalarKind) => {
4356                    Ok(Some(ValueKeyType::Member(*id)))
4357                }
4358                _ => Err(error(location, "key components need integers or members")),
4359            }
4360        }
4361        // A successful primitive key must be integer. Keep that family and
4362        // rank established while its actual primitive kind remains deferred.
4363        ValueType::Integer | ValueType::ScalarKind => Ok(Some(ValueKeyType::Integer)),
4364        ValueType::Member(id) => Ok(Some(ValueKeyType::Member(*id))),
4365        ValueType::Unestablished => Ok(None),
4366        _ => Err(error(location, "key components need integers or members")),
4367    }
4368}
4369
4370fn merge_key_facts(
4371    facts: &mut Option<Vec<Option<ValueKeyType>>>,
4372    types: &[ValueType],
4373    budget: &mut Budget,
4374    location: &Location,
4375) -> Result<(), QueryError> {
4376    for ty in types {
4377        budget.charge(1, location.clone())?;
4378        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4379            Some(shape_type(
4380                copy_type(ty, budget, location)?,
4381                budget,
4382                location,
4383            )?)
4384        } else {
4385            None
4386        };
4387        let ty = normalized.as_ref().unwrap_or(ty);
4388        let components = match ty {
4389            ValueType::Unestablished | ValueType::TupleKind => continue,
4390            ValueType::Tuple(types) if !types.is_empty() => {
4391                let mut result = metadata_vec(types.len(), budget, location)?;
4392                for ty in types {
4393                    budget.charge(1, location.clone())?;
4394                    result.push(key_component_type(ty, location)?);
4395                }
4396                result
4397            }
4398            ValueType::Integer | ValueType::ScalarKind | ValueType::Member(_) => {
4399                let mut result = metadata_vec(1, budget, location)?;
4400                result.push(key_component_type(ty, location)?);
4401                result
4402            }
4403            _ => {
4404                return Err(error(
4405                    location,
4406                    "key needs an integer, member or nonempty flat tuple",
4407                ));
4408            }
4409        };
4410        match facts {
4411            Some(previous) => {
4412                if previous.len() != components.len() {
4413                    return Err(error(location, "key rank mismatch"));
4414                }
4415                for (previous, ty) in previous.iter_mut().zip(components) {
4416                    budget.charge(1, location.clone())?;
4417                    if previous.is_some() && ty.is_some() && *previous != ty {
4418                        return Err(error(location, "incompatible keyed dimension type"));
4419                    }
4420                    if previous.is_none() {
4421                        *previous = ty;
4422                    }
4423                }
4424            }
4425            None => *facts = Some(components),
4426        }
4427    }
4428    Ok(())
4429}
4430
4431fn check_key_facts(
4432    facts: &Option<Vec<Option<ValueKeyType>>>,
4433    axes: &[ValueKeyType],
4434    budget: &mut Budget,
4435    location: &Location,
4436) -> Result<(), QueryError> {
4437    if let Some(facts) = facts {
4438        if facts.len() != axes.len() {
4439            return Err(error(location, "key rank mismatch"));
4440        }
4441        for (fact, axis) in facts.iter().zip(axes) {
4442            budget.charge(1, location.clone())?;
4443            if fact.is_some_and(|fact| fact != *axis) {
4444                return Err(error(location, "incompatible keyed dimension type"));
4445            }
4446        }
4447    }
4448    Ok(())
4449}
4450
4451fn established_facts(
4452    facts: Option<Vec<Option<ValueKeyType>>>,
4453    budget: &mut Budget,
4454    location: &Location,
4455) -> Result<Option<Vec<ValueKeyType>>, QueryError> {
4456    let Some(facts) = facts else {
4457        return Ok(None);
4458    };
4459    let mut axes = metadata_vec(facts.len(), budget, location)?;
4460    for fact in facts {
4461        budget.charge(1, location.clone())?;
4462        let Some(fact) = fact else {
4463            return Ok(None);
4464        };
4465        axes.push(fact);
4466    }
4467    Ok(Some(axes))
4468}
4469
4470fn source_key_type(
4471    types: &[ValueType],
4472    budget: &mut Budget,
4473    location: &Location,
4474) -> Result<Option<ValueKeyType>, QueryError> {
4475    let mut result = None;
4476    for ty in types {
4477        budget.charge(1, location.clone())?;
4478        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4479            Some(shape_type(
4480                copy_type(ty, budget, location)?,
4481                budget,
4482                location,
4483            )?)
4484        } else {
4485            None
4486        };
4487        let ty = normalized.as_ref().unwrap_or(ty);
4488        let keys = match ty {
4489            ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
4490            | ValueType::ArrayKind(elements)
4491            | ValueType::KeyedArray(KeyedArrayType { elements, .. }) => {
4492                let mut key_type = None;
4493                for ty in elements {
4494                    budget.charge(1, location.clone())?;
4495                    let ty = key_component_type(ty, location)?;
4496                    if key_type.is_some() && ty.is_some() && key_type != ty {
4497                        return Err(error(location, "incompatible axis source type"));
4498                    }
4499                    if ty.is_some() {
4500                        key_type = ty;
4501                    }
4502                }
4503                key_type
4504            }
4505            ValueType::Set(Some(SetElementType::Integer)) => Some(ValueKeyType::Integer),
4506            ValueType::Set(Some(SetElementType::Member(id))) => Some(ValueKeyType::Member(*id)),
4507            ValueType::Set(None) | ValueType::Unestablished => None,
4508            _ => {
4509                return Err(error(
4510                    location,
4511                    "axis source needs an integer or member array/set",
4512                ));
4513            }
4514        };
4515        if result.is_some() && keys.is_some() && result != keys {
4516            return Err(error(location, "incompatible axis source type"));
4517        }
4518        if keys.is_some() {
4519            result = keys;
4520        }
4521    }
4522    Ok(result)
4523}
4524
4525fn check_source_facts(
4526    facts: &Option<Vec<Option<ValueKeyType>>>,
4527    sources: &[Option<ValueKeyType>],
4528    budget: &mut Budget,
4529    location: &Location,
4530) -> Result<(), QueryError> {
4531    if let Some(facts) = facts {
4532        if facts.len() != sources.len() {
4533            return Err(error(location, "axis source rank mismatch"));
4534        }
4535        for (fact, source) in facts.iter().zip(sources) {
4536            budget.charge(1, location.clone())?;
4537            if fact.is_some() && source.is_some() && *fact != *source {
4538                return Err(error(location, "incompatible axis source type"));
4539            }
4540            // Unknown sources do not acquire identity from the key.
4541        }
4542    }
4543    Ok(())
4544}
4545
4546#[derive(Clone, Copy)]
4547enum ScalarRequirement {
4548    String,
4549    Boolean,
4550    Integer,
4551    Numeric,
4552}
4553
4554pub(crate) fn predicate_types(
4555    types: &[ValueType],
4556    budget: &mut Budget,
4557    range: &std::ops::Range<usize>,
4558) -> Result<(), QueryError> {
4559    let location = Location::Query(range.clone());
4560    for ty in types {
4561        budget.charge(1, location.clone())?;
4562        require_type(ty, ScalarRequirement::Boolean, &location)?;
4563    }
4564    Ok(())
4565}
4566
4567fn require_type(
4568    ty: &ValueType,
4569    requirement: ScalarRequirement,
4570    location: &Location,
4571) -> Result<(), QueryError> {
4572    if let ValueType::Declared {
4573        actual,
4574        declaration,
4575    } = ty
4576    {
4577        if !matches!(
4578            actual.as_ref(),
4579            ValueType::Unestablished | ValueType::ScalarKind
4580        ) {
4581            return require_type(actual, requirement, location);
4582        }
4583        let expected = underlying(declaration);
4584        let accepted = match requirement {
4585            ScalarRequirement::String => matches!(expected, DeclaredType::String),
4586            ScalarRequirement::Boolean => matches!(expected, DeclaredType::Boolean),
4587            ScalarRequirement::Integer => matches!(expected, DeclaredType::Integer),
4588            ScalarRequirement::Numeric => {
4589                matches!(expected, DeclaredType::Integer | DeclaredType::Float)
4590            }
4591        };
4592        if accepted {
4593            return Ok(());
4594        }
4595        return Err(error(location, "incompatible declared operand type"));
4596    }
4597    let accepted = matches!(ty, ValueType::Unestablished | ValueType::ScalarKind)
4598        || match requirement {
4599            ScalarRequirement::String => matches!(ty, ValueType::String),
4600            ScalarRequirement::Boolean => matches!(ty, ValueType::Boolean),
4601            ScalarRequirement::Integer => matches!(ty, ValueType::Integer),
4602            ScalarRequirement::Numeric => matches!(ty, ValueType::Integer | ValueType::Float),
4603        };
4604    if !accepted {
4605        return Err(error(
4606            location,
4607            match requirement {
4608                ScalarRequirement::String => "dictionary key needs a present string",
4609                ScalarRequirement::Boolean => "condition needs a fixed Boolean",
4610                ScalarRequirement::Integer => "operation needs integers",
4611                ScalarRequirement::Numeric => "operation needs numeric operands",
4612            },
4613        ));
4614    }
4615    Ok(())
4616}
4617
4618fn set_value_type(ty: Option<SetElementType>) -> ValueType {
4619    match ty {
4620        Some(SetElementType::Boolean) => ValueType::Boolean,
4621        Some(SetElementType::Integer) => ValueType::Integer,
4622        Some(SetElementType::Float) => ValueType::Float,
4623        Some(SetElementType::Member(id)) => ValueType::Member(id),
4624        None => ValueType::Unestablished,
4625    }
4626}
4627
4628fn merge_set_types(
4629    element_type: &mut Option<SetElementType>,
4630    types: &[ValueType],
4631    budget: &mut Budget,
4632    location: &Location,
4633) -> Result<(), QueryError> {
4634    for ty in types {
4635        budget.charge(1, location.clone())?;
4636        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4637            Some(shape_type(
4638                copy_type(ty, budget, location)?,
4639                budget,
4640                location,
4641            )?)
4642        } else {
4643            None
4644        };
4645        let ty = normalized.as_ref().unwrap_or(ty);
4646        let ty = match ty {
4647            ValueType::Boolean => SetElementType::Boolean,
4648            ValueType::Integer => SetElementType::Integer,
4649            ValueType::Float => SetElementType::Float,
4650            ValueType::Member(id) => SetElementType::Member(*id),
4651            ValueType::Unestablished => continue,
4652            _ => {
4653                return Err(error(
4654                    location,
4655                    "set elements must have one supported scalar type",
4656                ));
4657            }
4658        };
4659        if element_type.is_some_and(|previous| previous != ty) {
4660            return Err(error(
4661                location,
4662                "set elements must have one supported scalar type",
4663            ));
4664        }
4665        *element_type = Some(ty);
4666    }
4667    Ok(())
4668}
4669
4670fn compatible_types(
4671    left: &ValueType,
4672    right: &ValueType,
4673    budget: &mut Budget,
4674    location: &Location,
4675) -> Result<bool, QueryError> {
4676    budget.within_evaluation(1, location.clone(), |budget| {
4677        budget.charge(1, location.clone())?;
4678        if let ValueType::Declared { declaration, .. } = left {
4679            if let ValueType::Declared {
4680                declaration: expected,
4681                ..
4682            } = right
4683            {
4684                return compatible_declarations(declaration, expected, budget, location);
4685            }
4686            let expected = DeclaredType::Optional(Box::new(copy_declaration(
4687                underlying(declaration),
4688                budget,
4689                location,
4690            )?));
4691            check_known_type(right, &expected, budget, location)?;
4692            return Ok(true);
4693        }
4694        if matches!(right, ValueType::Declared { .. }) {
4695            return compatible_types(right, left, budget, location);
4696        }
4697        Ok(match (left, right) {
4698            (ValueType::Absent, _) | (_, ValueType::Absent) => true,
4699            (ValueType::Unestablished, _) | (_, ValueType::Unestablished) => true,
4700            (
4701                ValueType::ScalarKind,
4702                ValueType::ScalarKind
4703                | ValueType::Boolean
4704                | ValueType::Integer
4705                | ValueType::Float
4706                | ValueType::String,
4707            )
4708            | (
4709                ValueType::Boolean | ValueType::Integer | ValueType::Float | ValueType::String,
4710                ValueType::ScalarKind,
4711            ) => true,
4712            (ValueType::Boolean, ValueType::Boolean)
4713            | (ValueType::Integer, ValueType::Integer)
4714            | (ValueType::Float, ValueType::Float)
4715            | (ValueType::String, ValueType::String) => true,
4716            (ValueType::RecordKind, ValueType::RecordKind | ValueType::Record(_))
4717            | (ValueType::Record(_), ValueType::RecordKind)
4718            | (ValueType::TupleKind, ValueType::TupleKind | ValueType::Tuple(_))
4719            | (ValueType::Tuple(_), ValueType::TupleKind) => true,
4720            (ValueType::Member(a), ValueType::Member(b)) => a == b,
4721            (ValueType::ArrayKind(a), ValueType::ArrayKind(b))
4722            | (
4723                ValueType::ArrayKind(a),
4724                ValueType::Array(ArrayType::Positions(b) | ArrayType::Elements(b)),
4725            )
4726            | (
4727                ValueType::Array(ArrayType::Positions(a) | ArrayType::Elements(a)),
4728                ValueType::ArrayKind(b),
4729            ) => compatible_keyed_elements(a, b, budget, location)?,
4730            (ValueType::ArrayKind(a), ValueType::KeyedArray(b)) => {
4731                compatible_keyed_elements(a, &b.elements, budget, location)?
4732            }
4733            (ValueType::KeyedArray(a), ValueType::ArrayKind(b)) => {
4734                compatible_keyed_elements(&a.elements, b, budget, location)?
4735            }
4736            (ValueType::KeyedArray(a), ValueType::KeyedArray(b)) => {
4737                if let (Some(a), Some(b)) = (&a.axes, &b.axes)
4738                    && !same_axis_types(a, b, budget, location)?
4739                {
4740                    return Ok(false);
4741                }
4742                compatible_keyed_elements(&a.elements, &b.elements, budget, location)?
4743            }
4744            (ValueType::Set(a), ValueType::Set(b)) => a.is_none() || b.is_none() || a == b,
4745            (ValueType::Tuple(a), ValueType::Tuple(b)) => {
4746                if a.len() != b.len() {
4747                    return Ok(false);
4748                }
4749                for (a, b) in a.iter().zip(b) {
4750                    if !compatible_types(a, b, budget, location)? {
4751                        return Ok(false);
4752                    }
4753                }
4754                true
4755            }
4756            (ValueType::Array(a), ValueType::Array(b)) => {
4757                match (a, b) {
4758                    (ArrayType::Positions(a), ArrayType::Positions(b)) => {
4759                        for (a, b) in a.iter().zip(b) {
4760                            if !compatible_types(a, b, budget, location)? {
4761                                return Ok(false);
4762                            }
4763                        }
4764                    }
4765                    (
4766                        ArrayType::Positions(a) | ArrayType::Elements(a),
4767                        ArrayType::Positions(b) | ArrayType::Elements(b),
4768                    ) => {
4769                        for a in a {
4770                            for b in b {
4771                                if !compatible_types(a, b, budget, location)? {
4772                                    return Ok(false);
4773                                }
4774                            }
4775                        }
4776                    }
4777                }
4778                true
4779            }
4780            (ValueType::Record(a), ValueType::Record(b)) => {
4781                if a.len() != b.len() {
4782                    return Ok(false);
4783                }
4784                for (label, ty) in a {
4785                    let mut other = None;
4786                    for (other_label, other_ty) in b {
4787                        budget.charge(
4788                            sum(1, label.len().min(other_label.len()), location)?,
4789                            location.clone(),
4790                        )?;
4791                        if label == other_label {
4792                            other = Some(other_ty);
4793                            break;
4794                        }
4795                    }
4796                    let Some(other) = other else {
4797                        return Ok(false);
4798                    };
4799                    if !compatible_types(ty, other, budget, location)? {
4800                        return Ok(false);
4801                    }
4802                }
4803                true
4804            }
4805            _ => false,
4806        })
4807    })
4808}
4809
4810// Keyed facts retain possible element types, not correspondence between keys.
4811// Check each known family here; execution checks the actual matching cells.
4812fn compatible_keyed_elements(
4813    left: &[ValueType],
4814    right: &[ValueType],
4815    budget: &mut Budget,
4816    location: &Location,
4817) -> Result<bool, QueryError> {
4818    if left.is_empty() || right.is_empty() {
4819        return Ok(true);
4820    }
4821    for (types, alternatives) in [(left, right), (right, left)] {
4822        for ty in types {
4823            let mut compatible = false;
4824            for alternative in alternatives {
4825                if compatible_types(ty, alternative, budget, location)? {
4826                    compatible = true;
4827                    break;
4828                }
4829            }
4830            if !compatible {
4831                return Ok(false);
4832            }
4833        }
4834    }
4835    Ok(true)
4836}
4837
4838fn binary_type(
4839    operator: ValueBinaryOperator,
4840    left: &ValueType,
4841    right: &ValueType,
4842    budget: &mut Budget,
4843    location: &Location,
4844) -> Result<ValueType, QueryError> {
4845    if !matches!(
4846        operator,
4847        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual
4848    ) && (matches!(left, ValueType::Declared { .. })
4849        || matches!(right, ValueType::Declared { .. }))
4850    {
4851        let left = shape_type(copy_type(left, budget, location)?, budget, location)?;
4852        let right = shape_type(copy_type(right, budget, location)?, budget, location)?;
4853        return binary_type(operator, &left, &right, budget, location);
4854    }
4855    match operator {
4856        ValueBinaryOperator::Add
4857        | ValueBinaryOperator::Subtract
4858        | ValueBinaryOperator::Multiply
4859        | ValueBinaryOperator::Divide
4860        | ValueBinaryOperator::Less
4861        | ValueBinaryOperator::LessEqual
4862        | ValueBinaryOperator::Greater
4863        | ValueBinaryOperator::GreaterEqual => {
4864            require_type(left, ScalarRequirement::Numeric, location)?;
4865            require_type(right, ScalarRequirement::Numeric, location)?;
4866            Ok(match operator {
4867                ValueBinaryOperator::Less
4868                | ValueBinaryOperator::LessEqual
4869                | ValueBinaryOperator::Greater
4870                | ValueBinaryOperator::GreaterEqual => ValueType::Boolean,
4871                ValueBinaryOperator::Divide => ValueType::Float,
4872                _ if matches!(left, ValueType::Float) || matches!(right, ValueType::Float) => {
4873                    ValueType::Float
4874                }
4875                _ if matches!(left, ValueType::ScalarKind)
4876                    || matches!(right, ValueType::ScalarKind) =>
4877                {
4878                    ValueType::ScalarKind
4879                }
4880                _ if matches!(left, ValueType::Unestablished)
4881                    || matches!(right, ValueType::Unestablished) =>
4882                {
4883                    ValueType::Unestablished
4884                }
4885                _ => ValueType::Integer,
4886            })
4887        }
4888        ValueBinaryOperator::IntegerDivide | ValueBinaryOperator::Modulo => {
4889            require_type(left, ScalarRequirement::Integer, location)?;
4890            require_type(right, ScalarRequirement::Integer, location)?;
4891            Ok(ValueType::Integer)
4892        }
4893        ValueBinaryOperator::Contains
4894        | ValueBinaryOperator::StartsWith
4895        | ValueBinaryOperator::EndsWith => {
4896            if !matches!(
4897                left,
4898                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished
4899            ) || !matches!(
4900                right,
4901                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished
4902            ) {
4903                return Err(string_operand_error(budget, location));
4904            }
4905            Ok(ValueType::Boolean)
4906        }
4907        ValueBinaryOperator::And | ValueBinaryOperator::Or => {
4908            require_type(left, ScalarRequirement::Boolean, location)?;
4909            require_type(right, ScalarRequirement::Boolean, location)?;
4910            Ok(ValueType::Boolean)
4911        }
4912        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual => {
4913            if !compatible_types(left, right, budget, location)? {
4914                return Err(error(location, "equality needs compatible types"));
4915            }
4916            Ok(ValueType::Boolean)
4917        }
4918        ValueBinaryOperator::In => {
4919            let mut element_type = None;
4920            let deferred_primitive = matches!(left, ValueType::ScalarKind)
4921                && matches!(
4922                    right,
4923                    ValueType::Unestablished
4924                        | ValueType::Set(
4925                            None | Some(
4926                                SetElementType::Boolean
4927                                    | SetElementType::Integer
4928                                    | SetElementType::Float
4929                            )
4930                        )
4931                );
4932            if deferred_primitive {
4933                budget.charge(1, location.clone())?;
4934            } else {
4935                merge_set_types(
4936                    &mut element_type,
4937                    std::slice::from_ref(left),
4938                    budget,
4939                    location,
4940                )?;
4941            }
4942            match right {
4943                ValueType::Set(ty)
4944                    if ty.is_none() || element_type.is_none() || ty == &element_type =>
4945                {
4946                    Ok(ValueType::Boolean)
4947                }
4948                ValueType::Unestablished => Ok(ValueType::Boolean),
4949                _ => Err(error(
4950                    location,
4951                    "membership needs a compatible supported set",
4952                )),
4953            }
4954        }
4955        ValueBinaryOperator::Concatenate => match (left, right) {
4956            (ValueType::String, ValueType::String)
4957            | (
4958                ValueType::ScalarKind,
4959                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished,
4960            )
4961            | (ValueType::String | ValueType::Unestablished, ValueType::ScalarKind) => {
4962                Ok(ValueType::String)
4963            }
4964            (ValueType::Array(a), ValueType::Array(b)) => {
4965                let (ArrayType::Positions(a_entries) | ArrayType::Elements(a_entries)) = a;
4966                let (ArrayType::Positions(b_entries) | ArrayType::Elements(b_entries)) = b;
4967                let mut entries = copy_types(a_entries, budget, location)?;
4968                append_types(
4969                    &mut entries,
4970                    copy_types(b_entries, budget, location)?,
4971                    budget,
4972                    location,
4973                )?;
4974                Ok(ValueType::Array(
4975                    if matches!((a, b), (ArrayType::Positions(_), ArrayType::Positions(_))) {
4976                        ArrayType::Positions(entries)
4977                    } else {
4978                        ArrayType::Elements(entries)
4979                    },
4980                ))
4981            }
4982            (ValueType::Unestablished, other) | (other, ValueType::Unestablished)
4983                if matches!(
4984                    other,
4985                    ValueType::Unestablished | ValueType::String | ValueType::Array(_)
4986                ) =>
4987            {
4988                Ok(ValueType::Unestablished)
4989            }
4990            _ => Err(error(
4991                location,
4992                "concatenation needs two strings or two positional arrays",
4993            )),
4994        },
4995    }
4996}
4997
4998pub(crate) fn boolean(value: ConstructedValue, location: &Location) -> Result<bool, QueryError> {
4999    if let ConstructedValue::Boolean(value) = value.into_data() {
5000        Ok(value)
5001    } else {
5002        Err(error(location, "condition needs a fixed Boolean"))
5003    }
5004}
5005
5006fn integer(value: ConstructedValue, location: &Location) -> Result<i64, QueryError> {
5007    if let ConstructedValue::Integer(value) = value.into_data() {
5008        Ok(value)
5009    } else {
5010        Err(error(location, "operation needs integers"))
5011    }
5012}
5013
5014fn scalar(
5015    value: ConstructedValue,
5016    budget: &mut Budget,
5017    location: &Location,
5018) -> Result<ConstructedValue, QueryError> {
5019    budget.charge_results(1, location.clone())?;
5020    Ok(value)
5021}
5022
5023fn exact_float(value: i64, location: &Location) -> Result<f64, QueryError> {
5024    let magnitude = value.unsigned_abs();
5025    let significant_bits = u64::BITS - magnitude.leading_zeros();
5026    if significant_bits > 53 && magnitude.trailing_zeros() < significant_bits - 53 {
5027        return Err(error(
5028            location,
5029            "integer cannot be represented exactly as binary64",
5030        ));
5031    }
5032    // The significand test establishes exact representation, including MIN;
5033    // a float-to-i64 cast would saturate and wrongly accept MAX.
5034    Ok(value as f64)
5035}
5036
5037fn numeric_float(value: ConstructedValue, location: &Location) -> Result<f64, QueryError> {
5038    match value.into_data() {
5039        ConstructedValue::Integer(value) => exact_float(value, location),
5040        ConstructedValue::Float(value) => {
5041            finite(value, location)?;
5042            Ok(value)
5043        }
5044        _ => Err(error(location, "operation needs numeric operands")),
5045    }
5046}
5047
5048fn unary(
5049    operator: ValueUnaryOperator,
5050    value: ConstructedValue,
5051    budget: &mut Budget,
5052    location: &Location,
5053) -> Result<ConstructedValue, QueryError> {
5054    let value = match (operator, value.into_data()) {
5055        (ValueUnaryOperator::Not, value) => ConstructedValue::Boolean(!boolean(value, location)?),
5056        (
5057            ValueUnaryOperator::Positive,
5058            value @ (ConstructedValue::Integer(_) | ConstructedValue::Float(_)),
5059        ) => value,
5060        (ValueUnaryOperator::Negative, ConstructedValue::Integer(value)) => {
5061            ConstructedValue::Integer(
5062                value
5063                    .checked_neg()
5064                    .ok_or_else(|| error(location, "integer overflow"))?,
5065            )
5066        }
5067        (ValueUnaryOperator::Negative, ConstructedValue::Float(value)) => {
5068            ConstructedValue::Float(-value)
5069        }
5070        _ => return Err(error(location, "operation needs numeric operands")),
5071    };
5072    scalar(value, budget, location)
5073}
5074
5075fn string_operand_error(budget: &mut Budget, location: &Location) -> QueryError {
5076    let message = "string matching needs two strings";
5077    match budget.charge(message.len(), location.clone()) {
5078        Ok(()) => error(location, message),
5079        Err(error) => error,
5080    }
5081}
5082
5083fn string_match_ref(
5084    operator: ValueBinaryOperator,
5085    left: &ConstructedValue,
5086    right: &ConstructedValue,
5087    budget: &mut Budget,
5088    location: &Location,
5089) -> Result<ConstructedValue, QueryError> {
5090    let (ConstructedValue::String(text), ConstructedValue::String(search)) =
5091        (left.data(), right.data())
5092    else {
5093        return Err(string_operand_error(budget, location));
5094    };
5095    // Admit a byte-scan bound before borrowing the existing text. No
5096    // substring or normalized text is allocated by these operations.
5097    let work = if search.is_empty() {
5098        0
5099    } else if operator == ValueBinaryOperator::Contains {
5100        sum(text.len(), search.len(), location)?
5101    } else {
5102        text.len().min(search.len())
5103    };
5104    budget.charge(work, location.clone())?;
5105    let matched = match operator {
5106        ValueBinaryOperator::Contains => text.contains(search.as_str()),
5107        ValueBinaryOperator::StartsWith => text.starts_with(search.as_str()),
5108        ValueBinaryOperator::EndsWith => text.ends_with(search.as_str()),
5109        _ => unreachable!("string matching operator"),
5110    };
5111    scalar(ConstructedValue::Boolean(matched), budget, location)
5112}
5113
5114fn binary(
5115    operator: ValueBinaryOperator,
5116    left: ConstructedValue,
5117    right: ConstructedValue,
5118    budget: &mut Budget,
5119    location: &Location,
5120) -> Result<ConstructedValue, QueryError> {
5121    let (left, right) = if matches!(
5122        operator,
5123        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual
5124    ) {
5125        (left, right)
5126    } else {
5127        (
5128            expose(left, budget, location)?,
5129            expose(right, budget, location)?,
5130        )
5131    };
5132    match operator {
5133        ValueBinaryOperator::Contains
5134        | ValueBinaryOperator::StartsWith
5135        | ValueBinaryOperator::EndsWith => {
5136            string_match_ref(operator, &left, &right, budget, location)
5137        }
5138        ValueBinaryOperator::Concatenate => match (left, right) {
5139            (ConstructedValue::String(left), ConstructedValue::String(right)) => {
5140                let len = sum(left.len(), right.len(), location)?;
5141                allocation::<u8>(len, location)?;
5142                budget.charge(len, location.clone())?;
5143                budget.charge_results(1, location.clone())?;
5144                let mut value = String::with_capacity(len);
5145                value.push_str(&left);
5146                value.push_str(&right);
5147                Ok(ConstructedValue::String(value))
5148            }
5149            (ConstructedValue::Array(left), ConstructedValue::Array(right)) => {
5150                let len = sum(left.len(), right.len(), location)?;
5151                let mut values = admit_container(len, budget, location)?;
5152                values.extend(left);
5153                values.extend(right);
5154                Ok(ConstructedValue::Array(values))
5155            }
5156            _ => Err(error(
5157                location,
5158                "concatenation needs two strings or two positional arrays",
5159            )),
5160        },
5161        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual => {
5162            let equal = equal_values(&left, &right, budget, location)?;
5163            scalar(
5164                ConstructedValue::Boolean(if operator == ValueBinaryOperator::Equal {
5165                    equal
5166                } else {
5167                    !equal
5168                }),
5169                budget,
5170                location,
5171            )
5172        }
5173        ValueBinaryOperator::In => {
5174            let ConstructedValue::Set {
5175                element_type,
5176                elements,
5177            } = right
5178            else {
5179                return Err(error(location, "membership needs a supported set"));
5180            };
5181            let ty = scalar_set_type(&left)
5182                .ok_or_else(|| error(location, "membership needs a supported scalar"))?;
5183            if element_type.is_some_and(|element_type| element_type != ty) {
5184                return Err(error(location, "membership element type mismatch"));
5185            }
5186            let mut found = false;
5187            for value in &elements {
5188                if compare_set_values(&left, value, budget, location)? == Ordering::Equal {
5189                    found = true;
5190                    break;
5191                }
5192            }
5193            scalar(ConstructedValue::Boolean(found), budget, location)
5194        }
5195        ValueBinaryOperator::And | ValueBinaryOperator::Or => {
5196            let a = boolean(left, location)?;
5197            let b = boolean(right, location)?;
5198            scalar(
5199                ConstructedValue::Boolean(if operator == ValueBinaryOperator::And {
5200                    a && b
5201                } else {
5202                    a || b
5203                }),
5204                budget,
5205                location,
5206            )
5207        }
5208        ValueBinaryOperator::IntegerDivide | ValueBinaryOperator::Modulo => {
5209            let a = integer(left, location)?;
5210            let b = integer(right, location)?;
5211            if b == 0 {
5212                return Err(error(location, "division by zero"));
5213            }
5214            let value = if operator == ValueBinaryOperator::IntegerDivide {
5215                a.checked_div(b)
5216            } else {
5217                a.checked_rem(b)
5218            }
5219            .ok_or_else(|| error(location, "integer overflow"))?;
5220            scalar(ConstructedValue::Integer(value), budget, location)
5221        }
5222        ValueBinaryOperator::Add
5223        | ValueBinaryOperator::Subtract
5224        | ValueBinaryOperator::Multiply => {
5225            if let (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) = (&left, &right) {
5226                let value = match operator {
5227                    ValueBinaryOperator::Add => a.checked_add(*b),
5228                    ValueBinaryOperator::Subtract => a.checked_sub(*b),
5229                    ValueBinaryOperator::Multiply => a.checked_mul(*b),
5230                    _ => unreachable!("integer arithmetic"),
5231                }
5232                .ok_or_else(|| error(location, "integer overflow"))?;
5233                return scalar(ConstructedValue::Integer(value), budget, location);
5234            }
5235            float_operation(
5236                operator,
5237                numeric_float(left, location)?,
5238                numeric_float(right, location)?,
5239                budget,
5240                location,
5241            )
5242        }
5243        ValueBinaryOperator::Divide => float_operation(
5244            operator,
5245            numeric_float(left, location)?,
5246            numeric_float(right, location)?,
5247            budget,
5248            location,
5249        ),
5250        ValueBinaryOperator::Less
5251        | ValueBinaryOperator::LessEqual
5252        | ValueBinaryOperator::Greater
5253        | ValueBinaryOperator::GreaterEqual => {
5254            let order = if let (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) =
5255                (&left, &right)
5256            {
5257                a.cmp(b)
5258            } else {
5259                numeric_float(left, location)?
5260                    .partial_cmp(&numeric_float(right, location)?)
5261                    .expect("finite numeric operands")
5262            };
5263            let value = match operator {
5264                ValueBinaryOperator::Less => order == Ordering::Less,
5265                ValueBinaryOperator::LessEqual => order != Ordering::Greater,
5266                ValueBinaryOperator::Greater => order == Ordering::Greater,
5267                ValueBinaryOperator::GreaterEqual => order != Ordering::Less,
5268                _ => unreachable!("comparison"),
5269            };
5270            scalar(ConstructedValue::Boolean(value), budget, location)
5271        }
5272    }
5273}
5274
5275fn float_operation(
5276    operator: ValueBinaryOperator,
5277    a: f64,
5278    b: f64,
5279    budget: &mut Budget,
5280    location: &Location,
5281) -> Result<ConstructedValue, QueryError> {
5282    let (value, mathematically_zero) = match operator {
5283        ValueBinaryOperator::Add => (a + b, a == -b),
5284        ValueBinaryOperator::Subtract => (a - b, a == b),
5285        ValueBinaryOperator::Multiply => (a * b, a == 0.0 || b == 0.0),
5286        ValueBinaryOperator::Divide => {
5287            if b == 0.0 {
5288                return Err(error(location, "division by zero"));
5289            }
5290            (a / b, a == 0.0)
5291        }
5292        _ => unreachable!("floating arithmetic"),
5293    };
5294    finite(value, location)?;
5295    if value == 0.0 && !mathematically_zero {
5296        return Err(error(location, "nonzero floating result rounded to zero"));
5297    }
5298    scalar(ConstructedValue::Float(value), budget, location)
5299}
5300
5301fn equal_values(
5302    left: &ConstructedValue,
5303    right: &ConstructedValue,
5304    budget: &mut Budget,
5305    location: &Location,
5306) -> Result<bool, QueryError> {
5307    budget.within_evaluation(1, location.clone(), |budget| {
5308        budget.charge(1, location.clone())?;
5309        if declaration(left).is_some() || declaration(right).is_some() {
5310            let a = constructed_value_type(left, budget, location.clone())?;
5311            let b = constructed_value_type(right, budget, location.clone())?;
5312            if !compatible_types(&a, &b, budget, location)? {
5313                return Err(error(
5314                    location,
5315                    "equality needs compatible underlying types",
5316                ));
5317            }
5318        }
5319        Ok(match (left.data(), right.data()) {
5320            (ConstructedValue::Absent, ConstructedValue::Absent) => true,
5321            (ConstructedValue::Absent, _) | (_, ConstructedValue::Absent) => false,
5322            (ConstructedValue::Boolean(a), ConstructedValue::Boolean(b)) => a == b,
5323            (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => a == b,
5324            (ConstructedValue::Float(a), ConstructedValue::Float(b)) => a == b,
5325            (ConstructedValue::String(a), ConstructedValue::String(b)) => {
5326                budget.charge(a.len().min(b.len()), location.clone())?;
5327                a == b
5328            }
5329            (ConstructedValue::Member(a), ConstructedValue::Member(b)) => {
5330                check_member_pair(a, b, budget, location)?;
5331                if a.type_id != b.type_id {
5332                    return Err(error(location, "equality needs compatible nominal types"));
5333                }
5334                a.member_id == b.member_id
5335            }
5336            (
5337                ConstructedValue::KeyedArray {
5338                    axes: a,
5339                    entries: a_entries,
5340                },
5341                ConstructedValue::KeyedArray {
5342                    axes: b,
5343                    entries: b_entries,
5344                },
5345            ) => {
5346                if a.len() != b.len() || a_entries.len() != b_entries.len() {
5347                    return Ok(false);
5348                }
5349                for (a, b) in a.iter().zip(b) {
5350                    budget.charge(1, location.clone())?;
5351                    if a.key_type != b.key_type {
5352                        return Err(error(location, "equality needs compatible keyed types"));
5353                    }
5354                    if !equal_keys(&a.keys, &b.keys, budget, location)? {
5355                        return Ok(false);
5356                    }
5357                }
5358                for (a, b) in a_entries.iter().zip(b_entries) {
5359                    budget.charge(1, location.clone())?;
5360                    if !equal_keys(&a.key, &b.key, budget, location)?
5361                        || !equal_values(&a.value, &b.value, budget, location)?
5362                    {
5363                        return Ok(false);
5364                    }
5365                }
5366                true
5367            }
5368            (ConstructedValue::Array(a), ConstructedValue::Array(b))
5369            | (ConstructedValue::Tuple(a), ConstructedValue::Tuple(b)) => {
5370                if a.len() != b.len() {
5371                    return Ok(false);
5372                }
5373                for (a, b) in a.iter().zip(b) {
5374                    if !equal_values(a, b, budget, location)? {
5375                        return Ok(false);
5376                    }
5377                }
5378                true
5379            }
5380            (
5381                ConstructedValue::Set {
5382                    element_type: a_type,
5383                    elements: a,
5384                },
5385                ConstructedValue::Set {
5386                    element_type: b_type,
5387                    elements: b,
5388                },
5389            ) => {
5390                if matches!((a_type, b_type), (Some(a), Some(b)) if a != b) {
5391                    return Err(error(location, "equality needs compatible set types"));
5392                }
5393                if a.len() != b.len() {
5394                    return Ok(false);
5395                }
5396                for value in a {
5397                    let mut found = false;
5398                    for other in b {
5399                        if equal_values(value, other, budget, location)? {
5400                            found = true;
5401                            break;
5402                        }
5403                    }
5404                    if !found {
5405                        return Ok(false);
5406                    }
5407                }
5408                true
5409            }
5410            (ConstructedValue::Record(a), ConstructedValue::Record(b)) => {
5411                if a.len() != b.len() {
5412                    return Ok(false);
5413                }
5414                for (label, value) in a {
5415                    let mut other = None;
5416                    for (other_label, other_value) in b {
5417                        budget.charge(
5418                            sum(1, label.len().min(other_label.len()), location)?,
5419                            location.clone(),
5420                        )?;
5421                        if label == other_label {
5422                            other = Some(other_value);
5423                            break;
5424                        }
5425                    }
5426                    let Some(other) = other else {
5427                        return Ok(false);
5428                    };
5429                    if !equal_values(value, other, budget, location)? {
5430                        return Ok(false);
5431                    }
5432                }
5433                true
5434            }
5435            _ => return Err(error(location, "equality needs compatible types")),
5436        })
5437    })
5438}
5439
5440fn run<'a, Q, H: ValueQueryHost<Q> + ?Sized>(
5441    expression: &'a PreparedValue<'a, Q>,
5442    queries: &mut H,
5443    locals: &mut Frame<'a>,
5444    budget: &mut Budget,
5445    check_value: &mut impl FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5446) -> Result<ConstructedValue, H::Error> {
5447    let value = run_value(expression, queries, locals, budget, check_value)?;
5448    let location = Location::Query(expression.range.clone());
5449    budget.within_evaluation(1, location.clone(), |budget| {
5450        Ok(value.materialize(budget, &location)?)
5451    })
5452}
5453
5454struct PreparedArguments<'a, 'f, Q, F> {
5455    arguments: &'a [PreparedArgument<'a, Q>],
5456    frames: Vec<Frame<'a>>,
5457    check_value: &'f mut F,
5458}
5459impl<Q, E, F> ValueArguments<Q, E> for PreparedArguments<'_, '_, Q, F>
5460where
5461    E: From<QueryError>,
5462    F: FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5463{
5464    fn evaluate(
5465        &mut self,
5466        expression: &ValueExpression<Q>,
5467        queries: &mut dyn ValueQueryHost<Q, Error = E>,
5468        budget: &mut Budget,
5469    ) -> Result<ConstructedRead, E> {
5470        let index = self
5471            .arguments
5472            .iter()
5473            .position(|argument| std::ptr::eq(argument.source, expression))
5474            .ok_or_else(|| {
5475                QueryError::query(
5476                    expression.range.clone(),
5477                    "argument was not prepared in this invocation",
5478                )
5479            })?;
5480        let value = run_value(
5481            &self.arguments[index].expression,
5482            queries,
5483            &mut self.frames[index],
5484            budget,
5485            self.check_value,
5486        )?;
5487        Ok(ConstructedRead {
5488            value,
5489            location: Location::Query(expression.range.clone()),
5490            declaration: None,
5491        })
5492    }
5493}
5494
5495fn run_value<'a, Q, H: ValueQueryHost<Q> + ?Sized>(
5496    expression: &'a PreparedValue<'a, Q>,
5497    queries: &mut H,
5498    locals: &mut Frame<'a>,
5499    budget: &mut Budget,
5500    check_value: &mut impl FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5501) -> Result<RuntimeValue, H::Error> {
5502    execution::run_scalar(expression, queries, locals, budget, check_value)
5503}
5504
5505fn entries(
5506    array: ConstructedValue,
5507    budget: &mut Budget,
5508    location: &Location,
5509) -> Result<ConstructedValue, QueryError> {
5510    let array = expose(array, budget, location)?;
5511    let ConstructedValue::KeyedArray { axes, entries } = array else {
5512        return Err(error(location, "entries needs a keyed array"));
5513    };
5514    check_keyed(&axes, &entries, budget, location)?;
5515    let mut records = admit_container(entries.len(), budget, location)?;
5516    for entry in entries {
5517        // The outer array and record add two levels around a moved cell.
5518        budget.within_evaluation(2, location.clone(), |budget| {
5519            copy_size(&entry.value, budget, location).map(|_| ())
5520        })?;
5521        let key = if entry.key.len() == 1 {
5522            budget.charge(1, location.clone())?;
5523            key_value(entry.key.into_iter().next().expect("rank one"))
5524        } else {
5525            let mut components = admit_container(entry.key.len(), budget, location)?;
5526            for key in entry.key {
5527                budget.charge(1, location.clone())?;
5528                components.push(key_value(key));
5529            }
5530            ConstructedValue::Tuple(components)
5531        };
5532        budget.within_evaluation(2, location.clone(), |budget| {
5533            copy_size(&key, budget, location).map(|_| ())
5534        })?;
5535        let mut fields = admit_container(2, budget, location)?;
5536        budget.charge("key".len() + "value".len(), location.clone())?;
5537        fields.push(("key".into(), key));
5538        fields.push(("value".into(), entry.value));
5539        records.push(ConstructedValue::Record(fields));
5540    }
5541    Ok(ConstructedValue::Array(records))
5542}
5543
5544fn zip_exact(
5545    left: ConstructedValue,
5546    right: ConstructedValue,
5547    budget: &mut Budget,
5548    location: &Location,
5549) -> Result<ConstructedValue, QueryError> {
5550    let left = expose(left, budget, location)?;
5551    let right = expose(right, budget, location)?;
5552    let (
5553        ConstructedValue::KeyedArray {
5554            axes,
5555            entries: left,
5556        },
5557        ConstructedValue::KeyedArray {
5558            axes: right_axes,
5559            entries: right,
5560        },
5561    ) = (left, right)
5562    else {
5563        return Err(error(location, "zip_exact needs keyed arrays"));
5564    };
5565    check_keyed(&axes, &left, budget, location)?;
5566    check_keyed(&right_axes, &right, budget, location)?;
5567    budget.charge(1, location.clone())?;
5568    if axes.len() != right_axes.len() {
5569        return Err(error(location, "zip_exact needs identical ordered axes"));
5570    }
5571    for (a, b) in axes.iter().zip(&right_axes) {
5572        budget.charge(1, location.clone())?;
5573        if a.key_type != b.key_type || !equal_keys(&a.keys, &b.keys, budget, location)? {
5574            return Err(error(location, "zip_exact needs identical ordered axes"));
5575        }
5576    }
5577    budget.charge(1, location.clone())?;
5578    if left.len() != right.len() {
5579        return Err(error(location, "zip_exact needs identical present keys"));
5580    }
5581    let mut entries = admit_container(left.len(), budget, location)?;
5582    for (a, b) in left.into_iter().zip(right) {
5583        if !equal_keys(&a.key, &b.key, budget, location)? {
5584            return Err(error(location, "zip_exact needs identical present keys"));
5585        }
5586        // The keyed container and tuple add two levels around moved values.
5587        budget.within_evaluation(2, location.clone(), |budget| {
5588            copy_size(&a.value, budget, location)?;
5589            copy_size(&b.value, budget, location).map(|_| ())
5590        })?;
5591        let mut pair = admit_container(2, budget, location)?;
5592        pair.push(a.value);
5593        pair.push(b.value);
5594        entries.push(KeyedEntry {
5595            key: a.key,
5596            value: ConstructedValue::Tuple(pair),
5597        });
5598    }
5599    Ok(ConstructedValue::KeyedArray { axes, entries })
5600}
5601
5602fn group_by(
5603    rows: ConstructedValue,
5604    field: &Located<String>,
5605    budget: &mut Budget,
5606    location: &Location,
5607) -> Result<ConstructedValue, QueryError> {
5608    let rows = expose(rows, budget, location)?;
5609    let ConstructedValue::Array(rows) = rows else {
5610        return Err(error(location, "group_by needs a positional array"));
5611    };
5612    let selector = Location::Query(field.range.clone());
5613    let mut groups = admit_container(0, budget, location)?;
5614    let mut key_type = None;
5615    for row in rows {
5616        budget.charge(1, location.clone())?;
5617        let ConstructedValue::Record(fields) = row.data() else {
5618            return Err(error(location, "group_by needs record rows"));
5619        };
5620        // Moving rows adds a group record and items array around them. Check
5621        // that added depth without copying or charging their result units again.
5622        budget.within_evaluation(2, location.clone(), |budget| {
5623            copy_size(&row, budget, location).map(|_| ())
5624        })?;
5625        let mut key = None;
5626        for (label, value) in fields {
5627            budget.charge(
5628                sum(1, label.len().min(field.value.len()), &selector)?,
5629                selector.clone(),
5630            )?;
5631            if label == &field.value {
5632                key = Some(value);
5633                break;
5634            }
5635        }
5636        let Some(key) = key else {
5637            admit_diagnostic("missing record field: ", &field.value, budget, &selector)?;
5638            return Err(error(
5639                &selector,
5640                format!("missing record field: {}", field.value),
5641            ));
5642        };
5643        let ty = match key.data() {
5644            ConstructedValue::Integer(_) => ValueType::Integer,
5645            ConstructedValue::String(_) => ValueType::String,
5646            ConstructedValue::Member(member) => ValueType::Member(member.type_id),
5647            _ => {
5648                return Err(error(
5649                    &selector,
5650                    "group_by keys need integers, strings or nominal members",
5651                ));
5652            }
5653        };
5654        budget.charge(1, selector.clone())?;
5655        if key_type.as_ref().is_some_and(|previous| previous != &ty) {
5656            return Err(error(&selector, "group_by needs homogeneous key types"));
5657        }
5658        key_type = Some(ty);
5659        let mut matched = None;
5660        for (index, group) in groups.iter().enumerate() {
5661            let ConstructedValue::Record(fields) = group else {
5662                unreachable!("group record")
5663            };
5664            if equal_values(key, &fields[0].1, budget, &selector)? {
5665                matched = Some(index);
5666                break;
5667            }
5668        }
5669        let index = match matched {
5670            Some(index) => index,
5671            None => {
5672                let key = copy_constructed_value(key, budget, selector.clone())?;
5673                let mut fields = admit_container(2, budget, location)?;
5674                budget.charge("key".len() + "items".len(), location.clone())?;
5675                fields.push(("key".into(), key));
5676                fields.push((
5677                    "items".into(),
5678                    ConstructedValue::Array(admit_container(0, budget, location)?),
5679                ));
5680                allocation::<ConstructedValue>(sum(groups.len(), 1, location)?, location)?;
5681                budget.charge(1, location.clone())?;
5682                budget.charge_results(1, location.clone())?;
5683                groups.push(ConstructedValue::Record(fields));
5684                groups.len() - 1
5685            }
5686        };
5687        let ConstructedValue::Record(fields) = &mut groups[index] else {
5688            unreachable!("group record")
5689        };
5690        let ConstructedValue::Array(items) = &mut fields[1].1 else {
5691            unreachable!("group items")
5692        };
5693        allocation::<ConstructedValue>(sum(items.len(), 1, location)?, location)?;
5694        budget.charge(1, location.clone())?;
5695        budget.charge_results(1, location.clone())?;
5696        items.push(row);
5697    }
5698    Ok(ConstructedValue::Array(groups))
5699}
5700
5701// Filtering loses positions while preserving established element facts.
5702fn filter_elements(
5703    ty: ValueType,
5704    budget: &mut Budget,
5705    location: &Location,
5706) -> Result<Vec<ValueType>, QueryError> {
5707    match shape_type(ty, budget, location)? {
5708        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5709        | ValueType::ArrayKind(elements) => Ok(elements),
5710        ValueType::Unestablished => one_type(ValueType::Unestablished, budget, location),
5711        _ => Err(error(location, "filter needs a positional array")),
5712    }
5713}
5714
5715// Removal loses positional facts, but retains admitted element kinds.
5716fn scalar_pipeline_elements(
5717    ty: ValueType,
5718    tally: bool,
5719    budget: &mut Budget,
5720    location: &Location,
5721) -> Result<Vec<ValueType>, QueryError> {
5722    let elements = match shape_type(ty, budget, location)? {
5723        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5724        | ValueType::ArrayKind(elements) => elements,
5725        ValueType::Unestablished => return one_type(ValueType::Unestablished, budget, location),
5726        _ => return Err(error(location, "collection stage needs a positional array")),
5727    };
5728    let mut element_type = None;
5729    for element in &elements {
5730        budget.charge(1, location.clone())?;
5731        let ty = shape_type(copy_type(element, budget, location)?, budget, location)?;
5732        if matches!(ty, ValueType::Unestablished) {
5733            continue;
5734        }
5735        if tally {
5736            if ty != ValueType::String {
5737                return Err(error(location, "tally needs strings"));
5738            }
5739        } else if !matches!(
5740            ty,
5741            ValueType::Boolean
5742                | ValueType::Integer
5743                | ValueType::Float
5744                | ValueType::String
5745                | ValueType::Member(_)
5746        ) {
5747            return Err(error(location, "unique needs homogeneous scalar elements"));
5748        }
5749        if element_type
5750            .as_ref()
5751            .is_some_and(|previous| previous != &ty)
5752        {
5753            return Err(error(location, "unique needs homogeneous scalar elements"));
5754        }
5755        element_type = Some(ty);
5756    }
5757    Ok(elements)
5758}
5759
5760fn unique_value(
5761    mut array: ConstructedValue,
5762    budget: &mut Budget,
5763    location: &Location,
5764) -> Result<ConstructedValue, QueryError> {
5765    if declaration(&array).is_some() {
5766        scalar_pipeline_elements(
5767            constructed_value_type(&array, budget, location.clone())?,
5768            false,
5769            budget,
5770            location,
5771        )?;
5772    }
5773    let mut data = &mut array;
5774    while let ConstructedValue::Declared { value, .. } = data {
5775        data = value;
5776    }
5777    let ConstructedValue::Array(rows) = data else {
5778        return Err(error(location, "unique needs a positional array"));
5779    };
5780    let mut element_type = None;
5781    let mut retained = 0;
5782    let mut strings = HashSet::new();
5783    for index in 0..rows.len() {
5784        budget.charge(1, location.clone())?;
5785        let ty = match rows[index].data() {
5786            ConstructedValue::Boolean(_) => ValueType::Boolean,
5787            ConstructedValue::Integer(_) => ValueType::Integer,
5788            ConstructedValue::Float(value) => {
5789                finite(*value, location)?;
5790                ValueType::Float
5791            }
5792            ConstructedValue::String(_) => ValueType::String,
5793            ConstructedValue::Member(member) => {
5794                member_bytes(member, budget, location)?;
5795                ValueType::Member(member.type_id)
5796            }
5797            _ => return Err(error(location, "unique needs homogeneous scalar elements")),
5798        };
5799        if element_type
5800            .as_ref()
5801            .is_some_and(|previous| previous != &ty)
5802        {
5803            return Err(error(location, "unique needs homogeneous scalar elements"));
5804        }
5805        element_type = Some(ty);
5806        let duplicate = if let ConstructedValue::String(key) = rows[index].data() {
5807            if declaration(&rows[index]).is_some() {
5808                constructed_value_type(&rows[index], budget, location.clone())?;
5809            }
5810            if index == 0 {
5811                // Reserve once so growing the index never rehashes earlier keys.
5812                allocation::<String>(rows.len(), location)?;
5813                budget.charge(rows.len() * size_of::<String>(), location.clone())?;
5814                strings
5815                    .try_reserve(rows.len())
5816                    .map_err(|_| error(location, "unique index allocation failed"))?;
5817            }
5818            budget.charge(sum(1, key.len(), location)?, location.clone())?;
5819            if strings.contains(key) {
5820                true
5821            } else {
5822                allocation::<u8>(key.len(), location)?;
5823                // Copy the new key, hash it for insertion, and retain its row.
5824                let bytes = sum(key.len(), key.len(), location)?;
5825                budget.charge(sum(2, bytes, location)?, location.clone())?;
5826                strings.insert(key.clone());
5827                false
5828            }
5829        } else {
5830            let mut duplicate = false;
5831            for previous in &rows[..retained] {
5832                if equal_values(previous, &rows[index], budget, location)? {
5833                    duplicate = true;
5834                    break;
5835                }
5836            }
5837            duplicate
5838        };
5839        if !duplicate {
5840            rows.swap(retained, index);
5841            retained += 1;
5842        }
5843    }
5844    rows.truncate(retained);
5845    Ok(array)
5846}
5847
5848fn tally_value(
5849    array: ConstructedValue,
5850    budget: &mut Budget,
5851    location: &Location,
5852) -> Result<ConstructedValue, QueryError> {
5853    if declaration(&array).is_some() {
5854        scalar_pipeline_elements(
5855            constructed_value_type(&array, budget, location.clone())?,
5856            true,
5857            budget,
5858            location,
5859        )?;
5860    }
5861    let ConstructedValue::Array(rows) = array.data() else {
5862        return Err(error(location, "tally needs a positional array"));
5863    };
5864    for row in rows {
5865        budget.charge(1, location.clone())?;
5866        if !matches!(row.data(), ConstructedValue::String(_)) {
5867            return Err(error(location, "tally needs strings"));
5868        }
5869    }
5870    let ConstructedValue::Array(rows) = expose(array, budget, location)? else {
5871        unreachable!("checked positional array")
5872    };
5873    let mut fields: Vec<(String, ConstructedValue)> = metadata_vec(rows.len(), budget, location)?;
5874    budget.charge(1, location.clone())?;
5875    budget.charge_results(1, location.clone())?;
5876    for row in rows {
5877        budget.charge(1, location.clone())?;
5878        let ConstructedValue::String(label) = expose(row, budget, location)? else {
5879            return Err(error(location, "tally needs strings"));
5880        };
5881        let mut existing = None;
5882        for (previous, count) in &mut fields {
5883            budget.charge(
5884                sum(1, previous.len().min(label.len()), location)?,
5885                location.clone(),
5886            )?;
5887            if *previous == label {
5888                existing = Some(count);
5889                break;
5890            }
5891        }
5892        if let Some(ConstructedValue::Integer(count)) = existing {
5893            *count = count
5894                .checked_add(1)
5895                .ok_or_else(|| error(location, "tally count overflow"))?;
5896            continue;
5897        }
5898        budget.charge(2, location.clone())?;
5899        budget.charge_results(2, location.clone())?;
5900        fields.push((label, ConstructedValue::Integer(1)));
5901    }
5902    Ok(ConstructedValue::Record(fields))
5903}
5904
5905fn sort_type(
5906    ty: ValueType,
5907    selector: Option<&Located<SortSelector>>,
5908    budget: &mut Budget,
5909    location: &Location,
5910) -> Result<ValueType, QueryError> {
5911    let retained = match &ty {
5912        ValueType::Declared { declaration, .. } => {
5913            Some(copy_declaration(declaration, budget, location)?)
5914        }
5915        _ => None,
5916    };
5917    let selected = selector.map_or_else(
5918        || location.clone(),
5919        |selector| Location::Query(selector.range.clone()),
5920    );
5921    let field = match selector {
5922        Some(Located {
5923            value: SortSelector::Field(field),
5924            range,
5925        }) => Some(Located::new(field.as_str(), range.clone())),
5926        _ => None,
5927    };
5928    let ty = match shape_type(ty, budget, location)? {
5929        ty @ (ValueType::Array(_) | ValueType::ArrayKind(_)) => {
5930            if matches!(
5931                selector.map(|selector| &selector.value),
5932                Some(SortSelector::Value)
5933            ) {
5934                return Err(error(&selected, "value ordering needs a dictionary"));
5935            }
5936            ValueType::Array(ArrayType::Elements(ordering_elements(
5937                ty, field, budget, location,
5938            )?))
5939        }
5940        ty @ ValueType::Set(_) => {
5941            if selector.is_some() {
5942                return Err(error(&selected, "set sorting uses its scalar values"));
5943            }
5944            ty
5945        }
5946        ValueType::Record(fields) => {
5947            if selector.is_some() {
5948                check_ordering_types(
5949                    fields.iter().map(|(_, value)| value),
5950                    field,
5951                    budget,
5952                    &selected,
5953                )?;
5954            }
5955            ValueType::Record(fields)
5956        }
5957        ty @ (ValueType::RecordKind | ValueType::Unestablished) => ty,
5958        _ => return Err(error(location, "sort needs an array, set or dictionary")),
5959    };
5960    Ok(if let Some(declaration) = retained {
5961        ValueType::Declared {
5962            actual: Box::new(ty),
5963            declaration,
5964        }
5965    } else {
5966        ty
5967    })
5968}
5969
5970// Ordering retains row facts but cannot retain their original positions.
5971fn ordering_elements(
5972    ty: ValueType,
5973    field: Option<Located<&str>>,
5974    budget: &mut Budget,
5975    location: &Location,
5976) -> Result<Vec<ValueType>, QueryError> {
5977    let elements = match shape_type(ty, budget, location)? {
5978        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5979        | ValueType::ArrayKind(elements) => elements,
5980        ValueType::Unestablished => return one_type(ValueType::Unestablished, budget, location),
5981        _ => return Err(error(location, "ordering needs a positional array")),
5982    };
5983    let selector = field.as_ref().map_or_else(
5984        || location.clone(),
5985        |field| Location::Query(field.range.clone()),
5986    );
5987    check_ordering_types(elements.iter(), field, budget, &selector)?;
5988    Ok(elements)
5989}
5990
5991fn check_ordering_types<'a>(
5992    elements: impl IntoIterator<Item = &'a ValueType>,
5993    field: Option<Located<&str>>,
5994    budget: &mut Budget,
5995    selector: &Location,
5996) -> Result<(), QueryError> {
5997    let mut key_type = None;
5998    for element in elements {
5999        budget.charge(1, selector.clone())?;
6000        let ty = shape_type(copy_type(element, budget, selector)?, budget, selector)?;
6001        let ty = if let Some(field) = &field {
6002            match ty {
6003                ValueType::Record(fields) => {
6004                    let mut selected = ValueType::Unestablished;
6005                    for (label, ty) in fields {
6006                        budget.charge(
6007                            sum(1, label.len().min(field.value.len()), selector)?,
6008                            selector.clone(),
6009                        )?;
6010                        if label == field.value {
6011                            selected = ty;
6012                            break;
6013                        }
6014                    }
6015                    selected
6016                }
6017                ValueType::RecordKind | ValueType::Unestablished | ValueType::Absent => continue,
6018                _ => return Err(error(selector, "field ordering needs record rows")),
6019            }
6020        } else {
6021            ty
6022        };
6023        let ty = shape_type(ty, budget, selector)?;
6024        match ty {
6025            // Presence and missing data remain runtime checks, including in
6026            // skipped branches. An optional declaration still supplies its kind.
6027            ValueType::Absent | ValueType::Unestablished => continue,
6028            ValueType::Integer | ValueType::Float | ValueType::String | ValueType::Member(_) => {}
6029            _ => {
6030                return Err(error(
6031                    selector,
6032                    "ordering keys need integers, finite floats, strings or nominal members",
6033                ));
6034            }
6035        }
6036        if key_type.as_ref().is_some_and(|previous| previous != &ty) {
6037            return Err(error(selector, "ordering needs homogeneous key types"));
6038        }
6039        key_type = Some(ty);
6040    }
6041    Ok(())
6042}
6043
6044#[derive(Clone, Copy, PartialEq, Eq)]
6045pub(crate) enum OrderingKeyType {
6046    Integer,
6047    Float,
6048    String,
6049    Member(NominalTypeId),
6050}
6051
6052pub(crate) fn ordering_key<'a>(
6053    row: &'a ConstructedValue,
6054    field: Option<&str>,
6055    budget: &mut Budget,
6056    location: &Location,
6057) -> Result<(&'a ConstructedValue, OrderingKeyType), QueryError> {
6058    budget.charge(1, location.clone())?;
6059    let key = if let Some(field) = field {
6060        let ConstructedValue::Record(fields) = row.data() else {
6061            return Err(error(location, "field ordering needs record rows"));
6062        };
6063        let mut key = None;
6064        for (label, value) in fields {
6065            budget.charge(
6066                sum(1, label.len().min(field.len()), location)?,
6067                location.clone(),
6068            )?;
6069            if label == field {
6070                key = Some(value);
6071                break;
6072            }
6073        }
6074        key.ok_or_else(|| error(location, "missing ordering field"))?
6075    } else {
6076        row
6077    };
6078    let key = key.data();
6079    let ty = match key {
6080        ConstructedValue::Integer(_) => OrderingKeyType::Integer,
6081        ConstructedValue::Float(value) => {
6082            finite(*value, location)?;
6083            OrderingKeyType::Float
6084        }
6085        ConstructedValue::String(_) => OrderingKeyType::String,
6086        ConstructedValue::Member(member) => {
6087            member_bytes(member, budget, location)?;
6088            OrderingKeyType::Member(member.type_id)
6089        }
6090        ConstructedValue::Absent => return Err(error(location, "ordering keys must be present")),
6091        _ => {
6092            return Err(error(
6093                location,
6094                "ordering keys need integers, finite floats, strings or nominal members",
6095            ));
6096        }
6097    };
6098    Ok((key, ty))
6099}
6100
6101pub(crate) fn compare_ordering_keys(
6102    left: &ConstructedValue,
6103    right: &ConstructedValue,
6104    budget: &mut Budget,
6105    location: &Location,
6106) -> Result<Ordering, QueryError> {
6107    budget.charge(1, location.clone())?;
6108    match (left, right) {
6109        (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => Ok(a.cmp(b)),
6110        (ConstructedValue::Float(a), ConstructedValue::Float(b)) => a
6111            .partial_cmp(b)
6112            .ok_or_else(|| error(location, "nonfinite float")),
6113        (ConstructedValue::String(a), ConstructedValue::String(b)) => {
6114            for (a, b) in a.bytes().zip(b.bytes()) {
6115                budget.charge(1, location.clone())?;
6116                let order = a.cmp(&b);
6117                if order != Ordering::Equal {
6118                    return Ok(order);
6119                }
6120            }
6121            Ok(a.len().cmp(&b.len()))
6122        }
6123        (ConstructedValue::Member(a), ConstructedValue::Member(b)) if a.type_id == b.type_id => {
6124            check_member_pair(a, b, budget, location)?;
6125            Ok(a.declaration_order.cmp(&b.declaration_order))
6126        }
6127        _ => Err(error(location, "ordering needs homogeneous key types")),
6128    }
6129}
6130
6131#[derive(Clone, Copy)]
6132enum SortKey<'a> {
6133    Value(&'a ConstructedValue),
6134    Label(&'a str),
6135}
6136
6137#[derive(Clone, Copy)]
6138struct OrderingRow<'a> {
6139    index: usize,
6140    key: SortKey<'a>,
6141}
6142
6143fn sort_value(
6144    mut collection: ConstructedValue,
6145    selector: Option<&Located<SortSelector>>,
6146    descending: bool,
6147    budget: &mut Budget,
6148    location: &Location,
6149) -> Result<ConstructedValue, QueryError> {
6150    if declaration(&collection).is_some() {
6151        sort_type(
6152            constructed_value_type(&collection, budget, location.clone())?,
6153            selector,
6154            budget,
6155            location,
6156        )?;
6157    }
6158    let selected = selector.map_or_else(
6159        || location.clone(),
6160        |selector| Location::Query(selector.range.clone()),
6161    );
6162    let field = match selector.map(|selector| &selector.value) {
6163        Some(SortSelector::Field(field)) => Some(field.as_str()),
6164        _ => None,
6165    };
6166    let data = collection.data();
6167    let count = match data {
6168        ConstructedValue::Array(rows) => {
6169            if matches!(
6170                selector.map(|selector| &selector.value),
6171                Some(SortSelector::Value)
6172            ) {
6173                return Err(error(&selected, "value ordering needs a dictionary"));
6174            }
6175            rows.len()
6176        }
6177        ConstructedValue::Set { elements, .. } => {
6178            if selector.is_some() {
6179                return Err(error(&selected, "set sorting uses its scalar values"));
6180            }
6181            elements.len()
6182        }
6183        ConstructedValue::Record(fields) => fields.len(),
6184        _ => return Err(error(location, "sort needs an array, set or dictionary")),
6185    };
6186    let mut order: Vec<OrderingRow<'_>> = metadata_vec(count, budget, location)?;
6187    let mut key_type = None;
6188    for index in 0..count {
6189        let key = match data {
6190            ConstructedValue::Set { elements, .. } => {
6191                budget.charge(1, selected.clone())?;
6192                SortKey::Value(elements[index].data())
6193            }
6194            ConstructedValue::Record(fields) if selector.is_none() => {
6195                budget.charge(1, selected.clone())?;
6196                SortKey::Label(&fields[index].0)
6197            }
6198            _ => {
6199                let row = match data {
6200                    ConstructedValue::Array(rows) => &rows[index],
6201                    ConstructedValue::Record(fields) => &fields[index].1,
6202                    _ => unreachable!("checked collection kind"),
6203                };
6204                let (key, ty) = ordering_key(row, field, budget, &selected)?;
6205                if key_type.is_some_and(|previous| previous != ty) {
6206                    return Err(error(&selected, "ordering needs homogeneous key types"));
6207                }
6208                if let (Some(first), ConstructedValue::Member(member)) = (order.first(), key)
6209                    && let SortKey::Value(ConstructedValue::Member(first)) = first.key
6210                {
6211                    check_member_pair(first, member, budget, &selected)?;
6212                }
6213                key_type = Some(ty);
6214                SortKey::Value(key)
6215            }
6216        };
6217        order.push(OrderingRow { index, key });
6218    }
6219    let mut scratch = metadata_vec(order.len(), budget, location)?;
6220    scratch.extend_from_slice(&order);
6221    let mut width = 1;
6222    while width < order.len() {
6223        // Admit one descriptor write per output slot before each merge pass.
6224        budget.charge(order.len(), location.clone())?;
6225        let mut start = 0;
6226        while start < order.len() {
6227            let middle = start.saturating_add(width).min(order.len());
6228            let end = middle.saturating_add(width).min(order.len());
6229            let (mut left, mut right) = (start, middle);
6230            for slot in &mut scratch[start..end] {
6231                let take_left = if right == end {
6232                    true
6233                } else if left == middle {
6234                    false
6235                } else {
6236                    let order = match (order[left].key, order[right].key) {
6237                        (SortKey::Label(left), SortKey::Label(right)) => {
6238                            crate::ordering::compare_text(left, right, budget, &selected)?
6239                        }
6240                        (SortKey::Value(left), SortKey::Value(right))
6241                            if matches!(data, ConstructedValue::Set { .. }) =>
6242                        {
6243                            compare_set_values(left, right, budget, &selected)?
6244                        }
6245                        (SortKey::Value(left), SortKey::Value(right)) => {
6246                            compare_ordering_keys(left, right, budget, &selected)?
6247                        }
6248                        _ => unreachable!("one collection key kind"),
6249                    };
6250                    if descending {
6251                        order != Ordering::Less
6252                    } else {
6253                        order != Ordering::Greater
6254                    }
6255                };
6256                *slot = if take_left {
6257                    let row = order[left];
6258                    left += 1;
6259                    row
6260                } else {
6261                    let row = order[right];
6262                    right += 1;
6263                    row
6264                };
6265            }
6266            start = end;
6267        }
6268        std::mem::swap(&mut order, &mut scratch);
6269        width = width.saturating_mul(2);
6270    }
6271    let mut destinations = metadata_vec(order.len(), budget, location)?;
6272    destinations.resize(order.len(), 0);
6273    for (destination, row) in order.iter().enumerate() {
6274        budget.charge(1, location.clone())?;
6275        destinations[row.index] = destination;
6276    }
6277    // End the key borrows before moving payloads. Declarations and stored field
6278    // order move with their original rows; transparent collection annotations survive.
6279    drop(order);
6280    drop(scratch);
6281    let mut data = &mut collection;
6282    while let ConstructedValue::Declared { value, .. } = data {
6283        data = value;
6284    }
6285    for index in 0..count {
6286        while destinations[index] != index {
6287            budget.charge(1, location.clone())?;
6288            let destination = destinations[index];
6289            match data {
6290                ConstructedValue::Array(rows) | ConstructedValue::Set { elements: rows, .. } => {
6291                    rows.swap(index, destination)
6292                }
6293                ConstructedValue::Record(fields) => fields.swap(index, destination),
6294                _ => unreachable!("checked collection kind"),
6295            }
6296            destinations.swap(index, destination);
6297        }
6298    }
6299    Ok(collection)
6300}
6301
6302fn extremum_position(
6303    operator: ValueExtremum,
6304    array: &ConstructedValue,
6305    field: Option<&Located<String>>,
6306    budget: &mut Budget,
6307    location: &Location,
6308) -> Result<Option<usize>, QueryError> {
6309    let ConstructedValue::Array(rows) = array.data() else {
6310        return Err(error(location, "ordering needs a positional array"));
6311    };
6312    let selector = field.map_or_else(
6313        || location.clone(),
6314        |field| Location::Query(field.range.clone()),
6315    );
6316    let mut selected: Option<(usize, &ConstructedValue)> = None;
6317    let mut key_type = None;
6318    for (index, row) in rows.iter().enumerate() {
6319        let (key, ty) = ordering_key(
6320            row,
6321            field.map(|field| field.value.as_str()),
6322            budget,
6323            &selector,
6324        )?;
6325        if key_type.is_some_and(|previous| previous != ty) {
6326            return Err(error(&selector, "ordering needs homogeneous key types"));
6327        }
6328        key_type = Some(ty);
6329        let replace = match selected {
6330            None => true,
6331            Some((_, previous)) => {
6332                let order = compare_ordering_keys(key, previous, budget, &selector)?;
6333                order
6334                    == if operator == ValueExtremum::Min {
6335                        Ordering::Less
6336                    } else {
6337                        Ordering::Greater
6338                    }
6339            }
6340        };
6341        if replace {
6342            selected = Some((index, key));
6343        }
6344    }
6345    Ok(selected.map(|(index, _)| index))
6346}
6347
6348fn reduce_ref(
6349    operator: ValueReducer,
6350    array: &ConstructedValue,
6351    budget: &mut Budget,
6352    location: &Location,
6353) -> Result<ConstructedValue, QueryError> {
6354    let array = array.data();
6355    // Sources are eager; reductions inspect their admitted immutable data.
6356    let (positional, keyed) = match &array {
6357        ConstructedValue::Array(values) => (values.as_slice(), &[][..]),
6358        ConstructedValue::KeyedArray { entries, .. } => (&[][..], entries.as_slice()),
6359        _ => return Err(error(location, "reducer needs an array")),
6360    };
6361    let len = positional.len().max(keyed.len());
6362    let values = positional
6363        .iter()
6364        .chain(keyed.iter().map(|entry| &entry.value));
6365    let value = match operator {
6366        ValueReducer::Length => {
6367            budget.charge(1, location.clone())?;
6368            ConstructedValue::Integer(
6369                i64::try_from(len).map_err(|_| error(location, "length overflow"))?,
6370            )
6371        }
6372        ValueReducer::Sum => {
6373            let mut total = 0_i64;
6374            for value in values {
6375                budget.charge(1, location.clone())?;
6376                let ConstructedValue::Integer(value) = value.data() else {
6377                    return Err(error(location, "sum needs integer array elements"));
6378                };
6379                total = total
6380                    .checked_add(*value)
6381                    .ok_or_else(|| error(location, "integer sum overflow"))?;
6382            }
6383            ConstructedValue::Integer(total)
6384        }
6385        ValueReducer::Forall | ValueReducer::Exists => {
6386            let mut result = operator == ValueReducer::Forall;
6387            for value in values {
6388                budget.charge(1, location.clone())?;
6389                let ConstructedValue::Boolean(value) = value.data() else {
6390                    return Err(error(
6391                        location,
6392                        "quantifier needs fixed Boolean array elements",
6393                    ));
6394                };
6395                // Scan every element even once the Boolean result is decisive.
6396                if operator == ValueReducer::Forall {
6397                    result &= value;
6398                } else {
6399                    result |= value;
6400                }
6401            }
6402            ConstructedValue::Boolean(result)
6403        }
6404    };
6405    scalar(value, budget, location)
6406}
6407
6408fn take_position(
6409    values: Vec<ConstructedValue>,
6410    position: usize,
6411    budget: &mut Budget,
6412    location: &Location,
6413) -> Result<ConstructedValue, QueryError> {
6414    let index = position
6415        .checked_sub(1)
6416        .filter(|index| *index < values.len())
6417        .ok_or_else(|| error(location, "missing collection position"))?;
6418    // Moving an admitted child needs no result readmission. Charge the traversal
6419    // that consumes preceding positions before selecting it.
6420    budget.charge(sum(index, 1, location)?, location.clone())?;
6421    Ok(values.into_iter().nth(index).expect("position checked"))
6422}
6423
6424fn integer_range(
6425    lower: i64,
6426    upper: i64,
6427    budget: &mut Budget,
6428    location: &Location,
6429) -> Result<ConstructedValue, QueryError> {
6430    let len = if upper < lower {
6431        0
6432    } else {
6433        usize::try_from(i128::from(upper) - i128::from(lower) + 1)
6434            .map_err(|_| error(location, "range cardinality overflow"))?
6435    };
6436    allocation::<ConstructedValue>(len, location)?;
6437    budget.charge(len, location.clone())?;
6438    let units = sum(
6439        1,
6440        len.checked_mul(2)
6441            .ok_or_else(|| error(location, "range allocation overflow"))?,
6442        location,
6443    )?;
6444    budget.charge_results(units, location.clone())?;
6445    let mut elements = Vec::with_capacity(len);
6446    for offset in 0..len {
6447        let value = i64::try_from(i128::from(lower) + offset as i128)
6448            .map_err(|_| error(location, "range value overflow"))?;
6449        elements.push(ConstructedValue::Integer(value));
6450    }
6451    Ok(ConstructedValue::Set {
6452        element_type: Some(SetElementType::Integer),
6453        elements,
6454    })
6455}
6456
6457fn insert_set(
6458    values: &mut Vec<ConstructedValue>,
6459    element_type: &mut Option<SetElementType>,
6460    value: ConstructedValue,
6461    admit_slot: bool,
6462    budget: &mut Budget,
6463    location: &Location,
6464) -> Result<(), QueryError> {
6465    let ty = scalar_set_type(&value)
6466        .ok_or_else(|| error(location, "set elements must have one supported scalar type"))?;
6467    if element_type.is_some_and(|previous| previous != ty) {
6468        return Err(error(location, "set element type mismatch"));
6469    }
6470    *element_type = Some(ty);
6471    match value.data() {
6472        ConstructedValue::Float(value) => finite(*value, location)?,
6473        ConstructedValue::Member(member) => {
6474            for previous in values.iter() {
6475                if let ConstructedValue::Member(previous) = previous.data() {
6476                    check_member_pair(previous, member, budget, location)?;
6477                }
6478            }
6479        }
6480        _ => {}
6481    }
6482    for existing in values.iter() {
6483        if compare_set_values(&value, existing, budget, location)? == Ordering::Equal {
6484            return Ok(());
6485        }
6486    }
6487    allocation::<ConstructedValue>(sum(values.len(), 1, location)?, location)?;
6488    if admit_slot {
6489        budget.charge_results(1, location.clone())?;
6490    }
6491    values.push(value);
6492    Ok(())
6493}
6494
6495/// Copy inferred facts under the active metadata allowance.
6496///
6497/// # Errors
6498/// Metadata allocation, work or nesting exhaustion retains the supplied location.
6499pub fn copy_value_types(
6500    types: &[ValueType],
6501    budget: &mut Budget,
6502    location: Location,
6503) -> Result<Vec<ValueType>, QueryError> {
6504    copy_types(types, budget, &location)
6505}
6506
6507// Shared tuple/record type products preserve every preflighted branch possibility.
6508pub(crate) fn extend_type_positions(
6509    positions: &[Vec<ValueType>],
6510    types: &[ValueType],
6511    budget: &mut Budget,
6512    location: &Location,
6513) -> Result<Vec<Vec<ValueType>>, QueryError> {
6514    let count = positions
6515        .len()
6516        .checked_mul(types.len())
6517        .ok_or_else(|| error(location, "type possibilities overflow"))?;
6518    let mut expanded = metadata_vec(count, budget, location)?;
6519    for position in positions {
6520        for ty in types {
6521            let mut entries = copy_types(position, budget, location)?;
6522            let ty = copy_type(ty, budget, location)?;
6523            push_metadata(&mut entries, ty, budget, location)?;
6524            expanded.push(entries);
6525        }
6526    }
6527    Ok(expanded)
6528}
6529
6530pub(crate) fn function_facts<K>(
6531    declaration: &DeclaredType<K>,
6532    budget: &mut Budget,
6533    location: &Location,
6534) -> Result<Vec<ValueType>, QueryError> {
6535    if crate::runtime_types::contains_native(declaration, budget, location)? {
6536        one_type(ValueType::Unestablished, budget, location)
6537    } else {
6538        let declaration =
6539            crate::runtime_types::constructed_declaration(declaration, budget, location)?;
6540        one_type(
6541            declared_fact(&declaration, ValueType::Unestablished, budget, location)?,
6542            budget,
6543            location,
6544        )
6545    }
6546}
6547
6548pub(crate) fn prepare_function_body<'a, Q, K, H: ValueQueryHost<Q>>(
6549    declaration: crate::functions::DeclarationRef<'a, Q, K>,
6550    queries: &mut H,
6551    budget: &mut Budget,
6552) -> Result<execution::Body<'a, Q, K>, QueryError> {
6553    let globals = BTreeMap::new();
6554    let mut analysis = Analysis::new(Globals::Facts(&globals), queries);
6555    let mut preparation = Preparation::new();
6556    for parameter in declaration.parameters.iter() {
6557        let types = function_facts(
6558            parameter.declaration,
6559            budget,
6560            &Location::Query(parameter.range.clone()),
6561        )?;
6562        match parameter.pattern {
6563            pattern::PatternRef::Name(name) => {
6564                function_local(name, &analysis.locals, budget)?;
6565                push_metadata(
6566                    &mut analysis.locals,
6567                    (name.value.as_str(), types),
6568                    budget,
6569                    &Location::Query(name.range.clone()),
6570                )?;
6571                push_metadata(
6572                    &mut preparation.locals,
6573                    name.value.as_str(),
6574                    budget,
6575                    &Location::Query(name.range.clone()),
6576                )?;
6577            }
6578            pattern::PatternRef::Pattern(pattern) => {
6579                analysis.pattern(pattern, &types, false, budget)?;
6580                pattern::names(pattern, &mut preparation.locals, budget)?;
6581            }
6582        }
6583    }
6584    let mut bindings = metadata_vec(
6585        declaration.bindings.len(),
6586        budget,
6587        &Location::Query(declaration.name.range.clone()),
6588    )?;
6589    for (name, value) in declaration.bindings {
6590        function_local(name, &analysis.locals, budget)?;
6591        let types = analysis.walk(value, budget)?;
6592        let expression = preparation.resolve(value, analysis.queries, budget)?;
6593        analysis
6594            .queries
6595            .prepare_local(name, value, &types, budget)?;
6596        push_metadata(
6597            &mut analysis.locals,
6598            (name.value.as_str(), types),
6599            budget,
6600            &Location::Query(name.range.clone()),
6601        )?;
6602        push_metadata(
6603            &mut preparation.locals,
6604            name.value.as_str(),
6605            budget,
6606            &Location::Query(name.range.clone()),
6607        )?;
6608        bindings.push((name.value.as_str(), expression));
6609    }
6610    analysis.walk(declaration.value, budget)?;
6611    let value = preparation.resolve(declaration.value, analysis.queries, budget)?;
6612    Ok(execution::Body {
6613        declaration,
6614        bindings,
6615        value,
6616    })
6617}
6618fn function_local(
6619    name: &Located<String>,
6620    locals: &[(&str, Vec<ValueType>)],
6621    budget: &mut Budget,
6622) -> Result<(), QueryError> {
6623    let location = Location::Query(name.range.clone());
6624    budget.charge(name.value.len() + 1, location.clone())?;
6625    if name.value == "_" {
6626        return Err(error(&location, "discard name cannot be bound"));
6627    }
6628    for (previous, _) in locals {
6629        budget.charge(1 + previous.len().min(name.value.len()), location.clone())?;
6630        if *previous == name.value {
6631            return Err(error(&location, "binding is already defined"));
6632        }
6633    }
6634    Ok(())
6635}
6636pub(crate) fn run_functions(
6637    declarations: &[crate::FunctionDeclaration],
6638    entry: &ValueExpression,
6639    bindings: &Bindings<ConstructedValue>,
6640    budget: &mut Budget,
6641    limits: crate::FunctionLimits,
6642) -> Result<ConstructedValue, crate::FunctionError<QueryError>> {
6643    let mut retained = Vec::new();
6644    for d in declarations {
6645        crate::projection::admit_slots::<crate::functions::DeclarationRef<'_, (), ()>>(
6646            1,
6647            budget,
6648            &d.name.range,
6649        )?;
6650        retained.push(d.as_borrowed().into());
6651    }
6652    run_function_refs(&retained, entry, bindings, budget, limits)
6653}
6654pub(crate) fn run_function_refs(
6655    declarations: &[crate::functions::DeclarationRef<'_, (), ()>],
6656    entry: &ValueExpression,
6657    bindings: &Bindings<ConstructedValue>,
6658    budget: &mut Budget,
6659    limits: crate::FunctionLimits,
6660) -> Result<ConstructedValue, crate::FunctionError<QueryError>> {
6661    let mut queries = NoQueries {
6662        bindings: Some(bindings),
6663    };
6664    let mut resolver = crate::functions::Resolver {
6665        queries: &mut queries,
6666        declarations,
6667    };
6668    let location = Location::Query(entry.range.clone());
6669    let mut bodies = metadata_vec(declarations.len(), budget, &location)?;
6670    for declaration in declarations {
6671        bodies.push(prepare_function_body(*declaration, &mut resolver, budget)?);
6672    }
6673    Analysis::new(Globals::Available(bindings), &mut resolver).walk(entry, budget)?;
6674    let mut preparation = Preparation::new();
6675    let entry = preparation.resolve(entry, &mut resolver, budget)?;
6676    let names = preparation.names;
6677    let mut frame = Frame::new(names.clone(), budget, &location)?;
6678    let mut calls = crate::functions::CallTrace::default();
6679    let mut check = |_: &ConstructedValue, _: &mut Budget, _: &Location| Ok(());
6680    let mut host = execution::ScalarHost {
6681        queries: &mut queries,
6682        frame: &mut frame,
6683        check: &mut check,
6684        query: std::marker::PhantomData,
6685        calls: &mut calls,
6686    };
6687    let result = execution::execute(
6688        &entry,
6689        &names,
6690        &bodies,
6691        &mut host,
6692        budget,
6693        limits.active_depth,
6694        0,
6695    )
6696    .and_then(|v| v.materialize(budget, &location));
6697    result.map_err(|error| crate::FunctionError {
6698        error,
6699        omitted_calls: calls.omitted,
6700        calls: calls
6701            .frames
6702            .into_iter()
6703            .map(|(id, range)| crate::FunctionCall {
6704                function: declarations[id].name.value.chars().take(64).collect(),
6705                range,
6706            })
6707            .collect(),
6708    })
6709}