Skip to main content

zirql/
value.rs

1use std::cmp::Ordering;
2use std::collections::BTreeMap;
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(mut declaration: &DeclaredType<()>) -> &DeclaredType<()> {
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 optional_access(
1758    value: ConstructedValue,
1759    key: ConstructedValue,
1760    budget: &mut Budget,
1761    location: &Location,
1762) -> Result<(bool, ConstructedValue), QueryError> {
1763    let mut child = None;
1764    if let Some(declaration) = declaration(&value) {
1765        child = match underlying(declaration) {
1766            DeclaredType::Record(fields) => {
1767                let ConstructedValue::String(label) = key.data() else {
1768                    return Err(error(location, "record lookup needs a string label"));
1769                };
1770                Some(copy_declaration(
1771                    record_declaration(fields, label, budget, location)?,
1772                    budget,
1773                    location,
1774                )?)
1775            }
1776            DeclaredType::Array(element)
1777            | DeclaredType::Sequence(element)
1778            | DeclaredType::Keyed(element) => Some(copy_declaration(element, budget, location)?),
1779            _ => None,
1780        };
1781    }
1782    let mut selected = None;
1783    match expose(value, budget, location)? {
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 (candidate, value) in fields {
1789                budget.charge(1 + label.len().min(candidate.len()), location.clone())?;
1790                if label == candidate {
1791                    selected = Some(value);
1792                    break;
1793                }
1794            }
1795        }
1796        ConstructedValue::Array(values) => {
1797            let position = integer(key, location)?;
1798            if let Some(index) = usize::try_from(position)
1799                .ok()
1800                .and_then(|position| position.checked_sub(1))
1801            {
1802                budget.charge(values.len().min(index.saturating_add(1)), location.clone())?;
1803                selected = values.into_iter().nth(index);
1804            }
1805        }
1806        ConstructedValue::KeyedArray { axes, entries } => {
1807            let key = value_keys(key.into_data(), budget, location)?;
1808            if key.len() != axes.len() {
1809                return Err(error(location, "key rank mismatch"));
1810            }
1811            for (component, axis) in key.iter().zip(&axes) {
1812                budget.charge(1, location.clone())?;
1813                if key_type(component) != axis.key_type {
1814                    return Err(error(location, "incompatible key type"));
1815                }
1816                // Validate nominal metadata even for a key outside the axis.
1817                for candidate in &axis.keys {
1818                    compare_keys(component, candidate, budget, location)?;
1819                }
1820            }
1821            for entry in entries {
1822                budget.charge(1, location.clone())?;
1823                if equal_keys(&key, &entry.key, budget, location)? {
1824                    selected = Some(entry.value);
1825                    break;
1826                }
1827            }
1828        }
1829        _ => return Err(error(location, "lookup needs a record or array")),
1830    }
1831    let present = selected.is_some();
1832    let value = match selected {
1833        Some(value) => value,
1834        None => scalar(ConstructedValue::Absent, budget, location)?,
1835    };
1836    let value = match child {
1837        Some(child) => annotate(
1838            value,
1839            DeclaredType::Optional(Box::new(child)),
1840            budget,
1841            location,
1842        )?,
1843        None => value,
1844    };
1845    Ok((present, value))
1846}
1847
1848fn error(location: &Location, message: impl Into<String>) -> QueryError {
1849    QueryError {
1850        location: location.clone(),
1851        message: message.into(),
1852        category: QueryErrorCategory::Other,
1853    }
1854}
1855
1856// Availability checks can discard a Bindings::get error. Admit its potential
1857// diagnostic before lookup as well as messages that are actually returned.
1858fn admit_diagnostic(
1859    prefix: &str,
1860    value: &str,
1861    budget: &mut Budget,
1862    location: &Location,
1863) -> Result<(), QueryError> {
1864    let len = sum(prefix.len(), value.len(), location)?;
1865    allocation::<u8>(len, location)?;
1866    budget.charge(len, location.clone())
1867}
1868
1869fn sum(a: usize, b: usize, location: &Location) -> Result<usize, QueryError> {
1870    a.checked_add(b)
1871        .ok_or_else(|| error(location, "allocation size overflow"))
1872}
1873
1874pub(crate) fn allocation<T>(len: usize, location: &Location) -> Result<(), QueryError> {
1875    if len
1876        .checked_mul(size_of::<T>())
1877        .is_none_or(|size| size > isize::MAX as usize)
1878    {
1879        return Err(error(location, "allocation size overflow"));
1880    }
1881    Ok(())
1882}
1883
1884fn metadata_vec<T>(
1885    len: usize,
1886    budget: &mut Budget,
1887    location: &Location,
1888) -> Result<Vec<T>, QueryError> {
1889    allocation::<T>(len, location)?;
1890    budget.charge(len, location.clone())?;
1891    Ok(Vec::with_capacity(len))
1892}
1893
1894fn push_metadata<T>(
1895    values: &mut Vec<T>,
1896    value: T,
1897    budget: &mut Budget,
1898    location: &Location,
1899) -> Result<(), QueryError> {
1900    allocation::<T>(sum(values.len(), 1, location)?, location)?;
1901    budget.charge(1, location.clone())?;
1902    values.push(value);
1903    Ok(())
1904}
1905
1906fn admit_container<T>(
1907    len: usize,
1908    budget: &mut Budget,
1909    location: &Location,
1910) -> Result<Vec<T>, QueryError> {
1911    allocation::<T>(len, location)?;
1912    budget.charge(sum(len, 1, location)?, location.clone())?;
1913    budget.charge_results(sum(len, 1, location)?, location.clone())?;
1914    Ok(Vec::with_capacity(len))
1915}
1916
1917fn finite(value: f64, location: &Location) -> Result<(), QueryError> {
1918    if !value.is_finite() {
1919        return Err(error(location, "nonfinite float"));
1920    }
1921    Ok(())
1922}
1923
1924fn member_bytes(
1925    member: &NominalMember,
1926    budget: &mut Budget,
1927    location: &Location,
1928) -> Result<usize, QueryError> {
1929    allocation::<u8>(member.type_name.len(), location)?;
1930    allocation::<u8>(member.member_name.len(), location)?;
1931    let bytes = sum(member.type_name.len(), member.member_name.len(), location)?;
1932    budget.charge(sum(1, bytes, location)?, location.clone())?;
1933    Ok(bytes)
1934}
1935
1936pub(crate) fn check_member_pair(
1937    left: &NominalMember,
1938    right: &NominalMember,
1939    budget: &mut Budget,
1940    location: &Location,
1941) -> Result<(), QueryError> {
1942    budget.charge(1, location.clone())?;
1943    if left.type_id != right.type_id {
1944        return Ok(());
1945    }
1946    budget.charge(
1947        left.type_name.len().min(right.type_name.len()),
1948        location.clone(),
1949    )?;
1950    if left.type_name != right.type_name {
1951        return Err(error(location, "inconsistent nominal type name"));
1952    }
1953    if left.member_id == right.member_id {
1954        budget.charge(
1955            left.member_name.len().min(right.member_name.len()),
1956            location.clone(),
1957        )?;
1958        if left.declaration_order != right.declaration_order
1959            || left.member_name != right.member_name
1960        {
1961            return Err(error(location, "inconsistent nominal member metadata"));
1962        }
1963    } else if left.declaration_order == right.declaration_order {
1964        return Err(error(
1965            location,
1966            "distinct nominal members share declaration order",
1967        ));
1968    }
1969    Ok(())
1970}
1971
1972fn copy_member_admitted(member: &NominalMember) -> NominalMember {
1973    NominalMember {
1974        type_id: member.type_id,
1975        member_id: member.member_id,
1976        declaration_order: member.declaration_order,
1977        type_name: member.type_name.to_owned(),
1978        member_name: member.member_name.to_owned(),
1979    }
1980}
1981
1982fn key_value(key: ValueKey) -> ConstructedValue {
1983    match key {
1984        ValueKey::Integer(value) => ConstructedValue::Integer(value),
1985        ValueKey::Member(member) => ConstructedValue::Member(member),
1986    }
1987}
1988
1989fn key_type(key: &ValueKey) -> ValueKeyType {
1990    match key {
1991        ValueKey::Integer(_) => ValueKeyType::Integer,
1992        ValueKey::Member(member) => ValueKeyType::Member(member.type_id),
1993    }
1994}
1995
1996fn key_set_type(ty: ValueKeyType) -> SetElementType {
1997    match ty {
1998        ValueKeyType::Integer => SetElementType::Integer,
1999        ValueKeyType::Member(id) => SetElementType::Member(id),
2000    }
2001}
2002
2003fn compare_keys(
2004    left: &ValueKey,
2005    right: &ValueKey,
2006    budget: &mut Budget,
2007    location: &Location,
2008) -> Result<Ordering, QueryError> {
2009    budget.charge(1, location.clone())?;
2010    match (left, right) {
2011        (ValueKey::Integer(a), ValueKey::Integer(b)) => Ok(a.cmp(b)),
2012        (ValueKey::Member(a), ValueKey::Member(b)) if a.type_id == b.type_id => {
2013            check_member_pair(a, b, budget, location)?;
2014            Ok(if a.member_id == b.member_id {
2015                Ordering::Equal
2016            } else {
2017                a.declaration_order.cmp(&b.declaration_order)
2018            })
2019        }
2020        _ => Err(error(location, "incompatible keyed dimension type")),
2021    }
2022}
2023
2024fn keys_size(
2025    keys: &[ValueKey],
2026    budget: &mut Budget,
2027    location: &Location,
2028) -> Result<(usize, usize), QueryError> {
2029    allocation::<ValueKey>(keys.len(), location)?;
2030    let units = keys
2031        .len()
2032        .checked_mul(2)
2033        .ok_or_else(|| error(location, "key size overflow"))?;
2034    let mut bytes = 0;
2035    for key in keys {
2036        budget.charge(1, location.clone())?;
2037        if let ValueKey::Member(member) = key {
2038            bytes = sum(bytes, member_bytes(member, budget, location)?, location)?;
2039        }
2040    }
2041    Ok((units, bytes))
2042}
2043
2044fn copy_keys_admitted(
2045    keys: &[ValueKey],
2046    budget: &mut Budget,
2047    location: &Location,
2048) -> Result<Vec<ValueKey>, QueryError> {
2049    let mut copied = Vec::with_capacity(keys.len());
2050    for key in keys {
2051        budget.charge(1, location.clone())?;
2052        copied.push(match key {
2053            ValueKey::Integer(value) => ValueKey::Integer(*value),
2054            ValueKey::Member(member) => ValueKey::Member(copy_member_admitted(member)),
2055        });
2056    }
2057    Ok(copied)
2058}
2059
2060fn copy_key(
2061    key: &ValueKey,
2062    budget: &mut Budget,
2063    location: &Location,
2064) -> Result<ValueKey, QueryError> {
2065    budget.charge(1, location.clone())?;
2066    budget.charge_results(1, location.clone())?;
2067    Ok(match key {
2068        ValueKey::Integer(value) => ValueKey::Integer(*value),
2069        ValueKey::Member(member) => {
2070            let bytes = member_bytes(member, budget, location)?;
2071            budget.charge(bytes, location.clone())?;
2072            ValueKey::Member(copy_member_admitted(member))
2073        }
2074    })
2075}
2076
2077fn check_axes(
2078    axes: &[KeyAxis],
2079    budget: &mut Budget,
2080    location: &Location,
2081) -> Result<(), QueryError> {
2082    if axes.is_empty() {
2083        return Err(error(
2084            location,
2085            "keyed arrays need nonzero rank and established axes",
2086        ));
2087    }
2088    for axis in axes {
2089        budget.charge(1, location.clone())?;
2090        for (index, key) in axis.keys.iter().enumerate() {
2091            budget.charge(1, location.clone())?;
2092            if key_type(key) != axis.key_type {
2093                return Err(error(location, "axis key type mismatch"));
2094            }
2095            if let ValueKey::Member(member) = key {
2096                member_bytes(member, budget, location)?;
2097            }
2098            for previous in &axis.keys[..index] {
2099                if compare_keys(previous, key, budget, location)? == Ordering::Equal {
2100                    return Err(error(location, "duplicate axis key"));
2101                }
2102            }
2103        }
2104    }
2105    let members = axes
2106        .iter()
2107        .flat_map(|axis| &axis.keys)
2108        .filter_map(|key| match key {
2109            ValueKey::Member(member) => Some(member),
2110            ValueKey::Integer(_) => None,
2111        });
2112    for (index, member) in members.clone().enumerate() {
2113        for previous in members.clone().take(index) {
2114            check_member_pair(previous, member, budget, location)?;
2115        }
2116    }
2117    Ok(())
2118}
2119
2120fn copy_axes(
2121    axes: &[KeyAxis],
2122    budget: &mut Budget,
2123    location: &Location,
2124) -> Result<Vec<KeyAxis>, QueryError> {
2125    check_axes(axes, budget, location)?;
2126    allocation::<KeyAxis>(axes.len(), location)?;
2127    budget.charge(axes.len(), location.clone())?;
2128    budget.charge_results(axes.len(), location.clone())?;
2129    let mut copied = Vec::with_capacity(axes.len());
2130    for axis in axes {
2131        let (units, bytes) = keys_size(&axis.keys, budget, location)?;
2132        budget.charge(bytes, location.clone())?;
2133        budget.charge_results(units, location.clone())?;
2134        copied.push(KeyAxis {
2135            key_type: axis.key_type,
2136            keys: copy_keys_admitted(&axis.keys, budget, location)?,
2137        });
2138    }
2139    Ok(copied)
2140}
2141
2142fn copy_axis_types(
2143    types: &[ValueKeyType],
2144    budget: &mut Budget,
2145    location: &Location,
2146) -> Result<Vec<ValueKeyType>, QueryError> {
2147    if types.is_empty() {
2148        return Err(error(location, "keyed arrays need nonzero rank"));
2149    }
2150    let mut copied = metadata_vec(types.len(), budget, location)?;
2151    for ty in types {
2152        budget.charge(1, location.clone())?;
2153        copied.push(*ty);
2154    }
2155    Ok(copied)
2156}
2157
2158fn axis_types(
2159    axes: &[KeyAxis],
2160    budget: &mut Budget,
2161    location: &Location,
2162) -> Result<Vec<ValueKeyType>, QueryError> {
2163    let mut types = metadata_vec(axes.len(), budget, location)?;
2164    for axis in axes {
2165        budget.charge(1, location.clone())?;
2166        types.push(axis.key_type);
2167    }
2168    Ok(types)
2169}
2170
2171fn same_axis_types(
2172    left: &[ValueKeyType],
2173    right: &[ValueKeyType],
2174    budget: &mut Budget,
2175    location: &Location,
2176) -> Result<bool, QueryError> {
2177    budget.charge(1, location.clone())?;
2178    if left.len() != right.len() {
2179        return Ok(false);
2180    }
2181    for (a, b) in left.iter().zip(right) {
2182        budget.charge(1, location.clone())?;
2183        if a != b {
2184            return Ok(false);
2185        }
2186    }
2187    Ok(true)
2188}
2189
2190fn key_positions(
2191    key: &[ValueKey],
2192    axes: &[KeyAxis],
2193    budget: &mut Budget,
2194    location: &Location,
2195) -> Result<Vec<usize>, QueryError> {
2196    if key.len() != axes.len() {
2197        return Err(error(location, "key rank mismatch"));
2198    }
2199    let mut positions = metadata_vec(key.len(), budget, location)?;
2200    for (key, axis) in key.iter().zip(axes) {
2201        budget.charge(1, location.clone())?;
2202        if key_type(key) != axis.key_type {
2203            return Err(error(location, "incompatible keyed dimension type"));
2204        }
2205        let mut position = None;
2206        // Visit every key so contradictory copies cannot hide beyond a match.
2207        for (index, existing) in axis.keys.iter().enumerate() {
2208            if compare_keys(key, existing, budget, location)? == Ordering::Equal {
2209                position = Some(index);
2210            }
2211        }
2212        positions
2213            .push(position.ok_or_else(|| error(location, "key is absent from established axis"))?);
2214    }
2215    Ok(positions)
2216}
2217
2218fn compare_positions(
2219    left: &[usize],
2220    right: &[usize],
2221    budget: &mut Budget,
2222    location: &Location,
2223) -> Result<Ordering, QueryError> {
2224    for (a, b) in left.iter().zip(right) {
2225        budget.charge(1, location.clone())?;
2226        let order = a.cmp(b);
2227        if order != Ordering::Equal {
2228            return Ok(order);
2229        }
2230    }
2231    Ok(Ordering::Equal)
2232}
2233
2234fn equal_keys(
2235    left: &[ValueKey],
2236    right: &[ValueKey],
2237    budget: &mut Budget,
2238    location: &Location,
2239) -> Result<bool, QueryError> {
2240    budget.charge(1, location.clone())?;
2241    if left.len() != right.len() {
2242        return Ok(false);
2243    }
2244    for (a, b) in left.iter().zip(right) {
2245        if compare_keys(a, b, budget, location)? != Ordering::Equal {
2246            return Ok(false);
2247        }
2248    }
2249    Ok(true)
2250}
2251
2252fn check_keyed(
2253    axes: &[KeyAxis],
2254    entries: &[KeyedEntry],
2255    budget: &mut Budget,
2256    location: &Location,
2257) -> Result<(), QueryError> {
2258    check_axes(axes, budget, location)?;
2259    let mut previous: Option<Vec<usize>> = None;
2260    for entry in entries {
2261        budget.charge(1, location.clone())?;
2262        let positions = key_positions(&entry.key, axes, budget, location)?;
2263        if let Some(previous) = previous
2264            && compare_positions(&previous, &positions, budget, location)? != Ordering::Less
2265        {
2266            return Err(error(
2267                location,
2268                "keyed entries must be unique and in row-major order",
2269            ));
2270        }
2271        previous = Some(positions);
2272    }
2273    let members = axes
2274        .iter()
2275        .flat_map(|axis| &axis.keys)
2276        .chain(entries.iter().flat_map(|entry| &entry.key))
2277        .filter_map(|key| match key {
2278            ValueKey::Member(member) => Some(member),
2279            ValueKey::Integer(_) => None,
2280        });
2281    for (index, member) in members.clone().enumerate() {
2282        for previous in members.clone().take(index) {
2283            check_member_pair(previous, member, budget, location)?;
2284        }
2285    }
2286    Ok(())
2287}
2288
2289fn value_keys(
2290    value: ConstructedValue,
2291    budget: &mut Budget,
2292    location: &Location,
2293) -> Result<Vec<ValueKey>, QueryError> {
2294    let value = expose(value, budget, location)?;
2295    let values = match value {
2296        ConstructedValue::Tuple(values) if !values.is_empty() => values,
2297        value @ (ConstructedValue::Integer(_) | ConstructedValue::Member(_)) => {
2298            let mut values = metadata_vec(1, budget, location)?;
2299            values.push(value);
2300            values
2301        }
2302        _ => {
2303            return Err(error(
2304                location,
2305                "key needs an integer, member or nonempty flat tuple",
2306            ));
2307        }
2308    };
2309    allocation::<ValueKey>(values.len(), location)?;
2310    budget.charge(values.len(), location.clone())?;
2311    budget.charge_results(values.len(), location.clone())?;
2312    let mut keys = Vec::with_capacity(values.len());
2313    for value in values {
2314        budget.charge(1, location.clone())?;
2315        keys.push(match value.into_data() {
2316            ConstructedValue::Integer(value) => ValueKey::Integer(value),
2317            ConstructedValue::Member(member) => ValueKey::Member(member),
2318            _ => return Err(error(location, "key components need integers or members")),
2319        });
2320    }
2321    Ok(keys)
2322}
2323
2324fn infer_axes(
2325    entries: &[Located<KeyedEntry>],
2326    budget: &mut Budget,
2327    location: &Location,
2328) -> Result<Vec<KeyAxis>, QueryError> {
2329    let Some(first) = entries.first() else {
2330        return Err(error(location, "unestablished empty keyed axes"));
2331    };
2332    if first.value.key.is_empty() {
2333        return Err(error(location, "keyed arrays need nonzero rank"));
2334    }
2335    allocation::<KeyAxis>(first.value.key.len(), location)?;
2336    budget.charge(first.value.key.len(), location.clone())?;
2337    budget.charge_results(first.value.key.len(), location.clone())?;
2338    let mut axes = Vec::with_capacity(first.value.key.len());
2339    for key in &first.value.key {
2340        axes.push(KeyAxis {
2341            key_type: key_type(key),
2342            keys: Vec::new(),
2343        });
2344    }
2345    for entry in entries {
2346        let key_location = Location::Query(entry.range.clone());
2347        if entry.value.key.len() != axes.len() {
2348            return Err(error(&key_location, "key rank mismatch"));
2349        }
2350        for (key, axis) in entry.value.key.iter().zip(&mut axes) {
2351            if key_type(key) != axis.key_type {
2352                return Err(error(&key_location, "incompatible keyed dimension type"));
2353            }
2354            let mut position = axis.keys.len();
2355            let mut found = false;
2356            for (index, existing) in axis.keys.iter().enumerate() {
2357                match compare_keys(key, existing, budget, &key_location)? {
2358                    Ordering::Equal => found = true,
2359                    Ordering::Less if position == axis.keys.len() => position = index,
2360                    _ => {}
2361                }
2362            }
2363            if !found {
2364                let key = copy_key(key, budget, &key_location)?;
2365                allocation::<ValueKey>(sum(axis.keys.len(), 1, &key_location)?, &key_location)?;
2366                budget.charge(
2367                    sum(1, axis.keys.len() - position, &key_location)?,
2368                    key_location.clone(),
2369                )?;
2370                budget.charge_results(1, key_location.clone())?;
2371                axis.keys.insert(position, key);
2372            }
2373        }
2374    }
2375    Ok(axes)
2376}
2377
2378fn finish_keyed(
2379    axes: Option<Vec<KeyAxis>>,
2380    mut entries: Vec<Located<KeyedEntry>>,
2381    budget: &mut Budget,
2382    location: &Location,
2383) -> Result<ConstructedValue, QueryError> {
2384    let axes = match axes {
2385        Some(axes) => {
2386            check_axes(&axes, budget, location)?;
2387            axes
2388        }
2389        None => infer_axes(&entries, budget, location)?,
2390    };
2391    let mut positions = metadata_vec(entries.len(), budget, location)?;
2392    for entry in &entries {
2393        positions.push(key_positions(
2394            &entry.value.key,
2395            &axes,
2396            budget,
2397            &Location::Query(entry.range.clone()),
2398        )?);
2399    }
2400    // Insertion sorting admits every comparison and movement; sparse values need
2401    // no Cartesian cardinality or flattened index arithmetic.
2402    for index in 1..entries.len() {
2403        let mut cursor = index;
2404        while cursor > 0 {
2405            let key_location = Location::Query(entries[cursor].range.clone());
2406            match compare_positions(
2407                &positions[cursor - 1],
2408                &positions[cursor],
2409                budget,
2410                &key_location,
2411            )? {
2412                Ordering::Equal => return Err(error(&key_location, "duplicate full key")),
2413                Ordering::Less => break,
2414                Ordering::Greater => {
2415                    budget.charge(2, key_location)?;
2416                    entries.swap(cursor - 1, cursor);
2417                    positions.swap(cursor - 1, cursor);
2418                    cursor -= 1;
2419                }
2420            }
2421        }
2422    }
2423    let mut values = metadata_vec(entries.len(), budget, location)?;
2424    for entry in entries {
2425        values.push(entry.value);
2426    }
2427    check_keyed(&axes, &values, budget, location)?;
2428    Ok(ConstructedValue::KeyedArray {
2429        axes,
2430        entries: values,
2431    })
2432}
2433
2434fn source_axis(
2435    value: ConstructedValue,
2436    budget: &mut Budget,
2437    location: &Location,
2438) -> Result<KeyAxis, QueryError> {
2439    let empty = match value.data() {
2440        ConstructedValue::Array(values)
2441        | ConstructedValue::Set {
2442            elements: values, ..
2443        } => values.is_empty(),
2444        ConstructedValue::KeyedArray { entries, .. } => entries.is_empty(),
2445        _ => false,
2446    };
2447    let declared = if empty && let Some(declaration) = declaration(&value) {
2448        let facts = declared_facts(declaration, budget, location)?;
2449        source_key_type(std::slice::from_ref(&facts), budget, location)?
2450    } else {
2451        None
2452    };
2453    let value = expose(value, budget, location)?;
2454    let (declared, values) = match value {
2455        ConstructedValue::Array(values) => (declared, values),
2456        ConstructedValue::KeyedArray { entries, .. } => {
2457            let mut values = metadata_vec(entries.len(), budget, location)?;
2458            for entry in entries {
2459                budget.charge(1, location.clone())?;
2460                values.push(entry.value);
2461            }
2462            (declared, values)
2463        }
2464        ConstructedValue::Set {
2465            element_type,
2466            elements,
2467        } => {
2468            let ty = match element_type {
2469                Some(SetElementType::Integer) => Some(ValueKeyType::Integer),
2470                Some(SetElementType::Member(id)) => Some(ValueKeyType::Member(id)),
2471                _ => None,
2472            };
2473            (ty.or(declared), elements)
2474        }
2475        _ => {
2476            return Err(error(
2477                location,
2478                "axis source needs an integer or member array/set",
2479            ));
2480        }
2481    };
2482    // The source list admits its axis slots before allocating the axis vector.
2483    // This conversion admits only the new key slots; values move by ownership.
2484    allocation::<ValueKey>(values.len(), location)?;
2485    budget.charge(values.len(), location.clone())?;
2486    budget.charge_results(values.len(), location.clone())?;
2487    let mut keys = Vec::with_capacity(values.len());
2488    let mut ty = declared;
2489    for value in values {
2490        budget.charge(1, location.clone())?;
2491        let key = match value.into_data() {
2492            ConstructedValue::Integer(value) => ValueKey::Integer(value),
2493            ConstructedValue::Member(member) => ValueKey::Member(member),
2494            _ => {
2495                return Err(error(
2496                    location,
2497                    "axis source needs integer or member elements",
2498                ));
2499            }
2500        };
2501        if ty.is_some_and(|ty| ty != key_type(&key)) {
2502            return Err(error(location, "incompatible axis source type"));
2503        }
2504        ty = Some(key_type(&key));
2505        keys.push(key);
2506    }
2507    let axis = KeyAxis {
2508        key_type: ty.ok_or_else(|| error(location, "unestablished empty keyed axis"))?,
2509        keys,
2510    };
2511    check_axes(std::slice::from_ref(&axis), budget, location)?;
2512    Ok(axis)
2513}
2514
2515fn scalar_set_type(value: &ConstructedValue) -> Option<SetElementType> {
2516    match value.data() {
2517        ConstructedValue::Boolean(_) => Some(SetElementType::Boolean),
2518        ConstructedValue::Integer(_) => Some(SetElementType::Integer),
2519        ConstructedValue::Float(_) => Some(SetElementType::Float),
2520        ConstructedValue::Member(member) => Some(SetElementType::Member(member.type_id)),
2521        _ => None,
2522    }
2523}
2524
2525fn compare_set_values(
2526    left: &ConstructedValue,
2527    right: &ConstructedValue,
2528    budget: &mut Budget,
2529    location: &Location,
2530) -> Result<Ordering, QueryError> {
2531    budget.charge(1, location.clone())?;
2532    match (left.data(), right.data()) {
2533        (ConstructedValue::Boolean(a), ConstructedValue::Boolean(b)) => Ok(a.cmp(b)),
2534        (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => Ok(a.cmp(b)),
2535        (ConstructedValue::Float(a), ConstructedValue::Float(b)) => {
2536            finite(*a, location)?;
2537            finite(*b, location)?;
2538            Ok(a.partial_cmp(b).expect("finite floats are ordered"))
2539        }
2540        (ConstructedValue::Member(a), ConstructedValue::Member(b)) => {
2541            check_member_pair(a, b, budget, location)?;
2542            if a.type_id != b.type_id {
2543                return Err(error(location, "incompatible nominal types"));
2544            }
2545            Ok(if a.member_id == b.member_id {
2546                Ordering::Equal
2547            } else {
2548                a.declaration_order.cmp(&b.declaration_order)
2549            })
2550        }
2551        _ => Err(error(
2552            location,
2553            "set elements must have one supported scalar type",
2554        )),
2555    }
2556}
2557
2558fn check_set(
2559    element_type: Option<SetElementType>,
2560    elements: &[ConstructedValue],
2561    budget: &mut Budget,
2562    location: &Location,
2563) -> Result<(), QueryError> {
2564    if element_type.is_none() && !elements.is_empty() {
2565        return Err(error(location, "a nonempty set needs its element type"));
2566    }
2567    for (index, value) in elements.iter().enumerate() {
2568        budget.charge(1, location.clone())?;
2569        if scalar_set_type(value) != element_type {
2570            return Err(error(location, "set element type mismatch"));
2571        }
2572        match value.data() {
2573            ConstructedValue::Float(value) => finite(*value, location)?,
2574            ConstructedValue::Member(member) => {
2575                member_bytes(member, budget, location)?;
2576            }
2577            _ => {}
2578        }
2579        for previous in &elements[..index] {
2580            if compare_set_values(previous, value, budget, location)? == Ordering::Equal {
2581                return Err(error(location, "set elements must be unique"));
2582            }
2583        }
2584    }
2585    Ok(())
2586}
2587
2588fn check_value_labels<T>(
2589    fields: &[(String, T)],
2590    budget: &mut Budget,
2591    location: &Location,
2592) -> Result<(), QueryError> {
2593    for (index, (label, _)) in fields.iter().enumerate() {
2594        for (previous, _) in &fields[..index] {
2595            budget.charge(
2596                sum(1, label.len().min(previous.len()), location)?,
2597                location.clone(),
2598            )?;
2599            if label == previous {
2600                return Err(error(location, "duplicate record label"));
2601            }
2602        }
2603    }
2604    Ok(())
2605}
2606
2607fn copy_size(
2608    value: &ConstructedValue,
2609    budget: &mut Budget,
2610    location: &Location,
2611) -> Result<(usize, usize), QueryError> {
2612    budget.within_evaluation(1, location.clone(), |budget| {
2613        budget.charge(1, location.clone())?;
2614        let mut units = 1;
2615        let mut bytes = 0;
2616        match value {
2617            ConstructedValue::Absent
2618            | ConstructedValue::Boolean(_)
2619            | ConstructedValue::Integer(_) => {}
2620            ConstructedValue::Declared { value, declaration } => {
2621                crate::admit_constructed(value, declaration, budget, location.clone())?;
2622                let (child_units, child_bytes) = copy_size(value, budget, location)?;
2623                units = sum(units, child_units, location)?;
2624                bytes = child_bytes;
2625                // Declarations are copied with their own metadata charges below.
2626            }
2627            ConstructedValue::Float(value) => finite(*value, location)?,
2628            ConstructedValue::Member(member) => {
2629                bytes = member_bytes(member, budget, location)?;
2630            }
2631            ConstructedValue::KeyedArray { axes, entries } => {
2632                check_keyed(axes, entries, budget, location)?;
2633                allocation::<KeyAxis>(axes.len(), location)?;
2634                allocation::<KeyedEntry>(entries.len(), location)?;
2635                units = sum(units, sum(axes.len(), entries.len(), location)?, location)?;
2636                for axis in axes {
2637                    let (key_units, key_bytes) = keys_size(&axis.keys, budget, location)?;
2638                    units = sum(units, key_units, location)?;
2639                    bytes = sum(bytes, key_bytes, location)?;
2640                }
2641                for entry in entries {
2642                    let (key_units, key_bytes) = keys_size(&entry.key, budget, location)?;
2643                    let (child_units, child_bytes) = copy_size(&entry.value, budget, location)?;
2644                    units = sum(units, sum(key_units, child_units, location)?, location)?;
2645                    bytes = sum(bytes, sum(key_bytes, child_bytes, location)?, location)?;
2646                }
2647            }
2648            ConstructedValue::String(value) => {
2649                allocation::<u8>(value.len(), location)?;
2650                bytes = value.len();
2651            }
2652            ConstructedValue::Array(values)
2653            | ConstructedValue::Tuple(values)
2654            | ConstructedValue::Set {
2655                elements: values, ..
2656            } => {
2657                allocation::<ConstructedValue>(values.len(), location)?;
2658                if let ConstructedValue::Set { element_type, .. } = value {
2659                    check_set(*element_type, values, budget, location)?;
2660                }
2661                units = sum(units, values.len(), location)?;
2662                for value in values {
2663                    let (child_units, child_bytes) = copy_size(value, budget, location)?;
2664                    units = sum(units, child_units, location)?;
2665                    bytes = sum(bytes, child_bytes, location)?;
2666                }
2667            }
2668            ConstructedValue::Record(fields) => {
2669                allocation::<(String, ConstructedValue)>(fields.len(), location)?;
2670                check_value_labels(fields, budget, location)?;
2671                units = sum(units, fields.len(), location)?;
2672                for (label, value) in fields {
2673                    allocation::<u8>(label.len(), location)?;
2674                    let (child_units, child_bytes) = copy_size(value, budget, location)?;
2675                    units = sum(units, child_units, location)?;
2676                    bytes = sum(bytes, sum(label.len(), child_bytes, location)?, location)?;
2677                }
2678            }
2679        }
2680        Ok((units, bytes))
2681    })
2682}
2683
2684fn copy_admitted(
2685    value: &ConstructedValue,
2686    budget: &mut Budget,
2687    location: &Location,
2688) -> Result<ConstructedValue, QueryError> {
2689    budget.within_evaluation(1, location.clone(), |budget| {
2690        budget.charge(1, location.clone())?;
2691        Ok(match value {
2692            ConstructedValue::Absent => ConstructedValue::Absent,
2693            ConstructedValue::Declared { value, declaration } => ConstructedValue::Declared {
2694                value: Box::new(copy_admitted(value, budget, location)?),
2695                declaration: copy_declaration(declaration, budget, location)?,
2696            },
2697            ConstructedValue::Boolean(value) => ConstructedValue::Boolean(*value),
2698            ConstructedValue::Integer(value) => ConstructedValue::Integer(*value),
2699            ConstructedValue::Float(value) => ConstructedValue::Float(*value),
2700            ConstructedValue::String(value) => ConstructedValue::String(value.to_owned()),
2701            ConstructedValue::Member(member) => {
2702                ConstructedValue::Member(copy_member_admitted(member))
2703            }
2704            ConstructedValue::KeyedArray { axes, entries } => {
2705                let mut copied_axes = Vec::with_capacity(axes.len());
2706                for axis in axes {
2707                    budget.charge(1, location.clone())?;
2708                    copied_axes.push(KeyAxis {
2709                        key_type: axis.key_type,
2710                        keys: copy_keys_admitted(&axis.keys, budget, location)?,
2711                    });
2712                }
2713                let mut copied_entries = Vec::with_capacity(entries.len());
2714                for entry in entries {
2715                    budget.charge(1, location.clone())?;
2716                    copied_entries.push(KeyedEntry {
2717                        key: copy_keys_admitted(&entry.key, budget, location)?,
2718                        value: copy_admitted(&entry.value, budget, location)?,
2719                    });
2720                }
2721                ConstructedValue::KeyedArray {
2722                    axes: copied_axes,
2723                    entries: copied_entries,
2724                }
2725            }
2726            ConstructedValue::Array(values)
2727            | ConstructedValue::Tuple(values)
2728            | ConstructedValue::Set {
2729                elements: values, ..
2730            } => {
2731                let mut copied = Vec::with_capacity(values.len());
2732                for value in values {
2733                    copied.push(copy_admitted(value, budget, location)?);
2734                }
2735                match value {
2736                    ConstructedValue::Array(_) => ConstructedValue::Array(copied),
2737                    ConstructedValue::Tuple(_) => ConstructedValue::Tuple(copied),
2738                    ConstructedValue::Set { element_type, .. } => ConstructedValue::Set {
2739                        element_type: *element_type,
2740                        elements: copied,
2741                    },
2742                    _ => unreachable!("matched collection"),
2743                }
2744            }
2745            ConstructedValue::Record(fields) => {
2746                let mut copied = Vec::with_capacity(fields.len());
2747                for (label, value) in fields {
2748                    copied.push((label.to_owned(), copy_admitted(value, budget, location)?));
2749                }
2750                ConstructedValue::Record(copied)
2751            }
2752        })
2753    })
2754}
2755
2756fn copy_type(
2757    ty: &ValueType,
2758    budget: &mut Budget,
2759    location: &Location,
2760) -> Result<ValueType, QueryError> {
2761    budget.within_evaluation(1, location.clone(), |budget| {
2762        budget.charge(1, location.clone())?;
2763        Ok(match ty {
2764            ValueType::Absent => ValueType::Absent,
2765            ValueType::Declared {
2766                actual,
2767                declaration,
2768            } => ValueType::Declared {
2769                actual: Box::new(copy_type(actual, budget, location)?),
2770                declaration: copy_declaration(declaration, budget, location)?,
2771            },
2772            ValueType::ScalarKind => ValueType::ScalarKind,
2773            ValueType::Boolean => ValueType::Boolean,
2774            ValueType::Integer => ValueType::Integer,
2775            ValueType::Float => ValueType::Float,
2776            ValueType::String => ValueType::String,
2777            ValueType::Member(id) => ValueType::Member(*id),
2778            ValueType::KeyedArray(keyed) => {
2779                let axes = match &keyed.axes {
2780                    Some(axes) => Some(copy_axis_types(axes, budget, location)?),
2781                    None => None,
2782                };
2783                ValueType::KeyedArray(KeyedArrayType {
2784                    axes,
2785                    elements: copy_types(&keyed.elements, budget, location)?,
2786                })
2787            }
2788            ValueType::Unestablished => ValueType::Unestablished,
2789            ValueType::TupleKind => ValueType::TupleKind,
2790            ValueType::RecordKind => ValueType::RecordKind,
2791            ValueType::Set(element_type) => ValueType::Set(*element_type),
2792            ValueType::Array(array) => {
2793                let (ArrayType::Positions(entries) | ArrayType::Elements(entries)) = array;
2794                let entries = copy_types(entries, budget, location)?;
2795                ValueType::Array(match array {
2796                    ArrayType::Positions(_) => ArrayType::Positions(entries),
2797                    ArrayType::Elements(_) => ArrayType::Elements(entries),
2798                })
2799            }
2800            ValueType::ArrayKind(elements) => {
2801                ValueType::ArrayKind(copy_types(elements, budget, location)?)
2802            }
2803            ValueType::Tuple(entries) => ValueType::Tuple(copy_types(entries, budget, location)?),
2804            ValueType::Record(fields) => {
2805                check_value_labels(fields, budget, location)?;
2806                let mut copied = metadata_vec(fields.len(), budget, location)?;
2807                for (label, ty) in fields {
2808                    budget.charge(label.len(), location.clone())?;
2809                    copied.push((label.to_owned(), copy_type(ty, budget, location)?));
2810                }
2811                ValueType::Record(copied)
2812            }
2813        })
2814    })
2815}
2816
2817fn copy_types(
2818    types: &[ValueType],
2819    budget: &mut Budget,
2820    location: &Location,
2821) -> Result<Vec<ValueType>, QueryError> {
2822    let mut copied = metadata_vec(types.len(), budget, location)?;
2823    for ty in types {
2824        copied.push(copy_type(ty, budget, location)?);
2825    }
2826    Ok(copied)
2827}
2828
2829fn one_type(
2830    ty: ValueType,
2831    budget: &mut Budget,
2832    location: &Location,
2833) -> Result<Vec<ValueType>, QueryError> {
2834    let mut types = metadata_vec(1, budget, location)?;
2835    types.push(ty);
2836    Ok(types)
2837}
2838
2839fn append_types(
2840    target: &mut Vec<ValueType>,
2841    types: Vec<ValueType>,
2842    budget: &mut Budget,
2843    location: &Location,
2844) -> Result<(), QueryError> {
2845    allocation::<ValueType>(sum(target.len(), types.len(), location)?, location)?;
2846    budget.charge(types.len(), location.clone())?;
2847    target.extend(types);
2848    Ok(())
2849}
2850
2851enum Globals<'a> {
2852    Types(&'a BTreeMap<String, ValueType>),
2853    Facts(&'a BTreeMap<String, Vec<ValueType>>),
2854    Available(&'a Bindings<ConstructedValue>),
2855}
2856
2857/// Only selected terminal control positions may transfer to their loop.
2858fn check_loop_terminal<Q>(
2859    kind: &ValueExpressionKind<Q>,
2860    transfer: Option<usize>,
2861    location: &Location,
2862) -> Result<(), QueryError> {
2863    match (kind, transfer) {
2864        (ValueExpressionKind::Break(_), Some(_)) => Ok(()),
2865        (ValueExpressionKind::Continue(values), Some(arity)) if values.len() == arity => Ok(()),
2866        (ValueExpressionKind::Continue(_), Some(_)) => {
2867            Err(error(location, "loop state argument count mismatch"))
2868        }
2869        (ValueExpressionKind::Break(_) | ValueExpressionKind::Continue(_), None) => Err(error(
2870            location,
2871            "loop transfer requires a terminal loop body position",
2872        )),
2873        (
2874            ValueExpressionKind::Block { .. }
2875            | ValueExpressionKind::Conditional { .. }
2876            | ValueExpressionKind::ValueMatch { .. },
2877            Some(_),
2878        )
2879        | (_, None) => Ok(()),
2880        (_, Some(_)) => Err(error(
2881            location,
2882            "loop body path must end in break or continue",
2883        )),
2884    }
2885}
2886
2887struct Analysis<'a, 'h, Q, H> {
2888    queries: &'h mut H,
2889    query: std::marker::PhantomData<Q>,
2890    globals: Globals<'a>,
2891    locals: Vec<(&'a str, Vec<ValueType>)>,
2892    free: Vec<Located<String>>,
2893    fallback_names: Vec<Located<String>>,
2894    fallback: bool,
2895}
2896
2897impl<'a, 'h, Q: 'a, H: ValueQueryHost<Q>> Analysis<'a, 'h, Q, H> {
2898    fn new(globals: Globals<'a>, queries: &'h mut H) -> Self {
2899        Self {
2900            globals,
2901            queries,
2902            query: std::marker::PhantomData,
2903            locals: Vec::new(),
2904            free: Vec::new(),
2905            fallback_names: Vec::new(),
2906            fallback: false,
2907        }
2908    }
2909
2910    fn global_exists(
2911        &self,
2912        name: &str,
2913        range: std::ops::Range<usize>,
2914        budget: &mut Budget,
2915    ) -> Result<bool, QueryError> {
2916        admit_diagnostic(
2917            "undefined binding: ",
2918            name,
2919            budget,
2920            &Location::Query(range.clone()),
2921        )?;
2922        Ok(match self.globals {
2923            Globals::Types(types) => types.contains_key(name),
2924            Globals::Facts(types) => types.contains_key(name),
2925            Globals::Available(bindings) => bindings.get(name, range).is_ok(),
2926        })
2927    }
2928
2929    fn name(
2930        &mut self,
2931        name: &str,
2932        expression: &ValueExpression<Q>,
2933        budget: &mut Budget,
2934    ) -> Result<Vec<ValueType>, QueryError> {
2935        let location = Location::Query(expression.range.clone());
2936        if name == "_" {
2937            return Err(error(&location, "discard binder cannot be read"));
2938        }
2939        for (local, types) in self.locals.iter().rev() {
2940            budget.charge(
2941                sum(1, name.len().min(local.len()), &location)?,
2942                location.clone(),
2943            )?;
2944            if name == *local {
2945                return copy_types(types, budget, &location);
2946            }
2947        }
2948        self.queries
2949            .validate_name(name, &expression.range, budget)?;
2950        admit_diagnostic("undefined binding: ", name, budget, &location)?;
2951        let types = match self.globals {
2952            Globals::Types(types) => one_type(
2953                copy_type(
2954                    types
2955                        .get(name)
2956                        .ok_or_else(|| error(&location, format!("undefined binding: {name}")))?,
2957                    budget,
2958                    &location,
2959                )?,
2960                budget,
2961                &location,
2962            )?,
2963            Globals::Facts(types) => copy_types(
2964                types
2965                    .get(name)
2966                    .ok_or_else(|| error(&location, format!("undefined binding: {name}")))?,
2967                budget,
2968                &location,
2969            )?,
2970            Globals::Available(bindings) => {
2971                let value = bindings.get(name, expression.range.clone())?;
2972                let ty = if !self.fallback
2973                    && matches!(
2974                        value,
2975                        ConstructedValue::Array(_)
2976                            | ConstructedValue::Tuple(_)
2977                            | ConstructedValue::Record(_)
2978                            | ConstructedValue::Set { .. }
2979                            | ConstructedValue::KeyedArray { .. }
2980                    ) {
2981                    ValueType::Unestablished
2982                } else {
2983                    shallow_type(value, budget, &location)?
2984                };
2985                one_type(ty, budget, &location)?
2986            }
2987        };
2988        let mut found = false;
2989        for previous in &self.free {
2990            budget.charge(
2991                sum(1, name.len().min(previous.value.len()), &location)?,
2992                location.clone(),
2993            )?;
2994            if previous.value == name {
2995                found = true;
2996                break;
2997            }
2998        }
2999        if self.fallback && !found {
3000            budget.charge(name.len(), location.clone())?;
3001            push_metadata(
3002                &mut self.fallback_names,
3003                Located::new(name.to_owned(), expression.range.clone()),
3004                budget,
3005                &location,
3006            )?;
3007        } else if !self.fallback {
3008            let mut selected = None;
3009            for (index, previous) in self.fallback_names.iter().enumerate() {
3010                budget.charge(1 + name.len().min(previous.value.len()), location.clone())?;
3011                if previous.value == name {
3012                    selected = Some(index);
3013                    break;
3014                }
3015            }
3016            if let Some(index) = selected {
3017                self.fallback_names.remove(index);
3018            }
3019        }
3020        if !found {
3021            budget.charge(name.len(), location.clone())?;
3022            push_metadata(
3023                &mut self.free,
3024                Located::new(name.to_owned(), expression.range.clone()),
3025                budget,
3026                &location,
3027            )?;
3028        }
3029        Ok(types)
3030    }
3031
3032    fn walk(
3033        &mut self,
3034        expression: &'a ValueExpression<Q>,
3035        budget: &mut Budget,
3036    ) -> Result<Vec<ValueType>, QueryError> {
3037        self.walk_control(expression, budget, None)
3038    }
3039
3040    fn walk_control(
3041        &mut self,
3042        expression: &'a ValueExpression<Q>,
3043        budget: &mut Budget,
3044        transfer: Option<usize>,
3045    ) -> Result<Vec<ValueType>, QueryError> {
3046        let location = Location::Query(expression.range.clone());
3047        budget.within_evaluation(1, location.clone(), |budget| {
3048            budget.charge(1, location.clone())?;
3049            check_loop_terminal(&expression.value, transfer, &location)?;
3050            self.node(expression, budget, &location, transfer)
3051        })
3052    }
3053
3054    fn node(
3055        &mut self,
3056        expression: &'a ValueExpression<Q>,
3057        budget: &mut Budget,
3058        location: &Location,
3059        transfer: Option<usize>,
3060    ) -> Result<Vec<ValueType>, QueryError> {
3061        self.queries.prepare_computation(expression, budget)?;
3062        match &expression.value {
3063            ValueExpressionKind::Queue { items, .. } => {
3064                self.walk(items, budget)?;
3065                self.queries.worklist_types(expression, budget)
3066            }
3067            ValueExpressionKind::IdentitySet { .. } => {
3068                self.queries.worklist_types(expression, budget)
3069            }
3070            ValueExpressionKind::PopFront(queue) => {
3071                self.walk(queue, budget)?;
3072                self.queries.worklist_types(expression, budget)
3073            }
3074            ValueExpressionKind::PushBackAll { queue, items } => {
3075                self.walk(queue, budget)?;
3076                self.walk(items, budget)?;
3077                self.queries.worklist_types(expression, budget)
3078            }
3079            ValueExpressionKind::Insert { set, item } => {
3080                self.walk(set, budget)?;
3081                self.walk(item, budget)?;
3082                self.queries.worklist_types(expression, budget)
3083            }
3084            ValueExpressionKind::Call {
3085                function,
3086                arguments,
3087            } => {
3088                let (_, arity) = self.queries.function(function, budget)?;
3089                if arguments.len() != arity {
3090                    return Err(error(location, "function argument count mismatch"));
3091                }
3092                for argument in arguments {
3093                    self.walk(argument, budget)?;
3094                }
3095                self.queries.function_types(function, budget)
3096            }
3097            ValueExpressionKind::Fold {
3098                items,
3099                initial,
3100                step,
3101            }
3102            | ValueExpressionKind::Scan {
3103                items,
3104                initial,
3105                step,
3106            } => {
3107                let (_, state) = self.queries.accumulator_function(step, budget)?;
3108                let types = self.walk(items, budget)?;
3109                for ty in types {
3110                    if !matches!(
3111                        shape_type(ty, budget, location)?,
3112                        ValueType::Array(_) | ValueType::ArrayKind(_) | ValueType::Unestablished
3113                    ) {
3114                        return Err(error(
3115                            &Location::Query(items.range.clone()),
3116                            "named accumulator needs a positional sequence",
3117                        ));
3118                    }
3119                }
3120                self.walk(initial, budget)?;
3121                if matches!(expression.value, ValueExpressionKind::Scan { .. }) {
3122                    one_type(
3123                        ValueType::Array(ArrayType::Elements(state)),
3124                        budget,
3125                        location,
3126                    )
3127                } else {
3128                    Ok(state)
3129                }
3130            }
3131            ValueExpressionKind::NativeValue(query) => {
3132                self.queries
3133                    .native_value_types(query, &self.locals, budget, &expression.range)
3134            }
3135            ValueExpressionKind::Literal(value) => {
3136                let ty = if self.fallback {
3137                    shallow_type(value, budget, location)?
3138                } else {
3139                    constructed_value_type(value, budget, location.clone())?
3140                };
3141                one_type(ty, budget, location)
3142            }
3143            ValueExpressionKind::Occurs(value) | ValueExpressionKind::Absent(value) => {
3144                self.walk(value, budget)?;
3145                one_type(ValueType::Boolean, budget, location)
3146            }
3147            ValueExpressionKind::Lookup { value, key }
3148            | ValueExpressionKind::HasKey { value, key } => {
3149                let values = self.walk(value, budget)?;
3150                let keys = self.walk(key, budget)?;
3151                let label = match &key.value {
3152                    ValueExpressionKind::Literal(ConstructedValue::String(label)) => {
3153                        Some(label.as_str())
3154                    }
3155                    _ => None,
3156                };
3157                let mut result = Vec::new();
3158                for value in values {
3159                    append_types(
3160                        &mut result,
3161                        access_type(value, &keys, label, true, budget, location)?,
3162                        budget,
3163                        location,
3164                    )?;
3165                }
3166                if matches!(expression.value, ValueExpressionKind::HasKey { .. }) {
3167                    one_type(ValueType::Boolean, budget, location)
3168                } else {
3169                    Ok(result)
3170                }
3171            }
3172            ValueExpressionKind::Default { value, fallback } => {
3173                let left = self.walk(value, budget)?;
3174                let saved = self.fallback;
3175                self.fallback = true;
3176                let right = self.walk(fallback, budget);
3177                self.fallback = saved;
3178                let right = right?;
3179                for a in &left {
3180                    for b in &right {
3181                        if !compatible_types(a, b, budget, location)? {
3182                            return Err(error(
3183                                location,
3184                                "default needs compatible underlying types",
3185                            ));
3186                        }
3187                    }
3188                }
3189                let mut result = Vec::new();
3190                for ty in left.into_iter().chain(right) {
3191                    let ty = match ty {
3192                        ValueType::Absent => continue,
3193                        ValueType::Declared {
3194                            actual,
3195                            declaration,
3196                        } => {
3197                            let declaration =
3198                                copy_declaration(underlying(&declaration), budget, location)?;
3199                            ValueType::Declared {
3200                                actual: Box::new(if matches!(*actual, ValueType::Absent) {
3201                                    ValueType::Unestablished
3202                                } else {
3203                                    *actual
3204                                }),
3205                                declaration,
3206                            }
3207                        }
3208                        ty => ty,
3209                    };
3210                    push_metadata(&mut result, ty, budget, location)?;
3211                }
3212                if result.is_empty() {
3213                    one_type(ValueType::Absent, budget, location)
3214                } else {
3215                    Ok(result)
3216                }
3217            }
3218            ValueExpressionKind::CurrentInput => {
3219                self.queries.current_input_types(&expression.range, budget)
3220            }
3221            ValueExpressionKind::Name(name) => self.name(name, expression, budget),
3222            ValueExpressionKind::PredicateSubject => {
3223                if !self
3224                    .locals
3225                    .iter()
3226                    .any(|(name, _)| *name == PREDICATE_SUBJECT)
3227                {
3228                    return self
3229                        .queries
3230                        .predicate_subject_types(&expression.range, budget);
3231                }
3232                self.name(PREDICATE_SUBJECT, expression, budget)
3233            }
3234            ValueExpressionKind::SubjectScope { subject, argument } => {
3235                let types = self.walk(subject, budget)?;
3236                self.scoped_types(argument, subject, &types, false, budget)
3237            }
3238            ValueExpressionKind::PredicateScope { subject, predicate } => {
3239                let types = self.walk(subject, budget)?;
3240                let result = self.scoped_types(predicate, subject, &types, false, budget)?;
3241                predicate_types(&result, budget, &predicate.body.range)?;
3242                one_type(ValueType::Boolean, budget, location)
3243            }
3244            ValueExpressionKind::NativeQuery(query) => {
3245                self.queries
3246                    .query_types(query, &self.locals, budget, &expression.range)
3247            }
3248            ValueExpressionKind::One(array) => {
3249                let mut result = Vec::new();
3250                for ty in self.walk(array, budget)? {
3251                    match shape_type(ty, budget, location)? {
3252                        ValueType::Array(
3253                            ArrayType::Positions(types) | ArrayType::Elements(types),
3254                        )
3255                        | ValueType::ArrayKind(types) => {
3256                            append_types(&mut result, types, budget, location)?
3257                        }
3258                        ValueType::Unestablished => {
3259                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?
3260                        }
3261                        _ => return Err(error(location, "one needs a positional array")),
3262                    }
3263                }
3264                if result.is_empty() {
3265                    push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3266                }
3267                Ok(result)
3268            }
3269            ValueExpressionKind::Array(children) => {
3270                let mut entries = metadata_vec(children.len(), budget, location)?;
3271                let mut exact = true;
3272                for child in children {
3273                    let types = self.walk(child, budget)?;
3274                    exact &= types.len() == 1;
3275                    entries.push(types);
3276                }
3277                let mut types = Vec::new();
3278                for entries in entries {
3279                    append_types(&mut types, entries, budget, location)?;
3280                }
3281                one_type(
3282                    ValueType::Array(if exact {
3283                        ArrayType::Positions(types)
3284                    } else {
3285                        ArrayType::Elements(types)
3286                    }),
3287                    budget,
3288                    location,
3289                )
3290            }
3291            ValueExpressionKind::Tuple(children) => {
3292                let positions = self.positions(children.iter(), budget, location)?;
3293                let mut types = metadata_vec(positions.len(), budget, location)?;
3294                for position in positions {
3295                    types.push(ValueType::Tuple(position));
3296                }
3297                Ok(types)
3298            }
3299            ValueExpressionKind::Record(fields) => {
3300                for (index, (label, _)) in fields.iter().enumerate() {
3301                    for (previous, _) in &fields[..index] {
3302                        budget.charge(
3303                            sum(1, label.value.len().min(previous.value.len()), location)?,
3304                            location.clone(),
3305                        )?;
3306                        if label.value == previous.value {
3307                            return Err(QueryError::query(
3308                                label.range.clone(),
3309                                "duplicate record label",
3310                            ));
3311                        }
3312                    }
3313                }
3314                let positions =
3315                    self.positions(fields.iter().map(|(_, value)| value), budget, location)?;
3316                let mut types = metadata_vec(positions.len(), budget, location)?;
3317                for position in positions {
3318                    let mut result = metadata_vec(fields.len(), budget, location)?;
3319                    for ((label, _), ty) in fields.iter().zip(position) {
3320                        budget.charge(label.value.len(), Location::Query(label.range.clone()))?;
3321                        result.push((label.value.to_owned(), ty));
3322                    }
3323                    types.push(ValueType::Record(result));
3324                }
3325                Ok(types)
3326            }
3327            ValueExpressionKind::Dictionary(entries) => {
3328                for (key, value) in entries {
3329                    for ty in self.walk(key, budget)? {
3330                        require_type(
3331                            &ty,
3332                            ScalarRequirement::String,
3333                            &Location::Query(key.range.clone()),
3334                        )?;
3335                    }
3336                    self.walk(value, budget)?;
3337                }
3338                one_type(ValueType::RecordKind, budget, location)
3339            }
3340            ValueExpressionKind::DictionaryComprehension { clauses, key, head } => {
3341                let saved = self.locals.len();
3342                let result = (|| {
3343                    self.clauses(clauses, budget)?;
3344                    for ty in self.walk(key, budget)? {
3345                        require_type(
3346                            &ty,
3347                            ScalarRequirement::String,
3348                            &Location::Query(key.range.clone()),
3349                        )?;
3350                    }
3351                    self.walk(head, budget)?;
3352                    one_type(ValueType::RecordKind, budget, location)
3353                })();
3354                self.locals.truncate(saved);
3355                result
3356            }
3357            ValueExpressionKind::Set(children) => {
3358                let mut element_type = None;
3359                for child in children {
3360                    merge_set_types(
3361                        &mut element_type,
3362                        &self.walk(child, budget)?,
3363                        budget,
3364                        location,
3365                    )?;
3366                }
3367                one_type(ValueType::Set(element_type), budget, location)
3368            }
3369            ValueExpressionKind::KeyedArray {
3370                axes,
3371                declared_axis_sources,
3372                entries,
3373            } => {
3374                if axes.is_some() && declared_axis_sources.is_some() {
3375                    return Err(error(
3376                        location,
3377                        "conflicting concrete axes and declared axis sources",
3378                    ));
3379                }
3380                let source_facts = declared_axis_sources
3381                    .as_ref()
3382                    .map(|sources| self.axis_sources(sources, budget, location))
3383                    .transpose()?;
3384                let mut facts = None;
3385                let established = match axes {
3386                    Some(axes) => {
3387                        check_axes(axes, budget, location)?;
3388                        Some(axis_types(axes, budget, location)?)
3389                    }
3390                    None => None,
3391                };
3392                let mut elements = Vec::new();
3393                for (key, value) in entries {
3394                    let key_location = Location::Query(key.range.clone());
3395                    merge_key_facts(&mut facts, &self.walk(key, budget)?, budget, &key_location)?;
3396                    if let Some(axes) = &established {
3397                        check_key_facts(&facts, axes, budget, &key_location)?;
3398                    }
3399                    if let Some(sources) = &source_facts {
3400                        check_source_facts(&facts, sources, budget, &key_location)?;
3401                    }
3402                    append_types(&mut elements, self.walk(value, budget)?, budget, location)?;
3403                }
3404                let axes = match established {
3405                    Some(axes) => Some(axes),
3406                    None => established_facts(source_facts.or(facts), budget, location)?,
3407                };
3408                if entries.is_empty() && axes.is_none() && declared_axis_sources.is_none() {
3409                    return Err(error(location, "unestablished empty keyed axes"));
3410                }
3411                one_type(
3412                    ValueType::KeyedArray(KeyedArrayType { axes, elements }),
3413                    budget,
3414                    location,
3415                )
3416            }
3417            ValueExpressionKind::KeyedComprehension {
3418                clauses,
3419                key,
3420                head,
3421                declared_axis_sources,
3422                empty_axis_sources,
3423            } => {
3424                if declared_axis_sources.is_some() && empty_axis_sources.is_some() {
3425                    return Err(error(
3426                        location,
3427                        "conflicting declared and fallback axis sources",
3428                    ));
3429                }
3430                // Both source lists belong to the scope before our binders.
3431                let sources = declared_axis_sources
3432                    .as_ref()
3433                    .or(empty_axis_sources.as_ref());
3434                let source_facts = sources
3435                    .map(|sources| self.axis_sources(sources, budget, location))
3436                    .transpose()?;
3437                let saved = self.locals.len();
3438                let result = (|| {
3439                    self.clauses(clauses, budget)?;
3440                    let mut facts = None;
3441                    merge_key_facts(
3442                        &mut facts,
3443                        &self.walk(key, budget)?,
3444                        budget,
3445                        &Location::Query(key.range.clone()),
3446                    )?;
3447                    let elements = self.walk(head, budget)?;
3448                    if let Some(source_facts) = &source_facts {
3449                        check_source_facts(
3450                            &facts,
3451                            source_facts,
3452                            budget,
3453                            &Location::Query(key.range.clone()),
3454                        )?;
3455                    }
3456                    let axes = if declared_axis_sources.is_some() {
3457                        established_facts(source_facts, budget, location)?
3458                    } else {
3459                        established_facts(facts, budget, location)?
3460                    };
3461                    one_type(
3462                        ValueType::KeyedArray(KeyedArrayType { axes, elements }),
3463                        budget,
3464                        location,
3465                    )
3466                })();
3467                self.locals.truncate(saved);
3468                result
3469            }
3470            ValueExpressionKind::Unary { operator, operand } => {
3471                let types = self.walk(operand, budget)?;
3472                let mut result = metadata_vec(types.len(), budget, location)?;
3473                for ty in types {
3474                    budget.charge(1, location.clone())?;
3475                    match operator {
3476                        ValueUnaryOperator::Not => {
3477                            require_type(&ty, ScalarRequirement::Boolean, location)?;
3478                            result.push(ValueType::Boolean);
3479                        }
3480                        ValueUnaryOperator::Positive | ValueUnaryOperator::Negative => {
3481                            require_type(&ty, ScalarRequirement::Numeric, location)?;
3482                            result.push(ty);
3483                        }
3484                    }
3485                }
3486                Ok(result)
3487            }
3488            ValueExpressionKind::Binary {
3489                operator,
3490                left,
3491                right,
3492            } => {
3493                let left = self.walk(left, budget)?;
3494                let right = self.walk(right, budget)?;
3495                let mut result = Vec::new();
3496                for a in &left {
3497                    for b in &right {
3498                        budget.charge(1, location.clone())?;
3499                        let ty = binary_type(*operator, a, b, budget, location)?;
3500                        push_metadata(&mut result, ty, budget, location)?;
3501                    }
3502                }
3503                Ok(result)
3504            }
3505            ValueExpressionKind::Field { value, field } => {
3506                let types = self.walk(value, budget)?;
3507                let mut result = Vec::new();
3508                for ty in types {
3509                    if let ValueType::Declared { declaration, .. } = &ty
3510                        && let DeclaredType::Record(fields) = underlying(declaration)
3511                    {
3512                        let selected = record_declaration(fields, &field.value, budget, location)?;
3513                        push_metadata(
3514                            &mut result,
3515                            declared_fact(selected, ValueType::Unestablished, budget, location)?,
3516                            budget,
3517                            location,
3518                        )?;
3519                        continue;
3520                    }
3521                    let selected = match shape_type(ty, budget, location)? {
3522                        ValueType::Record(fields) => {
3523                            let mut selected = None;
3524                            for (label, ty) in fields {
3525                                budget.charge(
3526                                    sum(1, label.len().min(field.value.len()), location)?,
3527                                    location.clone(),
3528                                )?;
3529                                if label == field.value {
3530                                    selected = Some(ty);
3531                                }
3532                            }
3533                            selected.unwrap_or(ValueType::Unestablished)
3534                        }
3535                        ValueType::Unestablished | ValueType::RecordKind => {
3536                            ValueType::Unestablished
3537                        }
3538                        _ => return Err(error(location, "field access needs a record")),
3539                    };
3540                    push_metadata(&mut result, selected, budget, location)?;
3541                }
3542                Ok(result)
3543            }
3544            ValueExpressionKind::TupleAccess { value, position } => {
3545                let types = self.walk(value, budget)?;
3546                let mut result = Vec::new();
3547                for ty in types {
3548                    budget.charge(1, location.clone())?;
3549                    let selected = match shape_type(ty, budget, location)? {
3550                        ValueType::Tuple(entries) => {
3551                            budget.charge(entries.len().min(position.value), location.clone())?;
3552                            position
3553                                .value
3554                                .checked_sub(1)
3555                                .and_then(|index| entries.into_iter().nth(index))
3556                                .unwrap_or(ValueType::Unestablished)
3557                        }
3558                        ValueType::Unestablished | ValueType::TupleKind => ValueType::Unestablished,
3559                        _ => return Err(error(location, "tuple access needs a tuple")),
3560                    };
3561                    push_metadata(&mut result, selected, budget, location)?;
3562                }
3563                Ok(result)
3564            }
3565            ValueExpressionKind::ArrayAccess { array, index } => {
3566                let arrays = self.walk(array, budget)?;
3567                let indices = self.walk(index, budget)?;
3568                let constant = match index.value {
3569                    ValueExpressionKind::Literal(ConstructedValue::Integer(value)) => Some(value),
3570                    _ => None,
3571                };
3572                let mut result = Vec::new();
3573                for array in arrays {
3574                    if matches!(&array, ValueType::Record(_) | ValueType::RecordKind)
3575                        || matches!(&array, ValueType::Declared { declaration, .. }
3576                            if matches!(underlying(declaration), DeclaredType::Record(_)))
3577                    {
3578                        let label = match &index.value {
3579                            ValueExpressionKind::Literal(ConstructedValue::String(label)) => {
3580                                Some(label.as_str())
3581                            }
3582                            _ => None,
3583                        };
3584                        append_types(
3585                            &mut result,
3586                            access_type(array, &indices, label, false, budget, location)?,
3587                            budget,
3588                            location,
3589                        )?;
3590                        continue;
3591                    }
3592                    match shape_type(array, budget, location)? {
3593                        ValueType::ArrayKind(elements) => {
3594                            let mut facts = None;
3595                            merge_key_facts(&mut facts, &indices, budget, location)?;
3596                            append_types(&mut result, elements, budget, location)?;
3597                        }
3598                        ValueType::KeyedArray(keyed) => {
3599                            let mut facts = None;
3600                            merge_key_facts(&mut facts, &indices, budget, location)?;
3601                            if let Some(axes) = &keyed.axes {
3602                                check_key_facts(&facts, axes, budget, location)?;
3603                            }
3604                            append_types(&mut result, keyed.elements, budget, location)?;
3605                        }
3606                        ValueType::Array(array) => {
3607                            for ty in &indices {
3608                                require_type(ty, ScalarRequirement::Integer, location)?;
3609                            }
3610                            match array {
3611                                _ if constant.is_some_and(|position| position <= 0) => {
3612                                    push_metadata(
3613                                        &mut result,
3614                                        ValueType::Unestablished,
3615                                        budget,
3616                                        location,
3617                                    )?;
3618                                }
3619                                ArrayType::Positions(entries) if constant.is_some() => {
3620                                    let position =
3621                                        usize::try_from(constant.expect("constant checked")).ok();
3622                                    budget.charge(
3623                                        entries.len().min(position.unwrap_or(0)),
3624                                        location.clone(),
3625                                    )?;
3626                                    let ty = position
3627                                        .and_then(|position| position.checked_sub(1))
3628                                        .and_then(|index| entries.into_iter().nth(index))
3629                                        .unwrap_or(ValueType::Unestablished);
3630                                    push_metadata(&mut result, ty, budget, location)?;
3631                                }
3632                                ArrayType::Positions(entries) | ArrayType::Elements(entries) => {
3633                                    append_types(&mut result, entries, budget, location)?
3634                                }
3635                            }
3636                        }
3637                        ValueType::Unestablished => {
3638                            for ty in &indices {
3639                                if matches!(ty, ValueType::String) {
3640                                    continue;
3641                                }
3642                                let mut facts = None;
3643                                merge_key_facts(
3644                                    &mut facts,
3645                                    std::slice::from_ref(ty),
3646                                    budget,
3647                                    location,
3648                                )?;
3649                            }
3650                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3651                        }
3652                        _ => return Err(error(location, "array access needs an array or record")),
3653                    }
3654                }
3655                if result.is_empty() {
3656                    push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3657                }
3658                Ok(result)
3659            }
3660            ValueExpressionKind::IndexSet(array) => {
3661                let mut result = Vec::new();
3662                for ty in self.walk(array, budget)? {
3663                    budget.charge(1, location.clone())?;
3664                    let element_type = match shape_type(ty, budget, location)? {
3665                        ValueType::Array(_) => Some(SetElementType::Integer),
3666                        ValueType::KeyedArray(keyed) => match keyed.axes {
3667                            Some(axes) if axes.len() == 1 => Some(key_set_type(axes[0])),
3668                            Some(_) => return Err(error(location, "index_set needs rank one")),
3669                            None => None,
3670                        },
3671                        ValueType::ArrayKind(_) | ValueType::Unestablished => None,
3672                        _ => return Err(error(location, "index_set needs an array")),
3673                    };
3674                    push_metadata(&mut result, ValueType::Set(element_type), budget, location)?;
3675                }
3676                Ok(result)
3677            }
3678            ValueExpressionKind::Entries(array) => {
3679                let mut result = Vec::new();
3680                for ty in self.walk(array, budget)? {
3681                    budget.charge(1, location.clone())?;
3682                    let keyed = match shape_type(ty, budget, location)? {
3683                        ValueType::KeyedArray(keyed) => keyed,
3684                        ValueType::ArrayKind(elements) => KeyedArrayType {
3685                            axes: None,
3686                            elements,
3687                        },
3688                        ValueType::Unestablished => {
3689                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3690                            continue;
3691                        }
3692                        _ => return Err(error(location, "entries needs a keyed array")),
3693                    };
3694                    let key = match keyed.axes {
3695                        Some(axes) => {
3696                            let mut components = metadata_vec(axes.len(), budget, location)?;
3697                            for axis in axes {
3698                                budget.charge(1, location.clone())?;
3699                                components.push(match axis {
3700                                    ValueKeyType::Integer => ValueType::Integer,
3701                                    ValueKeyType::Member(id) => ValueType::Member(id),
3702                                });
3703                            }
3704                            if components.len() == 1 {
3705                                components.pop().expect("rank one")
3706                            } else {
3707                                ValueType::Tuple(components)
3708                            }
3709                        }
3710                        None => ValueType::Unestablished,
3711                    };
3712                    let mut records = metadata_vec(keyed.elements.len(), budget, location)?;
3713                    for value in keyed.elements {
3714                        let mut fields = metadata_vec(2, budget, location)?;
3715                        budget.charge("key".len() + "value".len(), location.clone())?;
3716                        fields.push(("key".into(), copy_type(&key, budget, location)?));
3717                        fields.push(("value".into(), value));
3718                        records.push(ValueType::Record(fields));
3719                    }
3720                    push_metadata(
3721                        &mut result,
3722                        ValueType::Array(ArrayType::Elements(records)),
3723                        budget,
3724                        location,
3725                    )?;
3726                }
3727                Ok(result)
3728            }
3729            ValueExpressionKind::ZipExact { left, right } => {
3730                let left = self.walk(left, budget)?;
3731                let right = self.walk(right, budget)?;
3732                let mut result = Vec::new();
3733                for a in &left {
3734                    for b in &right {
3735                        let ty = zip_exact_type(a, b, budget, location)?;
3736                        push_metadata(&mut result, ty, budget, location)?;
3737                    }
3738                }
3739                Ok(result)
3740            }
3741            ValueExpressionKind::Reduce { operator, array } => {
3742                for ty in self.walk(array, budget)? {
3743                    budget.charge(1, location.clone())?;
3744                    let elements = match shape_type(ty, budget, location)? {
3745                        ValueType::Array(
3746                            ArrayType::Positions(elements) | ArrayType::Elements(elements),
3747                        )
3748                        | ValueType::ArrayKind(elements) => elements,
3749                        ValueType::KeyedArray(keyed) => keyed.elements,
3750                        ValueType::Unestablished => continue,
3751                        _ => return Err(error(location, "reducer needs an array")),
3752                    };
3753                    if let Some(requirement) = match operator {
3754                        ValueReducer::Length => None,
3755                        ValueReducer::Sum => Some(ScalarRequirement::Integer),
3756                        ValueReducer::Forall | ValueReducer::Exists => {
3757                            Some(ScalarRequirement::Boolean)
3758                        }
3759                    } {
3760                        for element in elements {
3761                            budget.charge(1, location.clone())?;
3762                            require_type(&element, requirement, location)?;
3763                        }
3764                    }
3765                }
3766                one_type(
3767                    match operator {
3768                        ValueReducer::Length | ValueReducer::Sum => ValueType::Integer,
3769                        ValueReducer::Forall | ValueReducer::Exists => ValueType::Boolean,
3770                    },
3771                    budget,
3772                    location,
3773                )
3774            }
3775            ValueExpressionKind::Filter { array, predicate } => {
3776                let mut elements = Vec::new();
3777                for ty in self.walk(array, budget)? {
3778                    append_types(
3779                        &mut elements,
3780                        filter_elements(ty, budget, location)?,
3781                        budget,
3782                        location,
3783                    )?;
3784                }
3785                if elements.is_empty() {
3786                    push_metadata(&mut elements, ValueType::Unestablished, budget, location)?;
3787                }
3788                let result = self.scoped_types(predicate, array, &elements, true, budget)?;
3789                predicate_types(&result, budget, &predicate.body.range)?;
3790                one_type(
3791                    ValueType::Array(ArrayType::Elements(elements)),
3792                    budget,
3793                    location,
3794                )
3795            }
3796            ValueExpressionKind::Sort {
3797                array, selector, ..
3798            } => {
3799                let mut result = Vec::new();
3800                for ty in self.walk(array, budget)? {
3801                    let ty = sort_type(ty, selector.as_ref(), budget, location)?;
3802                    push_metadata(&mut result, ty, budget, location)?;
3803                }
3804                Ok(result)
3805            }
3806            ValueExpressionKind::Extremum { array, field, .. } => {
3807                let mut result = Vec::new();
3808                for ty in self.walk(array, budget)? {
3809                    let elements = ordering_elements(
3810                        ty,
3811                        field
3812                            .as_ref()
3813                            .map(|field| Located::new(field.value.as_str(), field.range.clone())),
3814                        budget,
3815                        location,
3816                    )?;
3817                    append_types(&mut result, elements, budget, location)?;
3818                }
3819                push_metadata(&mut result, ValueType::Absent, budget, location)?;
3820                Ok(result)
3821            }
3822            ValueExpressionKind::Unique(array) | ValueExpressionKind::Tally(array) => {
3823                let tally = matches!(expression.value, ValueExpressionKind::Tally(_));
3824                let mut result = Vec::new();
3825                for ty in self.walk(array, budget)? {
3826                    let retained = if !tally {
3827                        match &ty {
3828                            ValueType::Declared { declaration, .. } => {
3829                                Some(copy_declaration(declaration, budget, location)?)
3830                            }
3831                            _ => None,
3832                        }
3833                    } else {
3834                        None
3835                    };
3836                    let elements = scalar_pipeline_elements(ty, tally, budget, location)?;
3837                    let mut ty = if tally {
3838                        if elements.is_empty() {
3839                            ValueType::Record(Vec::new())
3840                        } else {
3841                            ValueType::RecordKind
3842                        }
3843                    } else {
3844                        ValueType::Array(ArrayType::Elements(elements))
3845                    };
3846                    if let Some(declaration) = retained {
3847                        ty = ValueType::Declared {
3848                            actual: Box::new(ty),
3849                            declaration,
3850                        };
3851                    }
3852                    push_metadata(&mut result, ty, budget, location)?;
3853                }
3854                Ok(result)
3855            }
3856            ValueExpressionKind::GroupBy { rows, field } => {
3857                let selector = Location::Query(field.range.clone());
3858                let mut result = Vec::new();
3859                for ty in self.walk(rows, budget)? {
3860                    budget.charge(1, location.clone())?;
3861                    let elements = match shape_type(ty, budget, location)? {
3862                        ValueType::Array(
3863                            ArrayType::Positions(elements) | ArrayType::Elements(elements),
3864                        )
3865                        | ValueType::ArrayKind(elements) => elements,
3866                        ValueType::Unestablished => {
3867                            push_metadata(&mut result, ValueType::Unestablished, budget, location)?;
3868                            continue;
3869                        }
3870                        _ => return Err(error(location, "group_by needs a positional array")),
3871                    };
3872                    let mut key_type = None;
3873                    for row in &elements {
3874                        budget.charge(1, location.clone())?;
3875                        let normalized = if matches!(row, ValueType::Declared { .. }) {
3876                            Some(shape_type(
3877                                copy_type(row, budget, location)?,
3878                                budget,
3879                                location,
3880                            )?)
3881                        } else {
3882                            None
3883                        };
3884                        let fields = match normalized.as_ref().unwrap_or(row) {
3885                            ValueType::Record(fields) => fields,
3886                            ValueType::Unestablished | ValueType::RecordKind => continue,
3887                            _ => return Err(error(location, "group_by needs record rows")),
3888                        };
3889                        for (label, ty) in fields {
3890                            budget.charge(
3891                                sum(1, label.len().min(field.value.len()), &selector)?,
3892                                selector.clone(),
3893                            )?;
3894                            if label != &field.value || matches!(ty, ValueType::Unestablished) {
3895                                continue;
3896                            }
3897                            if !matches!(
3898                                ty,
3899                                ValueType::Integer | ValueType::String | ValueType::Member(_)
3900                            ) {
3901                                return Err(error(
3902                                    &selector,
3903                                    "group_by keys need integers, strings or nominal members",
3904                                ));
3905                            }
3906                            if key_type.as_ref().is_some_and(|previous| previous != ty) {
3907                                return Err(error(
3908                                    &selector,
3909                                    "group_by needs homogeneous key types",
3910                                ));
3911                            }
3912                            if key_type.is_none() {
3913                                key_type = Some(copy_type(ty, budget, &selector)?);
3914                            }
3915                        }
3916                    }
3917                    let mut groups = Vec::new();
3918                    if !elements.is_empty() {
3919                        let mut fields = metadata_vec(2, budget, location)?;
3920                        budget.charge("key".len() + "items".len(), location.clone())?;
3921                        fields.push(("key".into(), key_type.unwrap_or(ValueType::Unestablished)));
3922                        fields.push((
3923                            "items".into(),
3924                            ValueType::Array(ArrayType::Elements(elements)),
3925                        ));
3926                        push_metadata(&mut groups, ValueType::Record(fields), budget, location)?;
3927                    }
3928                    push_metadata(
3929                        &mut result,
3930                        ValueType::Array(ArrayType::Elements(groups)),
3931                        budget,
3932                        location,
3933                    )?;
3934                }
3935                Ok(result)
3936            }
3937            ValueExpressionKind::Require {
3938                condition,
3939                value,
3940                message,
3941            } => {
3942                self.condition(condition, budget)?;
3943                let types = self.walk(value, budget)?;
3944                allocation::<u8>(message.value.len(), location)?;
3945                budget.charge(message.value.len(), Location::Query(message.range.clone()))?;
3946                Ok(types)
3947            }
3948            ValueExpressionKind::Range { lower, upper } => {
3949                for expression in [lower, upper] {
3950                    for ty in self.walk(expression, budget)? {
3951                        require_type(&ty, ScalarRequirement::Integer, location)?;
3952                    }
3953                }
3954                one_type(
3955                    ValueType::Set(Some(SetElementType::Integer)),
3956                    budget,
3957                    location,
3958                )
3959            }
3960            ValueExpressionKind::Conditional {
3961                branches,
3962                otherwise,
3963            } => {
3964                let mut types = Vec::new();
3965                for (condition, branch) in branches {
3966                    self.condition(condition, budget)?;
3967                    append_types(
3968                        &mut types,
3969                        self.walk_control(branch, budget, transfer)?,
3970                        budget,
3971                        location,
3972                    )?;
3973                }
3974                append_types(
3975                    &mut types,
3976                    self.walk_control(otherwise, budget, transfer)?,
3977                    budget,
3978                    location,
3979                )?;
3980                Ok(types)
3981            }
3982            ValueExpressionKind::Loop { states, body } => {
3983                let mut initial = metadata_vec(states.len(), budget, location)?;
3984                for (_, value) in states {
3985                    initial.push(self.walk(value, budget)?);
3986                }
3987                let saved = self.locals.len();
3988                self.queries.enter_scope(budget, &expression.range)?;
3989                let result = (|| {
3990                    for ((name, value), facts) in states.iter().zip(initial) {
3991                        function_local(name, &self.locals, budget)?;
3992                        if self.global_exists(&name.value, name.range.clone(), budget)? {
3993                            return Err(error(
3994                                &Location::Query(name.range.clone()),
3995                                "binding shadows an existing source or let",
3996                            ));
3997                        }
3998                        self.queries.prepare_local(name, value, &facts, budget)?;
3999                        push_metadata(
4000                            &mut self.locals,
4001                            (name.value.as_str(), facts),
4002                            budget,
4003                            location,
4004                        )?;
4005                    }
4006                    self.queries.prepare_computation(expression, budget)?;
4007                    self.walk_control(body, budget, Some(states.len()))
4008                })();
4009                self.locals.truncate(saved);
4010                self.queries.leave_scope();
4011                result
4012            }
4013            ValueExpressionKind::Break(value) => self.walk(value, budget),
4014            ValueExpressionKind::Continue(values) => {
4015                for value in values {
4016                    self.walk(value, budget)?;
4017                }
4018                Ok(Vec::new())
4019            }
4020            ValueExpressionKind::Block { bindings, value } => {
4021                let saved = self.locals.len();
4022                self.queries.enter_scope(budget, &expression.range)?;
4023                let result = (|| {
4024                    for (pattern, value) in bindings {
4025                        let types = self.walk(value, budget)?;
4026                        self.pattern(pattern, &types, false, budget)?;
4027                        self.queries.prepare_pattern(pattern, value, budget)?;
4028                    }
4029                    self.walk_control(value, budget, transfer)
4030                })();
4031                self.locals.truncate(saved);
4032                self.queries.leave_scope();
4033                result
4034            }
4035            ValueExpressionKind::ValueMatch { subject, arms } => {
4036                if arms.is_empty()
4037                    || !matches!(
4038                        arms.last().map(|a| (&a.pattern.value, &a.guard)),
4039                        Some((PatternKind::Wildcard, None))
4040                    )
4041                {
4042                    return Err(error(
4043                        location,
4044                        "value match needs a final unguarded wildcard",
4045                    ));
4046                }
4047                let facts = self.walk(subject, budget)?;
4048                let mut types = Vec::new();
4049                for (index, arm) in arms.iter().enumerate() {
4050                    if index + 1 < arms.len()
4051                        && matches!(arm.pattern.value, PatternKind::Wildcard)
4052                        && arm.guard.is_none()
4053                    {
4054                        return Err(error(
4055                            &Location::Query(arm.pattern.range.clone()),
4056                            "unguarded wildcard must be the final arm",
4057                        ));
4058                    }
4059                    let saved = self.locals.len();
4060                    self.queries.enter_scope(budget, &arm.pattern.range)?;
4061                    let result = (|| {
4062                        self.pattern(&arm.pattern, &facts, false, budget)?;
4063                        self.queries
4064                            .prepare_pattern(&arm.pattern, subject, budget)?;
4065                        if let Some(guard) = &arm.guard {
4066                            self.condition(guard, budget)?;
4067                        }
4068                        self.walk_control(&arm.value, budget, transfer)
4069                    })();
4070                    self.locals.truncate(saved);
4071                    self.queries.leave_scope();
4072                    append_types(&mut types, result?, budget, location)?;
4073                }
4074                Ok(types)
4075            }
4076            ValueExpressionKind::Comprehension {
4077                constructor,
4078                clauses,
4079                head,
4080            } => {
4081                let saved = self.locals.len();
4082                let result = (|| {
4083                    self.clauses(clauses, budget)?;
4084                    let types = self.walk(head, budget)?;
4085                    let ty = match constructor {
4086                        ValueConstructor::Array => ValueType::Array(ArrayType::Elements(types)),
4087                        ValueConstructor::Set => {
4088                            let mut element_type = None;
4089                            merge_set_types(&mut element_type, &types, budget, location)?;
4090                            ValueType::Set(element_type)
4091                        }
4092                    };
4093                    one_type(ty, budget, location)
4094                })();
4095                self.locals.truncate(saved);
4096                result
4097            }
4098        }
4099    }
4100
4101    fn scoped_types(
4102        &mut self,
4103        predicate: &'a PredicateArgument<Q>,
4104        source: &'a ValueExpression<Q>,
4105        types: &[ValueType],
4106        element: bool,
4107        budget: &mut Budget,
4108    ) -> Result<Vec<ValueType>, QueryError> {
4109        let range = &predicate.body.range;
4110        let location = Location::Query(range.clone());
4111        let saved = self.locals.len();
4112        self.queries.enter_scope(budget, range)?;
4113        let result = (|| {
4114            push_metadata(
4115                &mut self.locals,
4116                (PREDICATE_SUBJECT, copy_types(types, budget, &location)?),
4117                budget,
4118                &location,
4119            )?;
4120            if let Some(pattern) = &predicate.argument {
4121                self.pattern(pattern, types, true, budget)?;
4122            }
4123            self.queries.prepare_predicate(
4124                predicate.argument.as_ref(),
4125                source,
4126                types,
4127                element,
4128                budget,
4129            )?;
4130            self.walk(&predicate.body, budget)
4131        })();
4132        self.locals.truncate(saved);
4133        self.queries.leave_scope();
4134        result
4135    }
4136
4137    fn pattern(
4138        &mut self,
4139        pattern: &'a Pattern,
4140        facts: &[ValueType],
4141        required: bool,
4142        budget: &mut Budget,
4143    ) -> Result<(), QueryError> {
4144        let names = if required {
4145            pattern::required_facts(pattern, facts, budget)?
4146        } else {
4147            pattern::facts(pattern, facts, budget)?
4148        };
4149        for (name, facts) in names {
4150            let location = Location::Query(name.range.clone());
4151            function_local(name, &self.locals, budget)?;
4152            if self.global_exists(&name.value, name.range.clone(), budget)? {
4153                return Err(error(
4154                    &location,
4155                    "binding shadows an existing source or let",
4156                ));
4157            }
4158            push_metadata(
4159                &mut self.locals,
4160                (name.value.as_str(), facts),
4161                budget,
4162                &location,
4163            )?;
4164        }
4165        Ok(())
4166    }
4167
4168    fn clauses(
4169        &mut self,
4170        clauses: &'a [Located<ValueGenerator<Q>>],
4171        budget: &mut Budget,
4172    ) -> Result<(), QueryError> {
4173        for clause in clauses {
4174            let clause_location = Location::Query(clause.range.clone());
4175            budget.charge(1, clause_location.clone())?;
4176            match &clause.value {
4177                ValueGenerator::In {
4178                    binders,
4179                    source,
4180                    filter,
4181                } => {
4182                    if binders.is_empty() {
4183                        return Err(error(&clause_location, "in generator needs a binder"));
4184                    }
4185                    let source_types = self.walk(source, budget)?;
4186                    let mut elements = Vec::new();
4187                    for ty in source_types {
4188                        match shape_type(ty, budget, &clause_location)? {
4189                            ValueType::Array(
4190                                ArrayType::Positions(entries) | ArrayType::Elements(entries),
4191                            )
4192                            | ValueType::ArrayKind(entries) => {
4193                                append_types(&mut elements, entries, budget, &clause_location)?
4194                            }
4195                            ValueType::KeyedArray(keyed) => append_types(
4196                                &mut elements,
4197                                keyed.elements,
4198                                budget,
4199                                &clause_location,
4200                            )?,
4201                            ValueType::Set(element_type) => push_metadata(
4202                                &mut elements,
4203                                set_value_type(element_type),
4204                                budget,
4205                                &clause_location,
4206                            )?,
4207                            ValueType::Unestablished => push_metadata(
4208                                &mut elements,
4209                                ValueType::Unestablished,
4210                                budget,
4211                                &clause_location,
4212                            )?,
4213                            _ => {
4214                                return Err(error(
4215                                    &Location::Query(source.range.clone()),
4216                                    "in source needs an array or supported set",
4217                                ));
4218                            }
4219                        }
4220                    }
4221                    if elements.is_empty() {
4222                        push_metadata(
4223                            &mut elements,
4224                            ValueType::Unestablished,
4225                            budget,
4226                            &clause_location,
4227                        )?;
4228                    }
4229                    for binder in binders {
4230                        self.pattern(binder, &elements, false, budget)?;
4231                        self.queries
4232                            .prepare_generator(binder, source, &elements, true, budget)?;
4233                    }
4234                    if let Some(filter) = filter {
4235                        self.condition(filter, budget)?;
4236                    }
4237                }
4238                ValueGenerator::Bind {
4239                    binder,
4240                    value,
4241                    filter,
4242                } => {
4243                    let types = self.walk(value, budget)?;
4244                    self.pattern(binder, &types, false, budget)?;
4245                    self.queries
4246                        .prepare_generator(binder, value, &types, false, budget)?;
4247                    if let Some(filter) = filter {
4248                        self.condition(filter, budget)?;
4249                    }
4250                }
4251            }
4252        }
4253        Ok(())
4254    }
4255
4256    fn condition(
4257        &mut self,
4258        condition: &'a ValueExpression<Q>,
4259        budget: &mut Budget,
4260    ) -> Result<(), QueryError> {
4261        let location = Location::Query(condition.range.clone());
4262        for ty in self.walk(condition, budget)? {
4263            budget.charge(1, location.clone())?;
4264            require_type(&ty, ScalarRequirement::Boolean, &location)?;
4265        }
4266        Ok(())
4267    }
4268
4269    fn axis_sources(
4270        &mut self,
4271        sources: &'a [ValueExpression<Q>],
4272        budget: &mut Budget,
4273        location: &Location,
4274    ) -> Result<Vec<Option<ValueKeyType>>, QueryError> {
4275        if sources.is_empty() {
4276            return Err(error(location, "axis sources need nonzero rank"));
4277        }
4278        let mut facts = metadata_vec(sources.len(), budget, location)?;
4279        for source in sources {
4280            let types = self.walk(source, budget)?;
4281            facts.push(source_key_type(
4282                &types,
4283                budget,
4284                &Location::Query(source.range.clone()),
4285            )?);
4286        }
4287        Ok(facts)
4288    }
4289
4290    // Tuple and record positions retain each concrete branch possibility. Admit
4291    // the bounded product before materializing it; no unknown slot hides a known
4292    // incompatible type in a later access or recursive equality.
4293    fn positions(
4294        &mut self,
4295        children: impl Iterator<Item = &'a ValueExpression<Q>>,
4296        budget: &mut Budget,
4297        location: &Location,
4298    ) -> Result<Vec<Vec<ValueType>>, QueryError> {
4299        let mut positions = metadata_vec(1, budget, location)?;
4300        positions.push(Vec::new());
4301        for child in children {
4302            let types = self.walk(child, budget)?;
4303            positions = extend_type_positions(&positions, &types, budget, location)?;
4304        }
4305        Ok(positions)
4306    }
4307}
4308
4309fn zip_exact_type(
4310    left: &ValueType,
4311    right: &ValueType,
4312    budget: &mut Budget,
4313    location: &Location,
4314) -> Result<ValueType, QueryError> {
4315    if matches!(left, ValueType::Declared { .. }) || matches!(right, ValueType::Declared { .. }) {
4316        let left = shape_type(copy_type(left, budget, location)?, budget, location)?;
4317        let right = shape_type(copy_type(right, budget, location)?, budget, location)?;
4318        return zip_exact_type(&left, &right, budget, location);
4319    }
4320    let deferred = [ValueType::Unestablished];
4321    let [left, right] = [left, right].map(|ty| match ty {
4322        ValueType::KeyedArray(keyed) => Ok((keyed.axes.as_deref(), keyed.elements.as_slice())),
4323        ValueType::ArrayKind(elements) => Ok((None, elements.as_slice())),
4324        ValueType::Unestablished => Ok((None, deferred.as_slice())),
4325        _ => Err(error(location, "zip_exact needs keyed arrays")),
4326    });
4327    let (left_axes, a) = left?;
4328    let (right_axes, b) = right?;
4329    if let (Some(a), Some(b)) = (left_axes, right_axes)
4330        && !same_axis_types(a, b, budget, location)?
4331    {
4332        return Err(error(
4333            location,
4334            "zip_exact needs identical axis rank and types",
4335        ));
4336    }
4337    let axes = match left_axes.or(right_axes) {
4338        Some(axes) => Some(copy_axis_types(axes, budget, location)?),
4339        None => None,
4340    };
4341    let mut elements = Vec::new();
4342    for a in a {
4343        for b in b {
4344            let mut pair = metadata_vec(2, budget, location)?;
4345            pair.push(copy_type(a, budget, location)?);
4346            pair.push(copy_type(b, budget, location)?);
4347            push_metadata(&mut elements, ValueType::Tuple(pair), budget, location)?;
4348        }
4349    }
4350    Ok(ValueType::KeyedArray(KeyedArrayType { axes, elements }))
4351}
4352
4353// Keep known component facts even when one deferred component prevents a public
4354// complete axis description. This catches rank/identity conflicts in all arms.
4355fn key_component_type(
4356    ty: &ValueType,
4357    location: &Location,
4358) -> Result<Option<ValueKeyType>, QueryError> {
4359    match ty {
4360        ValueType::Declared {
4361            actual,
4362            declaration,
4363        } => {
4364            if !matches!(
4365                actual.as_ref(),
4366                ValueType::Unestablished | ValueType::ScalarKind
4367            ) {
4368                return key_component_type(actual, location);
4369            }
4370            match underlying(declaration) {
4371                DeclaredType::Integer => Ok(Some(ValueKeyType::Integer)),
4372                DeclaredType::Member(id) if !matches!(actual.as_ref(), ValueType::ScalarKind) => {
4373                    Ok(Some(ValueKeyType::Member(*id)))
4374                }
4375                _ => Err(error(location, "key components need integers or members")),
4376            }
4377        }
4378        // A successful primitive key must be integer. Keep that family and
4379        // rank established while its actual primitive kind remains deferred.
4380        ValueType::Integer | ValueType::ScalarKind => Ok(Some(ValueKeyType::Integer)),
4381        ValueType::Member(id) => Ok(Some(ValueKeyType::Member(*id))),
4382        ValueType::Unestablished => Ok(None),
4383        _ => Err(error(location, "key components need integers or members")),
4384    }
4385}
4386
4387fn merge_key_facts(
4388    facts: &mut Option<Vec<Option<ValueKeyType>>>,
4389    types: &[ValueType],
4390    budget: &mut Budget,
4391    location: &Location,
4392) -> Result<(), QueryError> {
4393    for ty in types {
4394        budget.charge(1, location.clone())?;
4395        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4396            Some(shape_type(
4397                copy_type(ty, budget, location)?,
4398                budget,
4399                location,
4400            )?)
4401        } else {
4402            None
4403        };
4404        let ty = normalized.as_ref().unwrap_or(ty);
4405        let components = match ty {
4406            ValueType::Unestablished | ValueType::TupleKind => continue,
4407            ValueType::Tuple(types) if !types.is_empty() => {
4408                let mut result = metadata_vec(types.len(), budget, location)?;
4409                for ty in types {
4410                    budget.charge(1, location.clone())?;
4411                    result.push(key_component_type(ty, location)?);
4412                }
4413                result
4414            }
4415            ValueType::Integer | ValueType::ScalarKind | ValueType::Member(_) => {
4416                let mut result = metadata_vec(1, budget, location)?;
4417                result.push(key_component_type(ty, location)?);
4418                result
4419            }
4420            _ => {
4421                return Err(error(
4422                    location,
4423                    "key needs an integer, member or nonempty flat tuple",
4424                ));
4425            }
4426        };
4427        match facts {
4428            Some(previous) => {
4429                if previous.len() != components.len() {
4430                    return Err(error(location, "key rank mismatch"));
4431                }
4432                for (previous, ty) in previous.iter_mut().zip(components) {
4433                    budget.charge(1, location.clone())?;
4434                    if previous.is_some() && ty.is_some() && *previous != ty {
4435                        return Err(error(location, "incompatible keyed dimension type"));
4436                    }
4437                    if previous.is_none() {
4438                        *previous = ty;
4439                    }
4440                }
4441            }
4442            None => *facts = Some(components),
4443        }
4444    }
4445    Ok(())
4446}
4447
4448fn check_key_facts(
4449    facts: &Option<Vec<Option<ValueKeyType>>>,
4450    axes: &[ValueKeyType],
4451    budget: &mut Budget,
4452    location: &Location,
4453) -> Result<(), QueryError> {
4454    if let Some(facts) = facts {
4455        if facts.len() != axes.len() {
4456            return Err(error(location, "key rank mismatch"));
4457        }
4458        for (fact, axis) in facts.iter().zip(axes) {
4459            budget.charge(1, location.clone())?;
4460            if fact.is_some_and(|fact| fact != *axis) {
4461                return Err(error(location, "incompatible keyed dimension type"));
4462            }
4463        }
4464    }
4465    Ok(())
4466}
4467
4468fn established_facts(
4469    facts: Option<Vec<Option<ValueKeyType>>>,
4470    budget: &mut Budget,
4471    location: &Location,
4472) -> Result<Option<Vec<ValueKeyType>>, QueryError> {
4473    let Some(facts) = facts else {
4474        return Ok(None);
4475    };
4476    let mut axes = metadata_vec(facts.len(), budget, location)?;
4477    for fact in facts {
4478        budget.charge(1, location.clone())?;
4479        let Some(fact) = fact else {
4480            return Ok(None);
4481        };
4482        axes.push(fact);
4483    }
4484    Ok(Some(axes))
4485}
4486
4487fn source_key_type(
4488    types: &[ValueType],
4489    budget: &mut Budget,
4490    location: &Location,
4491) -> Result<Option<ValueKeyType>, QueryError> {
4492    let mut result = None;
4493    for ty in types {
4494        budget.charge(1, location.clone())?;
4495        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4496            Some(shape_type(
4497                copy_type(ty, budget, location)?,
4498                budget,
4499                location,
4500            )?)
4501        } else {
4502            None
4503        };
4504        let ty = normalized.as_ref().unwrap_or(ty);
4505        let keys = match ty {
4506            ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
4507            | ValueType::ArrayKind(elements)
4508            | ValueType::KeyedArray(KeyedArrayType { elements, .. }) => {
4509                let mut key_type = None;
4510                for ty in elements {
4511                    budget.charge(1, location.clone())?;
4512                    let ty = key_component_type(ty, location)?;
4513                    if key_type.is_some() && ty.is_some() && key_type != ty {
4514                        return Err(error(location, "incompatible axis source type"));
4515                    }
4516                    if ty.is_some() {
4517                        key_type = ty;
4518                    }
4519                }
4520                key_type
4521            }
4522            ValueType::Set(Some(SetElementType::Integer)) => Some(ValueKeyType::Integer),
4523            ValueType::Set(Some(SetElementType::Member(id))) => Some(ValueKeyType::Member(*id)),
4524            ValueType::Set(None) | ValueType::Unestablished => None,
4525            _ => {
4526                return Err(error(
4527                    location,
4528                    "axis source needs an integer or member array/set",
4529                ));
4530            }
4531        };
4532        if result.is_some() && keys.is_some() && result != keys {
4533            return Err(error(location, "incompatible axis source type"));
4534        }
4535        if keys.is_some() {
4536            result = keys;
4537        }
4538    }
4539    Ok(result)
4540}
4541
4542fn check_source_facts(
4543    facts: &Option<Vec<Option<ValueKeyType>>>,
4544    sources: &[Option<ValueKeyType>],
4545    budget: &mut Budget,
4546    location: &Location,
4547) -> Result<(), QueryError> {
4548    if let Some(facts) = facts {
4549        if facts.len() != sources.len() {
4550            return Err(error(location, "axis source rank mismatch"));
4551        }
4552        for (fact, source) in facts.iter().zip(sources) {
4553            budget.charge(1, location.clone())?;
4554            if fact.is_some() && source.is_some() && *fact != *source {
4555                return Err(error(location, "incompatible axis source type"));
4556            }
4557            // Unknown sources do not acquire identity from the key.
4558        }
4559    }
4560    Ok(())
4561}
4562
4563#[derive(Clone, Copy)]
4564enum ScalarRequirement {
4565    String,
4566    Boolean,
4567    Integer,
4568    Numeric,
4569}
4570
4571pub(crate) fn predicate_types(
4572    types: &[ValueType],
4573    budget: &mut Budget,
4574    range: &std::ops::Range<usize>,
4575) -> Result<(), QueryError> {
4576    let location = Location::Query(range.clone());
4577    for ty in types {
4578        budget.charge(1, location.clone())?;
4579        require_type(ty, ScalarRequirement::Boolean, &location)?;
4580    }
4581    Ok(())
4582}
4583
4584fn require_type(
4585    ty: &ValueType,
4586    requirement: ScalarRequirement,
4587    location: &Location,
4588) -> Result<(), QueryError> {
4589    if let ValueType::Declared {
4590        actual,
4591        declaration,
4592    } = ty
4593    {
4594        if !matches!(
4595            actual.as_ref(),
4596            ValueType::Unestablished | ValueType::ScalarKind
4597        ) {
4598            return require_type(actual, requirement, location);
4599        }
4600        let expected = underlying(declaration);
4601        let accepted = match requirement {
4602            ScalarRequirement::String => matches!(expected, DeclaredType::String),
4603            ScalarRequirement::Boolean => matches!(expected, DeclaredType::Boolean),
4604            ScalarRequirement::Integer => matches!(expected, DeclaredType::Integer),
4605            ScalarRequirement::Numeric => {
4606                matches!(expected, DeclaredType::Integer | DeclaredType::Float)
4607            }
4608        };
4609        if accepted {
4610            return Ok(());
4611        }
4612        return Err(error(location, "incompatible declared operand type"));
4613    }
4614    let accepted = matches!(ty, ValueType::Unestablished | ValueType::ScalarKind)
4615        || match requirement {
4616            ScalarRequirement::String => matches!(ty, ValueType::String),
4617            ScalarRequirement::Boolean => matches!(ty, ValueType::Boolean),
4618            ScalarRequirement::Integer => matches!(ty, ValueType::Integer),
4619            ScalarRequirement::Numeric => matches!(ty, ValueType::Integer | ValueType::Float),
4620        };
4621    if !accepted {
4622        return Err(error(
4623            location,
4624            match requirement {
4625                ScalarRequirement::String => "dictionary key needs a present string",
4626                ScalarRequirement::Boolean => "condition needs a fixed Boolean",
4627                ScalarRequirement::Integer => "operation needs integers",
4628                ScalarRequirement::Numeric => "operation needs numeric operands",
4629            },
4630        ));
4631    }
4632    Ok(())
4633}
4634
4635fn set_value_type(ty: Option<SetElementType>) -> ValueType {
4636    match ty {
4637        Some(SetElementType::Boolean) => ValueType::Boolean,
4638        Some(SetElementType::Integer) => ValueType::Integer,
4639        Some(SetElementType::Float) => ValueType::Float,
4640        Some(SetElementType::Member(id)) => ValueType::Member(id),
4641        None => ValueType::Unestablished,
4642    }
4643}
4644
4645fn merge_set_types(
4646    element_type: &mut Option<SetElementType>,
4647    types: &[ValueType],
4648    budget: &mut Budget,
4649    location: &Location,
4650) -> Result<(), QueryError> {
4651    for ty in types {
4652        budget.charge(1, location.clone())?;
4653        let normalized = if matches!(ty, ValueType::Declared { .. }) {
4654            Some(shape_type(
4655                copy_type(ty, budget, location)?,
4656                budget,
4657                location,
4658            )?)
4659        } else {
4660            None
4661        };
4662        let ty = normalized.as_ref().unwrap_or(ty);
4663        let ty = match ty {
4664            ValueType::Boolean => SetElementType::Boolean,
4665            ValueType::Integer => SetElementType::Integer,
4666            ValueType::Float => SetElementType::Float,
4667            ValueType::Member(id) => SetElementType::Member(*id),
4668            ValueType::Unestablished => continue,
4669            _ => {
4670                return Err(error(
4671                    location,
4672                    "set elements must have one supported scalar type",
4673                ));
4674            }
4675        };
4676        if element_type.is_some_and(|previous| previous != ty) {
4677            return Err(error(
4678                location,
4679                "set elements must have one supported scalar type",
4680            ));
4681        }
4682        *element_type = Some(ty);
4683    }
4684    Ok(())
4685}
4686
4687fn compatible_types(
4688    left: &ValueType,
4689    right: &ValueType,
4690    budget: &mut Budget,
4691    location: &Location,
4692) -> Result<bool, QueryError> {
4693    budget.within_evaluation(1, location.clone(), |budget| {
4694        budget.charge(1, location.clone())?;
4695        if let ValueType::Declared { declaration, .. } = left {
4696            if let ValueType::Declared {
4697                declaration: expected,
4698                ..
4699            } = right
4700            {
4701                return compatible_declarations(declaration, expected, budget, location);
4702            }
4703            let expected = DeclaredType::Optional(Box::new(copy_declaration(
4704                underlying(declaration),
4705                budget,
4706                location,
4707            )?));
4708            check_known_type(right, &expected, budget, location)?;
4709            return Ok(true);
4710        }
4711        if matches!(right, ValueType::Declared { .. }) {
4712            return compatible_types(right, left, budget, location);
4713        }
4714        Ok(match (left, right) {
4715            (ValueType::Absent, _) | (_, ValueType::Absent) => true,
4716            (ValueType::Unestablished, _) | (_, ValueType::Unestablished) => true,
4717            (
4718                ValueType::ScalarKind,
4719                ValueType::ScalarKind
4720                | ValueType::Boolean
4721                | ValueType::Integer
4722                | ValueType::Float
4723                | ValueType::String,
4724            )
4725            | (
4726                ValueType::Boolean | ValueType::Integer | ValueType::Float | ValueType::String,
4727                ValueType::ScalarKind,
4728            ) => true,
4729            (ValueType::Boolean, ValueType::Boolean)
4730            | (ValueType::Integer, ValueType::Integer)
4731            | (ValueType::Float, ValueType::Float)
4732            | (ValueType::String, ValueType::String) => true,
4733            (ValueType::RecordKind, ValueType::RecordKind | ValueType::Record(_))
4734            | (ValueType::Record(_), ValueType::RecordKind)
4735            | (ValueType::TupleKind, ValueType::TupleKind | ValueType::Tuple(_))
4736            | (ValueType::Tuple(_), ValueType::TupleKind) => true,
4737            (ValueType::Member(a), ValueType::Member(b)) => a == b,
4738            (ValueType::ArrayKind(a), ValueType::ArrayKind(b))
4739            | (
4740                ValueType::ArrayKind(a),
4741                ValueType::Array(ArrayType::Positions(b) | ArrayType::Elements(b)),
4742            )
4743            | (
4744                ValueType::Array(ArrayType::Positions(a) | ArrayType::Elements(a)),
4745                ValueType::ArrayKind(b),
4746            ) => compatible_keyed_elements(a, b, budget, location)?,
4747            (ValueType::ArrayKind(a), ValueType::KeyedArray(b)) => {
4748                compatible_keyed_elements(a, &b.elements, budget, location)?
4749            }
4750            (ValueType::KeyedArray(a), ValueType::ArrayKind(b)) => {
4751                compatible_keyed_elements(&a.elements, b, budget, location)?
4752            }
4753            (ValueType::KeyedArray(a), ValueType::KeyedArray(b)) => {
4754                if let (Some(a), Some(b)) = (&a.axes, &b.axes)
4755                    && !same_axis_types(a, b, budget, location)?
4756                {
4757                    return Ok(false);
4758                }
4759                compatible_keyed_elements(&a.elements, &b.elements, budget, location)?
4760            }
4761            (ValueType::Set(a), ValueType::Set(b)) => a.is_none() || b.is_none() || a == b,
4762            (ValueType::Tuple(a), ValueType::Tuple(b)) => {
4763                if a.len() != b.len() {
4764                    return Ok(false);
4765                }
4766                for (a, b) in a.iter().zip(b) {
4767                    if !compatible_types(a, b, budget, location)? {
4768                        return Ok(false);
4769                    }
4770                }
4771                true
4772            }
4773            (ValueType::Array(a), ValueType::Array(b)) => {
4774                match (a, b) {
4775                    (ArrayType::Positions(a), ArrayType::Positions(b)) => {
4776                        for (a, b) in a.iter().zip(b) {
4777                            if !compatible_types(a, b, budget, location)? {
4778                                return Ok(false);
4779                            }
4780                        }
4781                    }
4782                    (
4783                        ArrayType::Positions(a) | ArrayType::Elements(a),
4784                        ArrayType::Positions(b) | ArrayType::Elements(b),
4785                    ) => {
4786                        for a in a {
4787                            for b in b {
4788                                if !compatible_types(a, b, budget, location)? {
4789                                    return Ok(false);
4790                                }
4791                            }
4792                        }
4793                    }
4794                }
4795                true
4796            }
4797            (ValueType::Record(a), ValueType::Record(b)) => {
4798                if a.len() != b.len() {
4799                    return Ok(false);
4800                }
4801                for (label, ty) in a {
4802                    let mut other = None;
4803                    for (other_label, other_ty) in b {
4804                        budget.charge(
4805                            sum(1, label.len().min(other_label.len()), location)?,
4806                            location.clone(),
4807                        )?;
4808                        if label == other_label {
4809                            other = Some(other_ty);
4810                            break;
4811                        }
4812                    }
4813                    let Some(other) = other else {
4814                        return Ok(false);
4815                    };
4816                    if !compatible_types(ty, other, budget, location)? {
4817                        return Ok(false);
4818                    }
4819                }
4820                true
4821            }
4822            _ => false,
4823        })
4824    })
4825}
4826
4827// Keyed facts retain possible element types, not correspondence between keys.
4828// Check each known family here; execution checks the actual matching cells.
4829fn compatible_keyed_elements(
4830    left: &[ValueType],
4831    right: &[ValueType],
4832    budget: &mut Budget,
4833    location: &Location,
4834) -> Result<bool, QueryError> {
4835    if left.is_empty() || right.is_empty() {
4836        return Ok(true);
4837    }
4838    for (types, alternatives) in [(left, right), (right, left)] {
4839        for ty in types {
4840            let mut compatible = false;
4841            for alternative in alternatives {
4842                if compatible_types(ty, alternative, budget, location)? {
4843                    compatible = true;
4844                    break;
4845                }
4846            }
4847            if !compatible {
4848                return Ok(false);
4849            }
4850        }
4851    }
4852    Ok(true)
4853}
4854
4855fn binary_type(
4856    operator: ValueBinaryOperator,
4857    left: &ValueType,
4858    right: &ValueType,
4859    budget: &mut Budget,
4860    location: &Location,
4861) -> Result<ValueType, QueryError> {
4862    if !matches!(
4863        operator,
4864        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual
4865    ) && (matches!(left, ValueType::Declared { .. })
4866        || matches!(right, ValueType::Declared { .. }))
4867    {
4868        let left = shape_type(copy_type(left, budget, location)?, budget, location)?;
4869        let right = shape_type(copy_type(right, budget, location)?, budget, location)?;
4870        return binary_type(operator, &left, &right, budget, location);
4871    }
4872    match operator {
4873        ValueBinaryOperator::Add
4874        | ValueBinaryOperator::Subtract
4875        | ValueBinaryOperator::Multiply
4876        | ValueBinaryOperator::Divide
4877        | ValueBinaryOperator::Less
4878        | ValueBinaryOperator::LessEqual
4879        | ValueBinaryOperator::Greater
4880        | ValueBinaryOperator::GreaterEqual => {
4881            require_type(left, ScalarRequirement::Numeric, location)?;
4882            require_type(right, ScalarRequirement::Numeric, location)?;
4883            Ok(match operator {
4884                ValueBinaryOperator::Less
4885                | ValueBinaryOperator::LessEqual
4886                | ValueBinaryOperator::Greater
4887                | ValueBinaryOperator::GreaterEqual => ValueType::Boolean,
4888                ValueBinaryOperator::Divide => ValueType::Float,
4889                _ if matches!(left, ValueType::Float) || matches!(right, ValueType::Float) => {
4890                    ValueType::Float
4891                }
4892                _ if matches!(left, ValueType::ScalarKind)
4893                    || matches!(right, ValueType::ScalarKind) =>
4894                {
4895                    ValueType::ScalarKind
4896                }
4897                _ if matches!(left, ValueType::Unestablished)
4898                    || matches!(right, ValueType::Unestablished) =>
4899                {
4900                    ValueType::Unestablished
4901                }
4902                _ => ValueType::Integer,
4903            })
4904        }
4905        ValueBinaryOperator::IntegerDivide | ValueBinaryOperator::Modulo => {
4906            require_type(left, ScalarRequirement::Integer, location)?;
4907            require_type(right, ScalarRequirement::Integer, location)?;
4908            Ok(ValueType::Integer)
4909        }
4910        ValueBinaryOperator::Contains
4911        | ValueBinaryOperator::StartsWith
4912        | ValueBinaryOperator::EndsWith => {
4913            if !matches!(
4914                left,
4915                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished
4916            ) || !matches!(
4917                right,
4918                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished
4919            ) {
4920                return Err(string_operand_error(budget, location));
4921            }
4922            Ok(ValueType::Boolean)
4923        }
4924        ValueBinaryOperator::And | ValueBinaryOperator::Or => {
4925            require_type(left, ScalarRequirement::Boolean, location)?;
4926            require_type(right, ScalarRequirement::Boolean, location)?;
4927            Ok(ValueType::Boolean)
4928        }
4929        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual => {
4930            if !compatible_types(left, right, budget, location)? {
4931                return Err(error(location, "equality needs compatible types"));
4932            }
4933            Ok(ValueType::Boolean)
4934        }
4935        ValueBinaryOperator::In => {
4936            let mut element_type = None;
4937            let deferred_primitive = matches!(left, ValueType::ScalarKind)
4938                && matches!(
4939                    right,
4940                    ValueType::Unestablished
4941                        | ValueType::Set(
4942                            None | Some(
4943                                SetElementType::Boolean
4944                                    | SetElementType::Integer
4945                                    | SetElementType::Float
4946                            )
4947                        )
4948                );
4949            if deferred_primitive {
4950                budget.charge(1, location.clone())?;
4951            } else {
4952                merge_set_types(
4953                    &mut element_type,
4954                    std::slice::from_ref(left),
4955                    budget,
4956                    location,
4957                )?;
4958            }
4959            match right {
4960                ValueType::Set(ty)
4961                    if ty.is_none() || element_type.is_none() || ty == &element_type =>
4962                {
4963                    Ok(ValueType::Boolean)
4964                }
4965                ValueType::Unestablished => Ok(ValueType::Boolean),
4966                _ => Err(error(
4967                    location,
4968                    "membership needs a compatible supported set",
4969                )),
4970            }
4971        }
4972        ValueBinaryOperator::Concatenate => match (left, right) {
4973            (ValueType::String, ValueType::String)
4974            | (
4975                ValueType::ScalarKind,
4976                ValueType::String | ValueType::ScalarKind | ValueType::Unestablished,
4977            )
4978            | (ValueType::String | ValueType::Unestablished, ValueType::ScalarKind) => {
4979                Ok(ValueType::String)
4980            }
4981            (ValueType::Array(a), ValueType::Array(b)) => {
4982                let (ArrayType::Positions(a_entries) | ArrayType::Elements(a_entries)) = a;
4983                let (ArrayType::Positions(b_entries) | ArrayType::Elements(b_entries)) = b;
4984                let mut entries = copy_types(a_entries, budget, location)?;
4985                append_types(
4986                    &mut entries,
4987                    copy_types(b_entries, budget, location)?,
4988                    budget,
4989                    location,
4990                )?;
4991                Ok(ValueType::Array(
4992                    if matches!((a, b), (ArrayType::Positions(_), ArrayType::Positions(_))) {
4993                        ArrayType::Positions(entries)
4994                    } else {
4995                        ArrayType::Elements(entries)
4996                    },
4997                ))
4998            }
4999            (ValueType::Unestablished, other) | (other, ValueType::Unestablished)
5000                if matches!(
5001                    other,
5002                    ValueType::Unestablished | ValueType::String | ValueType::Array(_)
5003                ) =>
5004            {
5005                Ok(ValueType::Unestablished)
5006            }
5007            _ => Err(error(
5008                location,
5009                "concatenation needs two strings or two positional arrays",
5010            )),
5011        },
5012    }
5013}
5014
5015pub(crate) fn boolean(value: ConstructedValue, location: &Location) -> Result<bool, QueryError> {
5016    if let ConstructedValue::Boolean(value) = value.into_data() {
5017        Ok(value)
5018    } else {
5019        Err(error(location, "condition needs a fixed Boolean"))
5020    }
5021}
5022
5023fn integer(value: ConstructedValue, location: &Location) -> Result<i64, QueryError> {
5024    if let ConstructedValue::Integer(value) = value.into_data() {
5025        Ok(value)
5026    } else {
5027        Err(error(location, "operation needs integers"))
5028    }
5029}
5030
5031fn scalar(
5032    value: ConstructedValue,
5033    budget: &mut Budget,
5034    location: &Location,
5035) -> Result<ConstructedValue, QueryError> {
5036    budget.charge_results(1, location.clone())?;
5037    Ok(value)
5038}
5039
5040fn exact_float(value: i64, location: &Location) -> Result<f64, QueryError> {
5041    let magnitude = value.unsigned_abs();
5042    let significant_bits = u64::BITS - magnitude.leading_zeros();
5043    if significant_bits > 53 && magnitude.trailing_zeros() < significant_bits - 53 {
5044        return Err(error(
5045            location,
5046            "integer cannot be represented exactly as binary64",
5047        ));
5048    }
5049    // The significand test establishes exact representation, including MIN;
5050    // a float-to-i64 cast would saturate and wrongly accept MAX.
5051    Ok(value as f64)
5052}
5053
5054fn numeric_float(value: ConstructedValue, location: &Location) -> Result<f64, QueryError> {
5055    match value.into_data() {
5056        ConstructedValue::Integer(value) => exact_float(value, location),
5057        ConstructedValue::Float(value) => {
5058            finite(value, location)?;
5059            Ok(value)
5060        }
5061        _ => Err(error(location, "operation needs numeric operands")),
5062    }
5063}
5064
5065fn unary(
5066    operator: ValueUnaryOperator,
5067    value: ConstructedValue,
5068    budget: &mut Budget,
5069    location: &Location,
5070) -> Result<ConstructedValue, QueryError> {
5071    let value = match (operator, value.into_data()) {
5072        (ValueUnaryOperator::Not, value) => ConstructedValue::Boolean(!boolean(value, location)?),
5073        (
5074            ValueUnaryOperator::Positive,
5075            value @ (ConstructedValue::Integer(_) | ConstructedValue::Float(_)),
5076        ) => value,
5077        (ValueUnaryOperator::Negative, ConstructedValue::Integer(value)) => {
5078            ConstructedValue::Integer(
5079                value
5080                    .checked_neg()
5081                    .ok_or_else(|| error(location, "integer overflow"))?,
5082            )
5083        }
5084        (ValueUnaryOperator::Negative, ConstructedValue::Float(value)) => {
5085            ConstructedValue::Float(-value)
5086        }
5087        _ => return Err(error(location, "operation needs numeric operands")),
5088    };
5089    scalar(value, budget, location)
5090}
5091
5092fn string_operand_error(budget: &mut Budget, location: &Location) -> QueryError {
5093    let message = "string matching needs two strings";
5094    match budget.charge(message.len(), location.clone()) {
5095        Ok(()) => error(location, message),
5096        Err(error) => error,
5097    }
5098}
5099
5100fn string_match_ref(
5101    operator: ValueBinaryOperator,
5102    left: &ConstructedValue,
5103    right: &ConstructedValue,
5104    budget: &mut Budget,
5105    location: &Location,
5106) -> Result<ConstructedValue, QueryError> {
5107    let (ConstructedValue::String(text), ConstructedValue::String(search)) =
5108        (left.data(), right.data())
5109    else {
5110        return Err(string_operand_error(budget, location));
5111    };
5112    // Admit a byte-scan bound before borrowing the existing text. No
5113    // substring or normalized text is allocated by these operations.
5114    let work = if search.is_empty() {
5115        0
5116    } else if operator == ValueBinaryOperator::Contains {
5117        sum(text.len(), search.len(), location)?
5118    } else {
5119        text.len().min(search.len())
5120    };
5121    budget.charge(work, location.clone())?;
5122    let matched = match operator {
5123        ValueBinaryOperator::Contains => text.contains(search.as_str()),
5124        ValueBinaryOperator::StartsWith => text.starts_with(search.as_str()),
5125        ValueBinaryOperator::EndsWith => text.ends_with(search.as_str()),
5126        _ => unreachable!("string matching operator"),
5127    };
5128    scalar(ConstructedValue::Boolean(matched), budget, location)
5129}
5130
5131fn binary(
5132    operator: ValueBinaryOperator,
5133    left: ConstructedValue,
5134    right: ConstructedValue,
5135    budget: &mut Budget,
5136    location: &Location,
5137) -> Result<ConstructedValue, QueryError> {
5138    let (left, right) = if matches!(
5139        operator,
5140        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual
5141    ) {
5142        (left, right)
5143    } else {
5144        (
5145            expose(left, budget, location)?,
5146            expose(right, budget, location)?,
5147        )
5148    };
5149    match operator {
5150        ValueBinaryOperator::Contains
5151        | ValueBinaryOperator::StartsWith
5152        | ValueBinaryOperator::EndsWith => {
5153            string_match_ref(operator, &left, &right, budget, location)
5154        }
5155        ValueBinaryOperator::Concatenate => match (left, right) {
5156            (ConstructedValue::String(left), ConstructedValue::String(right)) => {
5157                let len = sum(left.len(), right.len(), location)?;
5158                allocation::<u8>(len, location)?;
5159                budget.charge(len, location.clone())?;
5160                budget.charge_results(1, location.clone())?;
5161                let mut value = String::with_capacity(len);
5162                value.push_str(&left);
5163                value.push_str(&right);
5164                Ok(ConstructedValue::String(value))
5165            }
5166            (ConstructedValue::Array(left), ConstructedValue::Array(right)) => {
5167                let len = sum(left.len(), right.len(), location)?;
5168                let mut values = admit_container(len, budget, location)?;
5169                values.extend(left);
5170                values.extend(right);
5171                Ok(ConstructedValue::Array(values))
5172            }
5173            _ => Err(error(
5174                location,
5175                "concatenation needs two strings or two positional arrays",
5176            )),
5177        },
5178        ValueBinaryOperator::Equal | ValueBinaryOperator::NotEqual => {
5179            let equal = equal_values(&left, &right, budget, location)?;
5180            scalar(
5181                ConstructedValue::Boolean(if operator == ValueBinaryOperator::Equal {
5182                    equal
5183                } else {
5184                    !equal
5185                }),
5186                budget,
5187                location,
5188            )
5189        }
5190        ValueBinaryOperator::In => {
5191            let ConstructedValue::Set {
5192                element_type,
5193                elements,
5194            } = right
5195            else {
5196                return Err(error(location, "membership needs a supported set"));
5197            };
5198            let ty = scalar_set_type(&left)
5199                .ok_or_else(|| error(location, "membership needs a supported scalar"))?;
5200            if element_type.is_some_and(|element_type| element_type != ty) {
5201                return Err(error(location, "membership element type mismatch"));
5202            }
5203            let mut found = false;
5204            for value in &elements {
5205                if compare_set_values(&left, value, budget, location)? == Ordering::Equal {
5206                    found = true;
5207                    break;
5208                }
5209            }
5210            scalar(ConstructedValue::Boolean(found), budget, location)
5211        }
5212        ValueBinaryOperator::And | ValueBinaryOperator::Or => {
5213            let a = boolean(left, location)?;
5214            let b = boolean(right, location)?;
5215            scalar(
5216                ConstructedValue::Boolean(if operator == ValueBinaryOperator::And {
5217                    a && b
5218                } else {
5219                    a || b
5220                }),
5221                budget,
5222                location,
5223            )
5224        }
5225        ValueBinaryOperator::IntegerDivide | ValueBinaryOperator::Modulo => {
5226            let a = integer(left, location)?;
5227            let b = integer(right, location)?;
5228            if b == 0 {
5229                return Err(error(location, "division by zero"));
5230            }
5231            let value = if operator == ValueBinaryOperator::IntegerDivide {
5232                a.checked_div(b)
5233            } else {
5234                a.checked_rem(b)
5235            }
5236            .ok_or_else(|| error(location, "integer overflow"))?;
5237            scalar(ConstructedValue::Integer(value), budget, location)
5238        }
5239        ValueBinaryOperator::Add
5240        | ValueBinaryOperator::Subtract
5241        | ValueBinaryOperator::Multiply => {
5242            if let (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) = (&left, &right) {
5243                let value = match operator {
5244                    ValueBinaryOperator::Add => a.checked_add(*b),
5245                    ValueBinaryOperator::Subtract => a.checked_sub(*b),
5246                    ValueBinaryOperator::Multiply => a.checked_mul(*b),
5247                    _ => unreachable!("integer arithmetic"),
5248                }
5249                .ok_or_else(|| error(location, "integer overflow"))?;
5250                return scalar(ConstructedValue::Integer(value), budget, location);
5251            }
5252            float_operation(
5253                operator,
5254                numeric_float(left, location)?,
5255                numeric_float(right, location)?,
5256                budget,
5257                location,
5258            )
5259        }
5260        ValueBinaryOperator::Divide => float_operation(
5261            operator,
5262            numeric_float(left, location)?,
5263            numeric_float(right, location)?,
5264            budget,
5265            location,
5266        ),
5267        ValueBinaryOperator::Less
5268        | ValueBinaryOperator::LessEqual
5269        | ValueBinaryOperator::Greater
5270        | ValueBinaryOperator::GreaterEqual => {
5271            let order = if let (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) =
5272                (&left, &right)
5273            {
5274                a.cmp(b)
5275            } else {
5276                numeric_float(left, location)?
5277                    .partial_cmp(&numeric_float(right, location)?)
5278                    .expect("finite numeric operands")
5279            };
5280            let value = match operator {
5281                ValueBinaryOperator::Less => order == Ordering::Less,
5282                ValueBinaryOperator::LessEqual => order != Ordering::Greater,
5283                ValueBinaryOperator::Greater => order == Ordering::Greater,
5284                ValueBinaryOperator::GreaterEqual => order != Ordering::Less,
5285                _ => unreachable!("comparison"),
5286            };
5287            scalar(ConstructedValue::Boolean(value), budget, location)
5288        }
5289    }
5290}
5291
5292fn float_operation(
5293    operator: ValueBinaryOperator,
5294    a: f64,
5295    b: f64,
5296    budget: &mut Budget,
5297    location: &Location,
5298) -> Result<ConstructedValue, QueryError> {
5299    let (value, mathematically_zero) = match operator {
5300        ValueBinaryOperator::Add => (a + b, a == -b),
5301        ValueBinaryOperator::Subtract => (a - b, a == b),
5302        ValueBinaryOperator::Multiply => (a * b, a == 0.0 || b == 0.0),
5303        ValueBinaryOperator::Divide => {
5304            if b == 0.0 {
5305                return Err(error(location, "division by zero"));
5306            }
5307            (a / b, a == 0.0)
5308        }
5309        _ => unreachable!("floating arithmetic"),
5310    };
5311    finite(value, location)?;
5312    if value == 0.0 && !mathematically_zero {
5313        return Err(error(location, "nonzero floating result rounded to zero"));
5314    }
5315    scalar(ConstructedValue::Float(value), budget, location)
5316}
5317
5318fn equal_values(
5319    left: &ConstructedValue,
5320    right: &ConstructedValue,
5321    budget: &mut Budget,
5322    location: &Location,
5323) -> Result<bool, QueryError> {
5324    budget.within_evaluation(1, location.clone(), |budget| {
5325        budget.charge(1, location.clone())?;
5326        if declaration(left).is_some() || declaration(right).is_some() {
5327            let a = constructed_value_type(left, budget, location.clone())?;
5328            let b = constructed_value_type(right, budget, location.clone())?;
5329            if !compatible_types(&a, &b, budget, location)? {
5330                return Err(error(
5331                    location,
5332                    "equality needs compatible underlying types",
5333                ));
5334            }
5335        }
5336        Ok(match (left.data(), right.data()) {
5337            (ConstructedValue::Absent, ConstructedValue::Absent) => true,
5338            (ConstructedValue::Absent, _) | (_, ConstructedValue::Absent) => false,
5339            (ConstructedValue::Boolean(a), ConstructedValue::Boolean(b)) => a == b,
5340            (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => a == b,
5341            (ConstructedValue::Float(a), ConstructedValue::Float(b)) => a == b,
5342            (ConstructedValue::String(a), ConstructedValue::String(b)) => {
5343                budget.charge(a.len().min(b.len()), location.clone())?;
5344                a == b
5345            }
5346            (ConstructedValue::Member(a), ConstructedValue::Member(b)) => {
5347                check_member_pair(a, b, budget, location)?;
5348                if a.type_id != b.type_id {
5349                    return Err(error(location, "equality needs compatible nominal types"));
5350                }
5351                a.member_id == b.member_id
5352            }
5353            (
5354                ConstructedValue::KeyedArray {
5355                    axes: a,
5356                    entries: a_entries,
5357                },
5358                ConstructedValue::KeyedArray {
5359                    axes: b,
5360                    entries: b_entries,
5361                },
5362            ) => {
5363                if a.len() != b.len() || a_entries.len() != b_entries.len() {
5364                    return Ok(false);
5365                }
5366                for (a, b) in a.iter().zip(b) {
5367                    budget.charge(1, location.clone())?;
5368                    if a.key_type != b.key_type {
5369                        return Err(error(location, "equality needs compatible keyed types"));
5370                    }
5371                    if !equal_keys(&a.keys, &b.keys, budget, location)? {
5372                        return Ok(false);
5373                    }
5374                }
5375                for (a, b) in a_entries.iter().zip(b_entries) {
5376                    budget.charge(1, location.clone())?;
5377                    if !equal_keys(&a.key, &b.key, budget, location)?
5378                        || !equal_values(&a.value, &b.value, budget, location)?
5379                    {
5380                        return Ok(false);
5381                    }
5382                }
5383                true
5384            }
5385            (ConstructedValue::Array(a), ConstructedValue::Array(b))
5386            | (ConstructedValue::Tuple(a), ConstructedValue::Tuple(b)) => {
5387                if a.len() != b.len() {
5388                    return Ok(false);
5389                }
5390                for (a, b) in a.iter().zip(b) {
5391                    if !equal_values(a, b, budget, location)? {
5392                        return Ok(false);
5393                    }
5394                }
5395                true
5396            }
5397            (
5398                ConstructedValue::Set {
5399                    element_type: a_type,
5400                    elements: a,
5401                },
5402                ConstructedValue::Set {
5403                    element_type: b_type,
5404                    elements: b,
5405                },
5406            ) => {
5407                if matches!((a_type, b_type), (Some(a), Some(b)) if a != b) {
5408                    return Err(error(location, "equality needs compatible set types"));
5409                }
5410                if a.len() != b.len() {
5411                    return Ok(false);
5412                }
5413                for value in a {
5414                    let mut found = false;
5415                    for other in b {
5416                        if equal_values(value, other, budget, location)? {
5417                            found = true;
5418                            break;
5419                        }
5420                    }
5421                    if !found {
5422                        return Ok(false);
5423                    }
5424                }
5425                true
5426            }
5427            (ConstructedValue::Record(a), ConstructedValue::Record(b)) => {
5428                if a.len() != b.len() {
5429                    return Ok(false);
5430                }
5431                for (label, value) in a {
5432                    let mut other = None;
5433                    for (other_label, other_value) in b {
5434                        budget.charge(
5435                            sum(1, label.len().min(other_label.len()), location)?,
5436                            location.clone(),
5437                        )?;
5438                        if label == other_label {
5439                            other = Some(other_value);
5440                            break;
5441                        }
5442                    }
5443                    let Some(other) = other else {
5444                        return Ok(false);
5445                    };
5446                    if !equal_values(value, other, budget, location)? {
5447                        return Ok(false);
5448                    }
5449                }
5450                true
5451            }
5452            _ => return Err(error(location, "equality needs compatible types")),
5453        })
5454    })
5455}
5456
5457fn run<'a, Q, H: ValueQueryHost<Q> + ?Sized>(
5458    expression: &'a PreparedValue<'a, Q>,
5459    queries: &mut H,
5460    locals: &mut Frame<'a>,
5461    budget: &mut Budget,
5462    check_value: &mut impl FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5463) -> Result<ConstructedValue, H::Error> {
5464    let value = run_value(expression, queries, locals, budget, check_value)?;
5465    let location = Location::Query(expression.range.clone());
5466    budget.within_evaluation(1, location.clone(), |budget| {
5467        Ok(value.materialize(budget, &location)?)
5468    })
5469}
5470
5471struct PreparedArguments<'a, 'f, Q, F> {
5472    arguments: &'a [PreparedArgument<'a, Q>],
5473    frames: Vec<Frame<'a>>,
5474    check_value: &'f mut F,
5475}
5476impl<Q, E, F> ValueArguments<Q, E> for PreparedArguments<'_, '_, Q, F>
5477where
5478    E: From<QueryError>,
5479    F: FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5480{
5481    fn evaluate(
5482        &mut self,
5483        expression: &ValueExpression<Q>,
5484        queries: &mut dyn ValueQueryHost<Q, Error = E>,
5485        budget: &mut Budget,
5486    ) -> Result<ConstructedRead, E> {
5487        let index = self
5488            .arguments
5489            .iter()
5490            .position(|argument| std::ptr::eq(argument.source, expression))
5491            .ok_or_else(|| {
5492                QueryError::query(
5493                    expression.range.clone(),
5494                    "argument was not prepared in this invocation",
5495                )
5496            })?;
5497        let value = run_value(
5498            &self.arguments[index].expression,
5499            queries,
5500            &mut self.frames[index],
5501            budget,
5502            self.check_value,
5503        )?;
5504        Ok(ConstructedRead {
5505            value,
5506            location: Location::Query(expression.range.clone()),
5507            declaration: None,
5508        })
5509    }
5510}
5511
5512fn run_value<'a, Q, H: ValueQueryHost<Q> + ?Sized>(
5513    expression: &'a PreparedValue<'a, Q>,
5514    queries: &mut H,
5515    locals: &mut Frame<'a>,
5516    budget: &mut Budget,
5517    check_value: &mut impl FnMut(&ConstructedValue, &mut Budget, &Location) -> Result<(), QueryError>,
5518) -> Result<RuntimeValue, H::Error> {
5519    execution::run_scalar(expression, queries, locals, budget, check_value)
5520}
5521
5522fn entries(
5523    array: ConstructedValue,
5524    budget: &mut Budget,
5525    location: &Location,
5526) -> Result<ConstructedValue, QueryError> {
5527    let array = expose(array, budget, location)?;
5528    let ConstructedValue::KeyedArray { axes, entries } = array else {
5529        return Err(error(location, "entries needs a keyed array"));
5530    };
5531    check_keyed(&axes, &entries, budget, location)?;
5532    let mut records = admit_container(entries.len(), budget, location)?;
5533    for entry in entries {
5534        // The outer array and record add two levels around a moved cell.
5535        budget.within_evaluation(2, location.clone(), |budget| {
5536            copy_size(&entry.value, budget, location).map(|_| ())
5537        })?;
5538        let key = if entry.key.len() == 1 {
5539            budget.charge(1, location.clone())?;
5540            key_value(entry.key.into_iter().next().expect("rank one"))
5541        } else {
5542            let mut components = admit_container(entry.key.len(), budget, location)?;
5543            for key in entry.key {
5544                budget.charge(1, location.clone())?;
5545                components.push(key_value(key));
5546            }
5547            ConstructedValue::Tuple(components)
5548        };
5549        budget.within_evaluation(2, location.clone(), |budget| {
5550            copy_size(&key, budget, location).map(|_| ())
5551        })?;
5552        let mut fields = admit_container(2, budget, location)?;
5553        budget.charge("key".len() + "value".len(), location.clone())?;
5554        fields.push(("key".into(), key));
5555        fields.push(("value".into(), entry.value));
5556        records.push(ConstructedValue::Record(fields));
5557    }
5558    Ok(ConstructedValue::Array(records))
5559}
5560
5561fn zip_exact(
5562    left: ConstructedValue,
5563    right: ConstructedValue,
5564    budget: &mut Budget,
5565    location: &Location,
5566) -> Result<ConstructedValue, QueryError> {
5567    let left = expose(left, budget, location)?;
5568    let right = expose(right, budget, location)?;
5569    let (
5570        ConstructedValue::KeyedArray {
5571            axes,
5572            entries: left,
5573        },
5574        ConstructedValue::KeyedArray {
5575            axes: right_axes,
5576            entries: right,
5577        },
5578    ) = (left, right)
5579    else {
5580        return Err(error(location, "zip_exact needs keyed arrays"));
5581    };
5582    check_keyed(&axes, &left, budget, location)?;
5583    check_keyed(&right_axes, &right, budget, location)?;
5584    budget.charge(1, location.clone())?;
5585    if axes.len() != right_axes.len() {
5586        return Err(error(location, "zip_exact needs identical ordered axes"));
5587    }
5588    for (a, b) in axes.iter().zip(&right_axes) {
5589        budget.charge(1, location.clone())?;
5590        if a.key_type != b.key_type || !equal_keys(&a.keys, &b.keys, budget, location)? {
5591            return Err(error(location, "zip_exact needs identical ordered axes"));
5592        }
5593    }
5594    budget.charge(1, location.clone())?;
5595    if left.len() != right.len() {
5596        return Err(error(location, "zip_exact needs identical present keys"));
5597    }
5598    let mut entries = admit_container(left.len(), budget, location)?;
5599    for (a, b) in left.into_iter().zip(right) {
5600        if !equal_keys(&a.key, &b.key, budget, location)? {
5601            return Err(error(location, "zip_exact needs identical present keys"));
5602        }
5603        // The keyed container and tuple add two levels around moved values.
5604        budget.within_evaluation(2, location.clone(), |budget| {
5605            copy_size(&a.value, budget, location)?;
5606            copy_size(&b.value, budget, location).map(|_| ())
5607        })?;
5608        let mut pair = admit_container(2, budget, location)?;
5609        pair.push(a.value);
5610        pair.push(b.value);
5611        entries.push(KeyedEntry {
5612            key: a.key,
5613            value: ConstructedValue::Tuple(pair),
5614        });
5615    }
5616    Ok(ConstructedValue::KeyedArray { axes, entries })
5617}
5618
5619fn group_by(
5620    rows: ConstructedValue,
5621    field: &Located<String>,
5622    budget: &mut Budget,
5623    location: &Location,
5624) -> Result<ConstructedValue, QueryError> {
5625    let rows = expose(rows, budget, location)?;
5626    let ConstructedValue::Array(rows) = rows else {
5627        return Err(error(location, "group_by needs a positional array"));
5628    };
5629    let selector = Location::Query(field.range.clone());
5630    let mut groups = admit_container(0, budget, location)?;
5631    let mut key_type = None;
5632    for row in rows {
5633        budget.charge(1, location.clone())?;
5634        let ConstructedValue::Record(fields) = row.data() else {
5635            return Err(error(location, "group_by needs record rows"));
5636        };
5637        // Moving rows adds a group record and items array around them. Check
5638        // that added depth without copying or charging their result units again.
5639        budget.within_evaluation(2, location.clone(), |budget| {
5640            copy_size(&row, budget, location).map(|_| ())
5641        })?;
5642        let mut key = None;
5643        for (label, value) in fields {
5644            budget.charge(
5645                sum(1, label.len().min(field.value.len()), &selector)?,
5646                selector.clone(),
5647            )?;
5648            if label == &field.value {
5649                key = Some(value);
5650                break;
5651            }
5652        }
5653        let Some(key) = key else {
5654            admit_diagnostic("missing record field: ", &field.value, budget, &selector)?;
5655            return Err(error(
5656                &selector,
5657                format!("missing record field: {}", field.value),
5658            ));
5659        };
5660        let ty = match key.data() {
5661            ConstructedValue::Integer(_) => ValueType::Integer,
5662            ConstructedValue::String(_) => ValueType::String,
5663            ConstructedValue::Member(member) => ValueType::Member(member.type_id),
5664            _ => {
5665                return Err(error(
5666                    &selector,
5667                    "group_by keys need integers, strings or nominal members",
5668                ));
5669            }
5670        };
5671        budget.charge(1, selector.clone())?;
5672        if key_type.as_ref().is_some_and(|previous| previous != &ty) {
5673            return Err(error(&selector, "group_by needs homogeneous key types"));
5674        }
5675        key_type = Some(ty);
5676        let mut matched = None;
5677        for (index, group) in groups.iter().enumerate() {
5678            let ConstructedValue::Record(fields) = group else {
5679                unreachable!("group record")
5680            };
5681            if equal_values(key, &fields[0].1, budget, &selector)? {
5682                matched = Some(index);
5683                break;
5684            }
5685        }
5686        let index = match matched {
5687            Some(index) => index,
5688            None => {
5689                let key = copy_constructed_value(key, budget, selector.clone())?;
5690                let mut fields = admit_container(2, budget, location)?;
5691                budget.charge("key".len() + "items".len(), location.clone())?;
5692                fields.push(("key".into(), key));
5693                fields.push((
5694                    "items".into(),
5695                    ConstructedValue::Array(admit_container(0, budget, location)?),
5696                ));
5697                allocation::<ConstructedValue>(sum(groups.len(), 1, location)?, location)?;
5698                budget.charge(1, location.clone())?;
5699                budget.charge_results(1, location.clone())?;
5700                groups.push(ConstructedValue::Record(fields));
5701                groups.len() - 1
5702            }
5703        };
5704        let ConstructedValue::Record(fields) = &mut groups[index] else {
5705            unreachable!("group record")
5706        };
5707        let ConstructedValue::Array(items) = &mut fields[1].1 else {
5708            unreachable!("group items")
5709        };
5710        allocation::<ConstructedValue>(sum(items.len(), 1, location)?, location)?;
5711        budget.charge(1, location.clone())?;
5712        budget.charge_results(1, location.clone())?;
5713        items.push(row);
5714    }
5715    Ok(ConstructedValue::Array(groups))
5716}
5717
5718// Filtering loses positions while preserving established element facts.
5719fn filter_elements(
5720    ty: ValueType,
5721    budget: &mut Budget,
5722    location: &Location,
5723) -> Result<Vec<ValueType>, QueryError> {
5724    match shape_type(ty, budget, location)? {
5725        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5726        | ValueType::ArrayKind(elements) => Ok(elements),
5727        ValueType::Unestablished => one_type(ValueType::Unestablished, budget, location),
5728        _ => Err(error(location, "filter needs a positional array")),
5729    }
5730}
5731
5732// Removal loses positional facts, but retains admitted element kinds.
5733fn scalar_pipeline_elements(
5734    ty: ValueType,
5735    tally: bool,
5736    budget: &mut Budget,
5737    location: &Location,
5738) -> Result<Vec<ValueType>, QueryError> {
5739    let elements = match shape_type(ty, budget, location)? {
5740        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5741        | ValueType::ArrayKind(elements) => elements,
5742        ValueType::Unestablished => return one_type(ValueType::Unestablished, budget, location),
5743        _ => return Err(error(location, "collection stage needs a positional array")),
5744    };
5745    let mut element_type = None;
5746    for element in &elements {
5747        budget.charge(1, location.clone())?;
5748        let ty = shape_type(copy_type(element, budget, location)?, budget, location)?;
5749        if matches!(ty, ValueType::Unestablished) {
5750            continue;
5751        }
5752        if tally {
5753            if ty != ValueType::String {
5754                return Err(error(location, "tally needs strings"));
5755            }
5756        } else if !matches!(
5757            ty,
5758            ValueType::Boolean
5759                | ValueType::Integer
5760                | ValueType::Float
5761                | ValueType::String
5762                | ValueType::Member(_)
5763        ) {
5764            return Err(error(location, "unique needs homogeneous scalar elements"));
5765        }
5766        if element_type
5767            .as_ref()
5768            .is_some_and(|previous| previous != &ty)
5769        {
5770            return Err(error(location, "unique needs homogeneous scalar elements"));
5771        }
5772        element_type = Some(ty);
5773    }
5774    Ok(elements)
5775}
5776
5777fn unique_value(
5778    mut array: ConstructedValue,
5779    budget: &mut Budget,
5780    location: &Location,
5781) -> Result<ConstructedValue, QueryError> {
5782    if declaration(&array).is_some() {
5783        scalar_pipeline_elements(
5784            constructed_value_type(&array, budget, location.clone())?,
5785            false,
5786            budget,
5787            location,
5788        )?;
5789    }
5790    let mut data = &mut array;
5791    while let ConstructedValue::Declared { value, .. } = data {
5792        data = value;
5793    }
5794    let ConstructedValue::Array(rows) = data else {
5795        return Err(error(location, "unique needs a positional array"));
5796    };
5797    let mut element_type = None;
5798    let mut retained = 0;
5799    for index in 0..rows.len() {
5800        budget.charge(1, location.clone())?;
5801        let ty = match rows[index].data() {
5802            ConstructedValue::Boolean(_) => ValueType::Boolean,
5803            ConstructedValue::Integer(_) => ValueType::Integer,
5804            ConstructedValue::Float(value) => {
5805                finite(*value, location)?;
5806                ValueType::Float
5807            }
5808            ConstructedValue::String(_) => ValueType::String,
5809            ConstructedValue::Member(member) => {
5810                member_bytes(member, budget, location)?;
5811                ValueType::Member(member.type_id)
5812            }
5813            _ => return Err(error(location, "unique needs homogeneous scalar elements")),
5814        };
5815        if element_type
5816            .as_ref()
5817            .is_some_and(|previous| previous != &ty)
5818        {
5819            return Err(error(location, "unique needs homogeneous scalar elements"));
5820        }
5821        element_type = Some(ty);
5822        let mut duplicate = false;
5823        for previous in &rows[..retained] {
5824            if equal_values(previous, &rows[index], budget, location)? {
5825                duplicate = true;
5826                break;
5827            }
5828        }
5829        if !duplicate {
5830            rows.swap(retained, index);
5831            retained += 1;
5832        }
5833    }
5834    rows.truncate(retained);
5835    Ok(array)
5836}
5837
5838fn tally_value(
5839    array: ConstructedValue,
5840    budget: &mut Budget,
5841    location: &Location,
5842) -> Result<ConstructedValue, QueryError> {
5843    if declaration(&array).is_some() {
5844        scalar_pipeline_elements(
5845            constructed_value_type(&array, budget, location.clone())?,
5846            true,
5847            budget,
5848            location,
5849        )?;
5850    }
5851    let ConstructedValue::Array(rows) = array.data() else {
5852        return Err(error(location, "tally needs a positional array"));
5853    };
5854    for row in rows {
5855        budget.charge(1, location.clone())?;
5856        if !matches!(row.data(), ConstructedValue::String(_)) {
5857            return Err(error(location, "tally needs strings"));
5858        }
5859    }
5860    let ConstructedValue::Array(rows) = expose(array, budget, location)? else {
5861        unreachable!("checked positional array")
5862    };
5863    let mut fields: Vec<(String, ConstructedValue)> = metadata_vec(rows.len(), budget, location)?;
5864    budget.charge(1, location.clone())?;
5865    budget.charge_results(1, location.clone())?;
5866    for row in rows {
5867        budget.charge(1, location.clone())?;
5868        let ConstructedValue::String(label) = expose(row, budget, location)? else {
5869            return Err(error(location, "tally needs strings"));
5870        };
5871        let mut existing = None;
5872        for (previous, count) in &mut fields {
5873            budget.charge(
5874                sum(1, previous.len().min(label.len()), location)?,
5875                location.clone(),
5876            )?;
5877            if *previous == label {
5878                existing = Some(count);
5879                break;
5880            }
5881        }
5882        if let Some(ConstructedValue::Integer(count)) = existing {
5883            *count = count
5884                .checked_add(1)
5885                .ok_or_else(|| error(location, "tally count overflow"))?;
5886            continue;
5887        }
5888        budget.charge(2, location.clone())?;
5889        budget.charge_results(2, location.clone())?;
5890        fields.push((label, ConstructedValue::Integer(1)));
5891    }
5892    Ok(ConstructedValue::Record(fields))
5893}
5894
5895fn sort_type(
5896    ty: ValueType,
5897    selector: Option<&Located<SortSelector>>,
5898    budget: &mut Budget,
5899    location: &Location,
5900) -> Result<ValueType, QueryError> {
5901    let retained = match &ty {
5902        ValueType::Declared { declaration, .. } => {
5903            Some(copy_declaration(declaration, budget, location)?)
5904        }
5905        _ => None,
5906    };
5907    let selected = selector.map_or_else(
5908        || location.clone(),
5909        |selector| Location::Query(selector.range.clone()),
5910    );
5911    let field = match selector {
5912        Some(Located {
5913            value: SortSelector::Field(field),
5914            range,
5915        }) => Some(Located::new(field.as_str(), range.clone())),
5916        _ => None,
5917    };
5918    let ty = match shape_type(ty, budget, location)? {
5919        ty @ (ValueType::Array(_) | ValueType::ArrayKind(_)) => {
5920            if matches!(
5921                selector.map(|selector| &selector.value),
5922                Some(SortSelector::Value)
5923            ) {
5924                return Err(error(&selected, "value ordering needs a dictionary"));
5925            }
5926            ValueType::Array(ArrayType::Elements(ordering_elements(
5927                ty, field, budget, location,
5928            )?))
5929        }
5930        ty @ ValueType::Set(_) => {
5931            if selector.is_some() {
5932                return Err(error(&selected, "set sorting uses its scalar values"));
5933            }
5934            ty
5935        }
5936        ValueType::Record(fields) => {
5937            if selector.is_some() {
5938                check_ordering_types(
5939                    fields.iter().map(|(_, value)| value),
5940                    field,
5941                    budget,
5942                    &selected,
5943                )?;
5944            }
5945            ValueType::Record(fields)
5946        }
5947        ty @ (ValueType::RecordKind | ValueType::Unestablished) => ty,
5948        _ => return Err(error(location, "sort needs an array, set or dictionary")),
5949    };
5950    Ok(if let Some(declaration) = retained {
5951        ValueType::Declared {
5952            actual: Box::new(ty),
5953            declaration,
5954        }
5955    } else {
5956        ty
5957    })
5958}
5959
5960// Ordering retains row facts but cannot retain their original positions.
5961fn ordering_elements(
5962    ty: ValueType,
5963    field: Option<Located<&str>>,
5964    budget: &mut Budget,
5965    location: &Location,
5966) -> Result<Vec<ValueType>, QueryError> {
5967    let elements = match shape_type(ty, budget, location)? {
5968        ValueType::Array(ArrayType::Positions(elements) | ArrayType::Elements(elements))
5969        | ValueType::ArrayKind(elements) => elements,
5970        ValueType::Unestablished => return one_type(ValueType::Unestablished, budget, location),
5971        _ => return Err(error(location, "ordering needs a positional array")),
5972    };
5973    let selector = field.as_ref().map_or_else(
5974        || location.clone(),
5975        |field| Location::Query(field.range.clone()),
5976    );
5977    check_ordering_types(elements.iter(), field, budget, &selector)?;
5978    Ok(elements)
5979}
5980
5981fn check_ordering_types<'a>(
5982    elements: impl IntoIterator<Item = &'a ValueType>,
5983    field: Option<Located<&str>>,
5984    budget: &mut Budget,
5985    selector: &Location,
5986) -> Result<(), QueryError> {
5987    let mut key_type = None;
5988    for element in elements {
5989        budget.charge(1, selector.clone())?;
5990        let ty = shape_type(copy_type(element, budget, selector)?, budget, selector)?;
5991        let ty = if let Some(field) = &field {
5992            match ty {
5993                ValueType::Record(fields) => {
5994                    let mut selected = ValueType::Unestablished;
5995                    for (label, ty) in fields {
5996                        budget.charge(
5997                            sum(1, label.len().min(field.value.len()), selector)?,
5998                            selector.clone(),
5999                        )?;
6000                        if label == field.value {
6001                            selected = ty;
6002                            break;
6003                        }
6004                    }
6005                    selected
6006                }
6007                ValueType::RecordKind | ValueType::Unestablished | ValueType::Absent => continue,
6008                _ => return Err(error(selector, "field ordering needs record rows")),
6009            }
6010        } else {
6011            ty
6012        };
6013        let ty = shape_type(ty, budget, selector)?;
6014        match ty {
6015            // Presence and missing data remain runtime checks, including in
6016            // skipped branches. An optional declaration still supplies its kind.
6017            ValueType::Absent | ValueType::Unestablished => continue,
6018            ValueType::Integer | ValueType::Float | ValueType::String | ValueType::Member(_) => {}
6019            _ => {
6020                return Err(error(
6021                    selector,
6022                    "ordering keys need integers, finite floats, strings or nominal members",
6023                ));
6024            }
6025        }
6026        if key_type.as_ref().is_some_and(|previous| previous != &ty) {
6027            return Err(error(selector, "ordering needs homogeneous key types"));
6028        }
6029        key_type = Some(ty);
6030    }
6031    Ok(())
6032}
6033
6034#[derive(Clone, Copy, PartialEq, Eq)]
6035pub(crate) enum OrderingKeyType {
6036    Integer,
6037    Float,
6038    String,
6039    Member(NominalTypeId),
6040}
6041
6042pub(crate) fn ordering_key<'a>(
6043    row: &'a ConstructedValue,
6044    field: Option<&str>,
6045    budget: &mut Budget,
6046    location: &Location,
6047) -> Result<(&'a ConstructedValue, OrderingKeyType), QueryError> {
6048    budget.charge(1, location.clone())?;
6049    let key = if let Some(field) = field {
6050        let ConstructedValue::Record(fields) = row.data() else {
6051            return Err(error(location, "field ordering needs record rows"));
6052        };
6053        let mut key = None;
6054        for (label, value) in fields {
6055            budget.charge(
6056                sum(1, label.len().min(field.len()), location)?,
6057                location.clone(),
6058            )?;
6059            if label == field {
6060                key = Some(value);
6061                break;
6062            }
6063        }
6064        key.ok_or_else(|| error(location, "missing ordering field"))?
6065    } else {
6066        row
6067    };
6068    let key = key.data();
6069    let ty = match key {
6070        ConstructedValue::Integer(_) => OrderingKeyType::Integer,
6071        ConstructedValue::Float(value) => {
6072            finite(*value, location)?;
6073            OrderingKeyType::Float
6074        }
6075        ConstructedValue::String(_) => OrderingKeyType::String,
6076        ConstructedValue::Member(member) => {
6077            member_bytes(member, budget, location)?;
6078            OrderingKeyType::Member(member.type_id)
6079        }
6080        ConstructedValue::Absent => return Err(error(location, "ordering keys must be present")),
6081        _ => {
6082            return Err(error(
6083                location,
6084                "ordering keys need integers, finite floats, strings or nominal members",
6085            ));
6086        }
6087    };
6088    Ok((key, ty))
6089}
6090
6091pub(crate) fn compare_ordering_keys(
6092    left: &ConstructedValue,
6093    right: &ConstructedValue,
6094    budget: &mut Budget,
6095    location: &Location,
6096) -> Result<Ordering, QueryError> {
6097    budget.charge(1, location.clone())?;
6098    match (left, right) {
6099        (ConstructedValue::Integer(a), ConstructedValue::Integer(b)) => Ok(a.cmp(b)),
6100        (ConstructedValue::Float(a), ConstructedValue::Float(b)) => a
6101            .partial_cmp(b)
6102            .ok_or_else(|| error(location, "nonfinite float")),
6103        (ConstructedValue::String(a), ConstructedValue::String(b)) => {
6104            for (a, b) in a.bytes().zip(b.bytes()) {
6105                budget.charge(1, location.clone())?;
6106                let order = a.cmp(&b);
6107                if order != Ordering::Equal {
6108                    return Ok(order);
6109                }
6110            }
6111            Ok(a.len().cmp(&b.len()))
6112        }
6113        (ConstructedValue::Member(a), ConstructedValue::Member(b)) if a.type_id == b.type_id => {
6114            check_member_pair(a, b, budget, location)?;
6115            Ok(a.declaration_order.cmp(&b.declaration_order))
6116        }
6117        _ => Err(error(location, "ordering needs homogeneous key types")),
6118    }
6119}
6120
6121#[derive(Clone, Copy)]
6122enum SortKey<'a> {
6123    Value(&'a ConstructedValue),
6124    Label(&'a str),
6125}
6126
6127#[derive(Clone, Copy)]
6128struct OrderingRow<'a> {
6129    index: usize,
6130    key: SortKey<'a>,
6131}
6132
6133fn sort_value(
6134    mut collection: ConstructedValue,
6135    selector: Option<&Located<SortSelector>>,
6136    descending: bool,
6137    budget: &mut Budget,
6138    location: &Location,
6139) -> Result<ConstructedValue, QueryError> {
6140    if declaration(&collection).is_some() {
6141        sort_type(
6142            constructed_value_type(&collection, budget, location.clone())?,
6143            selector,
6144            budget,
6145            location,
6146        )?;
6147    }
6148    let selected = selector.map_or_else(
6149        || location.clone(),
6150        |selector| Location::Query(selector.range.clone()),
6151    );
6152    let field = match selector.map(|selector| &selector.value) {
6153        Some(SortSelector::Field(field)) => Some(field.as_str()),
6154        _ => None,
6155    };
6156    let data = collection.data();
6157    let count = match data {
6158        ConstructedValue::Array(rows) => {
6159            if matches!(
6160                selector.map(|selector| &selector.value),
6161                Some(SortSelector::Value)
6162            ) {
6163                return Err(error(&selected, "value ordering needs a dictionary"));
6164            }
6165            rows.len()
6166        }
6167        ConstructedValue::Set { elements, .. } => {
6168            if selector.is_some() {
6169                return Err(error(&selected, "set sorting uses its scalar values"));
6170            }
6171            elements.len()
6172        }
6173        ConstructedValue::Record(fields) => fields.len(),
6174        _ => return Err(error(location, "sort needs an array, set or dictionary")),
6175    };
6176    let mut order: Vec<OrderingRow<'_>> = metadata_vec(count, budget, location)?;
6177    let mut key_type = None;
6178    for index in 0..count {
6179        let key = match data {
6180            ConstructedValue::Set { elements, .. } => {
6181                budget.charge(1, selected.clone())?;
6182                SortKey::Value(elements[index].data())
6183            }
6184            ConstructedValue::Record(fields) if selector.is_none() => {
6185                budget.charge(1, selected.clone())?;
6186                SortKey::Label(&fields[index].0)
6187            }
6188            _ => {
6189                let row = match data {
6190                    ConstructedValue::Array(rows) => &rows[index],
6191                    ConstructedValue::Record(fields) => &fields[index].1,
6192                    _ => unreachable!("checked collection kind"),
6193                };
6194                let (key, ty) = ordering_key(row, field, budget, &selected)?;
6195                if key_type.is_some_and(|previous| previous != ty) {
6196                    return Err(error(&selected, "ordering needs homogeneous key types"));
6197                }
6198                if let (Some(first), ConstructedValue::Member(member)) = (order.first(), key)
6199                    && let SortKey::Value(ConstructedValue::Member(first)) = first.key
6200                {
6201                    check_member_pair(first, member, budget, &selected)?;
6202                }
6203                key_type = Some(ty);
6204                SortKey::Value(key)
6205            }
6206        };
6207        order.push(OrderingRow { index, key });
6208    }
6209    let mut scratch = metadata_vec(order.len(), budget, location)?;
6210    scratch.extend_from_slice(&order);
6211    let mut width = 1;
6212    while width < order.len() {
6213        // Admit one descriptor write per output slot before each merge pass.
6214        budget.charge(order.len(), location.clone())?;
6215        let mut start = 0;
6216        while start < order.len() {
6217            let middle = start.saturating_add(width).min(order.len());
6218            let end = middle.saturating_add(width).min(order.len());
6219            let (mut left, mut right) = (start, middle);
6220            for slot in &mut scratch[start..end] {
6221                let take_left = if right == end {
6222                    true
6223                } else if left == middle {
6224                    false
6225                } else {
6226                    let order = match (order[left].key, order[right].key) {
6227                        (SortKey::Label(left), SortKey::Label(right)) => {
6228                            crate::ordering::compare_text(left, right, budget, &selected)?
6229                        }
6230                        (SortKey::Value(left), SortKey::Value(right))
6231                            if matches!(data, ConstructedValue::Set { .. }) =>
6232                        {
6233                            compare_set_values(left, right, budget, &selected)?
6234                        }
6235                        (SortKey::Value(left), SortKey::Value(right)) => {
6236                            compare_ordering_keys(left, right, budget, &selected)?
6237                        }
6238                        _ => unreachable!("one collection key kind"),
6239                    };
6240                    if descending {
6241                        order != Ordering::Less
6242                    } else {
6243                        order != Ordering::Greater
6244                    }
6245                };
6246                *slot = if take_left {
6247                    let row = order[left];
6248                    left += 1;
6249                    row
6250                } else {
6251                    let row = order[right];
6252                    right += 1;
6253                    row
6254                };
6255            }
6256            start = end;
6257        }
6258        std::mem::swap(&mut order, &mut scratch);
6259        width = width.saturating_mul(2);
6260    }
6261    let mut destinations = metadata_vec(order.len(), budget, location)?;
6262    destinations.resize(order.len(), 0);
6263    for (destination, row) in order.iter().enumerate() {
6264        budget.charge(1, location.clone())?;
6265        destinations[row.index] = destination;
6266    }
6267    // End the key borrows before moving payloads. Declarations and stored field
6268    // order move with their original rows; transparent collection annotations survive.
6269    drop(order);
6270    drop(scratch);
6271    let mut data = &mut collection;
6272    while let ConstructedValue::Declared { value, .. } = data {
6273        data = value;
6274    }
6275    for index in 0..count {
6276        while destinations[index] != index {
6277            budget.charge(1, location.clone())?;
6278            let destination = destinations[index];
6279            match data {
6280                ConstructedValue::Array(rows) | ConstructedValue::Set { elements: rows, .. } => {
6281                    rows.swap(index, destination)
6282                }
6283                ConstructedValue::Record(fields) => fields.swap(index, destination),
6284                _ => unreachable!("checked collection kind"),
6285            }
6286            destinations.swap(index, destination);
6287        }
6288    }
6289    Ok(collection)
6290}
6291
6292fn extremum_position(
6293    operator: ValueExtremum,
6294    array: &ConstructedValue,
6295    field: Option<&Located<String>>,
6296    budget: &mut Budget,
6297    location: &Location,
6298) -> Result<Option<usize>, QueryError> {
6299    let ConstructedValue::Array(rows) = array.data() else {
6300        return Err(error(location, "ordering needs a positional array"));
6301    };
6302    let selector = field.map_or_else(
6303        || location.clone(),
6304        |field| Location::Query(field.range.clone()),
6305    );
6306    let mut selected: Option<(usize, &ConstructedValue)> = None;
6307    let mut key_type = None;
6308    for (index, row) in rows.iter().enumerate() {
6309        let (key, ty) = ordering_key(
6310            row,
6311            field.map(|field| field.value.as_str()),
6312            budget,
6313            &selector,
6314        )?;
6315        if key_type.is_some_and(|previous| previous != ty) {
6316            return Err(error(&selector, "ordering needs homogeneous key types"));
6317        }
6318        key_type = Some(ty);
6319        let replace = match selected {
6320            None => true,
6321            Some((_, previous)) => {
6322                let order = compare_ordering_keys(key, previous, budget, &selector)?;
6323                order
6324                    == if operator == ValueExtremum::Min {
6325                        Ordering::Less
6326                    } else {
6327                        Ordering::Greater
6328                    }
6329            }
6330        };
6331        if replace {
6332            selected = Some((index, key));
6333        }
6334    }
6335    Ok(selected.map(|(index, _)| index))
6336}
6337
6338fn reduce_ref(
6339    operator: ValueReducer,
6340    array: &ConstructedValue,
6341    budget: &mut Budget,
6342    location: &Location,
6343) -> Result<ConstructedValue, QueryError> {
6344    let array = array.data();
6345    // Sources are eager; reductions inspect their admitted immutable data.
6346    let (positional, keyed) = match &array {
6347        ConstructedValue::Array(values) => (values.as_slice(), &[][..]),
6348        ConstructedValue::KeyedArray { entries, .. } => (&[][..], entries.as_slice()),
6349        _ => return Err(error(location, "reducer needs an array")),
6350    };
6351    let len = positional.len().max(keyed.len());
6352    let values = positional
6353        .iter()
6354        .chain(keyed.iter().map(|entry| &entry.value));
6355    let value = match operator {
6356        ValueReducer::Length => {
6357            budget.charge(1, location.clone())?;
6358            ConstructedValue::Integer(
6359                i64::try_from(len).map_err(|_| error(location, "length overflow"))?,
6360            )
6361        }
6362        ValueReducer::Sum => {
6363            let mut total = 0_i64;
6364            for value in values {
6365                budget.charge(1, location.clone())?;
6366                let ConstructedValue::Integer(value) = value.data() else {
6367                    return Err(error(location, "sum needs integer array elements"));
6368                };
6369                total = total
6370                    .checked_add(*value)
6371                    .ok_or_else(|| error(location, "integer sum overflow"))?;
6372            }
6373            ConstructedValue::Integer(total)
6374        }
6375        ValueReducer::Forall | ValueReducer::Exists => {
6376            let mut result = operator == ValueReducer::Forall;
6377            for value in values {
6378                budget.charge(1, location.clone())?;
6379                let ConstructedValue::Boolean(value) = value.data() else {
6380                    return Err(error(
6381                        location,
6382                        "quantifier needs fixed Boolean array elements",
6383                    ));
6384                };
6385                // Scan every element even once the Boolean result is decisive.
6386                if operator == ValueReducer::Forall {
6387                    result &= value;
6388                } else {
6389                    result |= value;
6390                }
6391            }
6392            ConstructedValue::Boolean(result)
6393        }
6394    };
6395    scalar(value, budget, location)
6396}
6397
6398fn take_position(
6399    values: Vec<ConstructedValue>,
6400    position: usize,
6401    budget: &mut Budget,
6402    location: &Location,
6403) -> Result<ConstructedValue, QueryError> {
6404    let index = position
6405        .checked_sub(1)
6406        .filter(|index| *index < values.len())
6407        .ok_or_else(|| error(location, "missing collection position"))?;
6408    // Moving an admitted child needs no result readmission. Charge the traversal
6409    // that consumes preceding positions before selecting it.
6410    budget.charge(sum(index, 1, location)?, location.clone())?;
6411    Ok(values.into_iter().nth(index).expect("position checked"))
6412}
6413
6414fn integer_range(
6415    lower: i64,
6416    upper: i64,
6417    budget: &mut Budget,
6418    location: &Location,
6419) -> Result<ConstructedValue, QueryError> {
6420    let len = if upper < lower {
6421        0
6422    } else {
6423        usize::try_from(i128::from(upper) - i128::from(lower) + 1)
6424            .map_err(|_| error(location, "range cardinality overflow"))?
6425    };
6426    allocation::<ConstructedValue>(len, location)?;
6427    budget.charge(len, location.clone())?;
6428    let units = sum(
6429        1,
6430        len.checked_mul(2)
6431            .ok_or_else(|| error(location, "range allocation overflow"))?,
6432        location,
6433    )?;
6434    budget.charge_results(units, location.clone())?;
6435    let mut elements = Vec::with_capacity(len);
6436    for offset in 0..len {
6437        let value = i64::try_from(i128::from(lower) + offset as i128)
6438            .map_err(|_| error(location, "range value overflow"))?;
6439        elements.push(ConstructedValue::Integer(value));
6440    }
6441    Ok(ConstructedValue::Set {
6442        element_type: Some(SetElementType::Integer),
6443        elements,
6444    })
6445}
6446
6447fn insert_set(
6448    values: &mut Vec<ConstructedValue>,
6449    element_type: &mut Option<SetElementType>,
6450    value: ConstructedValue,
6451    admit_slot: bool,
6452    budget: &mut Budget,
6453    location: &Location,
6454) -> Result<(), QueryError> {
6455    let ty = scalar_set_type(&value)
6456        .ok_or_else(|| error(location, "set elements must have one supported scalar type"))?;
6457    if element_type.is_some_and(|previous| previous != ty) {
6458        return Err(error(location, "set element type mismatch"));
6459    }
6460    *element_type = Some(ty);
6461    match value.data() {
6462        ConstructedValue::Float(value) => finite(*value, location)?,
6463        ConstructedValue::Member(member) => {
6464            for previous in values.iter() {
6465                if let ConstructedValue::Member(previous) = previous.data() {
6466                    check_member_pair(previous, member, budget, location)?;
6467                }
6468            }
6469        }
6470        _ => {}
6471    }
6472    for existing in values.iter() {
6473        if compare_set_values(&value, existing, budget, location)? == Ordering::Equal {
6474            return Ok(());
6475        }
6476    }
6477    allocation::<ConstructedValue>(sum(values.len(), 1, location)?, location)?;
6478    if admit_slot {
6479        budget.charge_results(1, location.clone())?;
6480    }
6481    values.push(value);
6482    Ok(())
6483}
6484
6485/// Copy inferred facts under the active metadata allowance.
6486///
6487/// # Errors
6488/// Metadata allocation, work or nesting exhaustion retains the supplied location.
6489pub fn copy_value_types(
6490    types: &[ValueType],
6491    budget: &mut Budget,
6492    location: Location,
6493) -> Result<Vec<ValueType>, QueryError> {
6494    copy_types(types, budget, &location)
6495}
6496
6497// Shared tuple/record type products preserve every preflighted branch possibility.
6498pub(crate) fn extend_type_positions(
6499    positions: &[Vec<ValueType>],
6500    types: &[ValueType],
6501    budget: &mut Budget,
6502    location: &Location,
6503) -> Result<Vec<Vec<ValueType>>, QueryError> {
6504    let count = positions
6505        .len()
6506        .checked_mul(types.len())
6507        .ok_or_else(|| error(location, "type possibilities overflow"))?;
6508    let mut expanded = metadata_vec(count, budget, location)?;
6509    for position in positions {
6510        for ty in types {
6511            let mut entries = copy_types(position, budget, location)?;
6512            let ty = copy_type(ty, budget, location)?;
6513            push_metadata(&mut entries, ty, budget, location)?;
6514            expanded.push(entries);
6515        }
6516    }
6517    Ok(expanded)
6518}
6519
6520pub(crate) fn function_facts<K>(
6521    declaration: &DeclaredType<K>,
6522    budget: &mut Budget,
6523    location: &Location,
6524) -> Result<Vec<ValueType>, QueryError> {
6525    if crate::runtime_types::contains_native(declaration, budget, location)? {
6526        one_type(ValueType::Unestablished, budget, location)
6527    } else {
6528        let declaration =
6529            crate::runtime_types::constructed_declaration(declaration, budget, location)?;
6530        one_type(
6531            declared_fact(&declaration, ValueType::Unestablished, budget, location)?,
6532            budget,
6533            location,
6534        )
6535    }
6536}
6537
6538pub(crate) fn prepare_function_body<'a, Q, K, H: ValueQueryHost<Q>>(
6539    declaration: crate::functions::DeclarationRef<'a, Q, K>,
6540    queries: &mut H,
6541    budget: &mut Budget,
6542) -> Result<execution::Body<'a, Q, K>, QueryError> {
6543    let globals = BTreeMap::new();
6544    let mut analysis = Analysis::new(Globals::Facts(&globals), queries);
6545    let mut preparation = Preparation::new();
6546    for parameter in declaration.parameters.iter() {
6547        let types = function_facts(
6548            parameter.declaration,
6549            budget,
6550            &Location::Query(parameter.range.clone()),
6551        )?;
6552        match parameter.pattern {
6553            pattern::PatternRef::Name(name) => {
6554                function_local(name, &analysis.locals, budget)?;
6555                push_metadata(
6556                    &mut analysis.locals,
6557                    (name.value.as_str(), types),
6558                    budget,
6559                    &Location::Query(name.range.clone()),
6560                )?;
6561                push_metadata(
6562                    &mut preparation.locals,
6563                    name.value.as_str(),
6564                    budget,
6565                    &Location::Query(name.range.clone()),
6566                )?;
6567            }
6568            pattern::PatternRef::Pattern(pattern) => {
6569                analysis.pattern(pattern, &types, false, budget)?;
6570                pattern::names(pattern, &mut preparation.locals, budget)?;
6571            }
6572        }
6573    }
6574    let mut bindings = metadata_vec(
6575        declaration.bindings.len(),
6576        budget,
6577        &Location::Query(declaration.name.range.clone()),
6578    )?;
6579    for (name, value) in declaration.bindings {
6580        function_local(name, &analysis.locals, budget)?;
6581        let types = analysis.walk(value, budget)?;
6582        let expression = preparation.resolve(value, analysis.queries, budget)?;
6583        analysis
6584            .queries
6585            .prepare_local(name, value, &types, budget)?;
6586        push_metadata(
6587            &mut analysis.locals,
6588            (name.value.as_str(), types),
6589            budget,
6590            &Location::Query(name.range.clone()),
6591        )?;
6592        push_metadata(
6593            &mut preparation.locals,
6594            name.value.as_str(),
6595            budget,
6596            &Location::Query(name.range.clone()),
6597        )?;
6598        bindings.push((name.value.as_str(), expression));
6599    }
6600    analysis.walk(declaration.value, budget)?;
6601    let value = preparation.resolve(declaration.value, analysis.queries, budget)?;
6602    Ok(execution::Body {
6603        declaration,
6604        bindings,
6605        value,
6606    })
6607}
6608fn function_local(
6609    name: &Located<String>,
6610    locals: &[(&str, Vec<ValueType>)],
6611    budget: &mut Budget,
6612) -> Result<(), QueryError> {
6613    let location = Location::Query(name.range.clone());
6614    budget.charge(name.value.len() + 1, location.clone())?;
6615    if name.value == "_" {
6616        return Err(error(&location, "discard name cannot be bound"));
6617    }
6618    for (previous, _) in locals {
6619        budget.charge(1 + previous.len().min(name.value.len()), location.clone())?;
6620        if *previous == name.value {
6621            return Err(error(&location, "binding is already defined"));
6622        }
6623    }
6624    Ok(())
6625}
6626pub(crate) fn run_functions(
6627    declarations: &[crate::FunctionDeclaration],
6628    entry: &ValueExpression,
6629    bindings: &Bindings<ConstructedValue>,
6630    budget: &mut Budget,
6631    limits: crate::FunctionLimits,
6632) -> Result<ConstructedValue, crate::FunctionError<QueryError>> {
6633    let mut retained = Vec::new();
6634    for d in declarations {
6635        crate::projection::admit_slots::<crate::functions::DeclarationRef<'_, (), ()>>(
6636            1,
6637            budget,
6638            &d.name.range,
6639        )?;
6640        retained.push(d.as_borrowed().into());
6641    }
6642    run_function_refs(&retained, entry, bindings, budget, limits)
6643}
6644pub(crate) fn run_function_refs(
6645    declarations: &[crate::functions::DeclarationRef<'_, (), ()>],
6646    entry: &ValueExpression,
6647    bindings: &Bindings<ConstructedValue>,
6648    budget: &mut Budget,
6649    limits: crate::FunctionLimits,
6650) -> Result<ConstructedValue, crate::FunctionError<QueryError>> {
6651    let mut queries = NoQueries {
6652        bindings: Some(bindings),
6653    };
6654    let mut resolver = crate::functions::Resolver {
6655        queries: &mut queries,
6656        declarations,
6657    };
6658    let location = Location::Query(entry.range.clone());
6659    let mut bodies = metadata_vec(declarations.len(), budget, &location)?;
6660    for declaration in declarations {
6661        bodies.push(prepare_function_body(*declaration, &mut resolver, budget)?);
6662    }
6663    Analysis::new(Globals::Available(bindings), &mut resolver).walk(entry, budget)?;
6664    let mut preparation = Preparation::new();
6665    let entry = preparation.resolve(entry, &mut resolver, budget)?;
6666    let names = preparation.names;
6667    let mut frame = Frame::new(names.clone(), budget, &location)?;
6668    let mut calls = crate::functions::CallTrace::default();
6669    let mut check = |_: &ConstructedValue, _: &mut Budget, _: &Location| Ok(());
6670    let mut host = execution::ScalarHost {
6671        queries: &mut queries,
6672        frame: &mut frame,
6673        check: &mut check,
6674        query: std::marker::PhantomData,
6675        calls: &mut calls,
6676    };
6677    let result = execution::execute(
6678        &entry,
6679        &names,
6680        &bodies,
6681        &mut host,
6682        budget,
6683        limits.active_depth,
6684        0,
6685    )
6686    .and_then(|v| v.materialize(budget, &location));
6687    result.map_err(|error| crate::FunctionError {
6688        error,
6689        omitted_calls: calls.omitted,
6690        calls: calls
6691            .frames
6692            .into_iter()
6693            .map(|(id, range)| crate::FunctionCall {
6694                function: declarations[id].name.value.chars().take(64).collect(),
6695                range,
6696            })
6697            .collect(),
6698    })
6699}