Skip to main content

cel_parser/
ast.rs

1//! A span-carrying CEL expression AST, built by [`AstContext`] as an
2//! alternative to [`DynSegmentContext`](crate::parser_context::DynSegmentContext)'s direct
3//! execution. Consumed as-is by adam-lang (method bodies/initializers), the language server, the
4//! formatter, and the future macro-compilation backend. Carries no resolved types or operator
5//! overloads: resolution and type/range validation are deferred to a later, separate phase.
6//!
7//! Array nodes preserve optional recursive type ascriptions exactly as written — for example
8//! `[0, 1]: [i32]`, `[]: [[i32]]`, or `[]: [Custom]` — using unresolved [`TypeExpr`] leaves so a
9//! later checker can resolve names through built-in CEL types or a host registry. Recursive
10//! tuple, array, and callable types compose without per-shape host registrations.
11
12use std::any::Any;
13use std::ffi::CString;
14
15use proc_macro2::Span;
16
17use crate::op_table::OpLookup;
18use crate::parser_context::ParserContext;
19use crate::type_expr::TypeExpr;
20
21/// Source range of an AST node: start of its first token to end of its last.
22///
23/// Two `proc_macro2::Span`s (not a [`crate::SourceSpan`]) so the future macro-compilation backend
24/// can attach `compile_error!`/`quote_spanned!`, and so the (separate, deferred) comment/trivia
25/// pass can call `Span::source_text()`. `crate::SourceSpan` remains the `Send + Sync` wire format
26/// used only at the `CELError` diagnostic boundary; this AST is an internal parser artifact using
27/// the same span currency the parser itself already does.
28///
29/// `proc_macro2::Span` isn't `PartialEq`, so neither is this type — shape tests should match
30/// structurally and ignore spans rather than assert exact span equality.
31///
32/// # Examples
33/// See [`Self::for_text`].
34#[derive(Clone, Copy, Debug)]
35pub struct ExprSpan {
36    /// Start of the first token of the node.
37    pub start: Span,
38    /// End of the last token of the node.
39    pub end: Span,
40}
41
42impl ExprSpan {
43    /// A single-token range where start and end coincide.
44    fn point(span: Span) -> Self {
45        ExprSpan {
46            start: span,
47            end: span,
48        }
49    }
50
51    /// Creates a span whose `source_text()` returns exactly `text`, by
52    /// tokenizing it and taking the resulting token's span. For hand-built
53    /// AST nodes (not produced by parsing real source), this gives leaves
54    /// like a type name or literal a span the formatter can read back
55    /// text from, the same way `cel-rs-macros` already does internally.
56    ///
57    /// - Precondition: `text` tokenizes to exactly one token tree.
58    ///
59    /// - Complexity: O(n) in `text.len()` (it lexes `text`).
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use cel_parser::ExprSpan;
65    ///
66    /// let span = ExprSpan::for_text("i64");
67    /// assert_eq!(span.start.source_text().as_deref(), Some("i64"));
68    /// ```
69    #[must_use]
70    pub fn for_text(text: &str) -> Self {
71        let tokens: proc_macro2::TokenStream = text
72            .parse()
73            .expect("ExprSpan::for_text: text must be valid tokens");
74        let mut iter = tokens.into_iter();
75        let first = iter
76            .next()
77            .expect("ExprSpan::for_text: text must be non-empty");
78        debug_assert!(
79            iter.next().is_none(),
80            "ExprSpan::for_text: text must tokenize to exactly one token"
81        );
82        let span = first.span();
83        ExprSpan {
84            start: span,
85            end: span,
86        }
87    }
88}
89
90/// A CEL literal, one variant per concrete Rust type [`crate::ParserContext::push_literal`] can
91/// receive from `push_literal_token` in `lib.rs`.
92///
93/// `Byte` literals (`b'A'`) and `u8`-suffixed integer literals (`65u8`) both arrive as `u8` and
94/// are indistinguishable here — both become `Literal::U8`; a formatter that must reproduce exact
95/// original syntax should re-slice the node's original source text from its span instead of
96/// relying on this enum to recover lexical form.
97///
98/// # Examples
99/// See [`crate::Parser::parse_tokens_ast`] for retained literal values.
100#[derive(Clone, Debug, PartialEq)]
101pub enum Literal {
102    /// `i8` literal (e.g. `1i8`).
103    I8(i8),
104    /// `i16` literal (e.g. `1i16`).
105    I16(i16),
106    /// `i32` literal, the default integer suffix (e.g. `1` or `1i32`).
107    I32(i32),
108    /// `i64` literal (e.g. `1i64`).
109    I64(i64),
110    /// `i128` literal (e.g. `1i128`).
111    I128(i128),
112    /// `isize` literal (e.g. `1isize`).
113    Isize(isize),
114    /// `u8` literal or byte literal (e.g. `1u8` or `b'A'`).
115    U8(u8),
116    /// `u16` literal (e.g. `1u16`).
117    U16(u16),
118    /// `u32` literal (e.g. `1u32`).
119    U32(u32),
120    /// `u64` literal (e.g. `1u64`).
121    U64(u64),
122    /// `u128` literal (e.g. `1u128`).
123    U128(u128),
124    /// `usize` literal (e.g. `1usize`).
125    Usize(usize),
126    /// `f32` literal (e.g. `1.0f32`).
127    F32(f32),
128    /// `f64` literal, the default float suffix (e.g. `1.0` or `1.0f64`).
129    F64(f64),
130    /// Boolean literal (`true`/`false`).
131    Bool(bool),
132    /// Character literal (e.g. `'a'`).
133    Char(char),
134    /// String literal (e.g. `"x"`).
135    Str(String),
136    /// Byte-string literal (e.g. `b"x"`).
137    ByteStr(Vec<u8>),
138    /// C-string literal (e.g. `c"x"`).
139    CStr(CString),
140    /// Unit (`()`).
141    Unit,
142}
143
144/// The two short-circuiting logical operators.
145///
146/// # Examples
147/// See [`crate::ParserContext::apply_logical`] for lazy logical composition.
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub enum LogicalOp {
150    /// `&&`.
151    And,
152    /// `||`.
153    Or,
154}
155
156/// A parsed CEL expression with source spans on every node.
157///
158/// Built by [`AstContext`]; consumed as-is by adam-lang (method
159/// bodies/initializers), the language server (hover/goto), the formatter, and the future
160/// macro-compilation backend. Carries no resolved types or operator overloads — resolution is
161/// deferred to a later, separate type-checking phase.
162///
163/// `Logical` is kept distinct from `If` (rather than desugaring `a || b` to
164/// `if a { true } else { b }`, which is how [`DynSegmentContext`](crate::parser_context::DynSegmentContext)
165/// executes it) so a formatter can round-trip `a || b` as `a || b`.
166///
167/// # Examples
168/// See [`crate::Parser::parse_tokens_ast`] for building retained expressions.
169#[derive(Clone, Debug)]
170pub enum Expr {
171    /// A named record constructor, unresolved until semantic checking.
172    ///
173    /// # Examples
174    /// ```
175    /// use cel_parser::{AstContext, Expr, OpLookup, Parser};
176    /// let expr = Parser::<AstContext>::new(OpLookup::new())
177    ///     .parse_str_ast("Point { x: 1.0 }").unwrap();
178    /// assert!(matches!(expr, Expr::Struct { fields, .. } if fields.len() == 1));
179    /// ```
180    Struct {
181        /// The constructor's type name, not a value reference.
182        name: String,
183        /// The constructor name token's span.
184        name_span: ExprSpan,
185        /// Field labels and expressions in their written evaluation order.
186        fields: Vec<RecordInit>,
187        /// The whole constructor, including the closing brace.
188        span: ExprSpan,
189    },
190    /// A literal value (`10i32`, `"x"`, `true`, `()`, ...).
191    Literal {
192        /// The literal's value.
193        value: Literal,
194        /// The literal token's span.
195        span: ExprSpan,
196    },
197    /// A bare identifier reference or zero-arg builtin lookup (`x`, `pi`) — unresolved.
198    Ident {
199        /// The identifier's name.
200        name: String,
201        /// The identifier token's span.
202        span: ExprSpan,
203    },
204    /// A prefix (arity 1) or infix (arity 2) operator application (`-x`, `a + b`, `a == b`).
205    Op {
206        /// The operator's name (e.g. `"+"`, `"-"`, `"=="`).
207        name: String,
208        /// The operand sub-expressions, in source order.
209        operands: Vec<Expr>,
210        /// The span of the whole operator application.
211        span: ExprSpan,
212    },
213    /// A call: `callee(args...)` — the grammar's `"()"` operator.
214    Apply {
215        /// The expression being called.
216        callee: Box<Expr>,
217        /// The argument sub-expressions, in source order.
218        args: Vec<Expr>,
219        /// The span of the whole call, including its argument list.
220        span: ExprSpan,
221    },
222    /// A direct receiver operation (`receiver.name` or `receiver.name(args...)`).
223    ///
224    /// The label is not an identifier reference. Type and overload resolution are deferred.
225    ///
226    /// # Examples
227    /// ```
228    /// use cel_parser::{AstContext, Expr, OpLookup, Parser};
229    /// let expr = Parser::<AstContext>::new(OpLookup::new()).parse_str_ast("a.x()").unwrap();
230    /// assert!(matches!(expr, Expr::Member { args: Some(args), .. } if args.is_empty()));
231    /// ```
232    Member {
233        /// The receiver, evaluated before explicit arguments.
234        receiver: Box<Expr>,
235        /// The operation's name.
236        name: String,
237        /// The member name token's span.
238        name_span: ExprSpan,
239        /// Explicit arguments in source order; `None` preserves property spelling.
240        args: Option<Vec<Expr>>,
241        /// The span from the receiver through the name or closing parenthesis.
242        span: ExprSpan,
243    },
244    /// A tuple literal (`(a, b, ...)`; a 1-tuple is `(a,)`).
245    Tuple {
246        /// The element sub-expressions, in source order.
247        elements: Vec<Expr>,
248        /// The span of the whole tuple, including its parentheses.
249        span: ExprSpan,
250    },
251    /// An array literal (`[a, b, ...]`, `[a, b]: [T]`, or `[]: [T]`). An unannotated empty array
252    /// is still rejected by the grammar (see <https://github.com/stlab/cel-rs/issues/212>), but
253    /// a typed empty array is recorded here with `elements.is_empty()`. `type_annotation` stores
254    /// the complete recursive array type exactly as written, including nested arrays and custom
255    /// leaf names such as `Custom`. Whether the elements share one type, whether
256    /// `type_annotation` names a complete array type are deferred to the later type-checking phase (see the module doc
257    /// comment), same as `TupleIndex`'s deferred bounds check.
258    Array {
259        /// The element sub-expressions, in source order.
260        elements: Vec<Expr>,
261        /// The optional declared array type, as written after `:`.
262        type_annotation: Option<TypeExpr>,
263        /// The span from the `:` token through the end of the declared type, when present.
264        annotation_span: Option<ExprSpan>,
265        /// The span of the whole array literal, including its brackets and any annotation.
266        span: ExprSpan,
267    },
268    /// A tuple index (`base.N`). Whether `base` is actually a tuple and `N` is in range is
269    /// unchecked here — deferred to the type-checking phase (see the module doc comment).
270    TupleIndex {
271        /// The expression being indexed.
272        base: Box<Expr>,
273        /// The index.
274        index: usize,
275        /// The span from the start of `base` through the index token.
276        span: ExprSpan,
277    },
278    /// An `if cond { then } else { else_ }` expression.
279    If {
280        /// The condition.
281        cond: Box<Expr>,
282        /// The then-branch.
283        then_branch: Box<Expr>,
284        /// The else-branch, or `None` if no `else`/`else if` was written.
285        else_branch: Option<Box<Expr>>,
286        /// The span of the whole `if`/`else` construct.
287        span: ExprSpan,
288    },
289    /// A short-circuiting `&&`/`||`.
290    Logical {
291        /// Which logical operator.
292        op: LogicalOp,
293        /// The left-hand side.
294        lhs: Box<Expr>,
295        /// The right-hand side.
296        rhs: Box<Expr>,
297        /// The span of the whole logical expression.
298        span: ExprSpan,
299    },
300    /// A cast (`expr as Type`). Whether `type_name` is a recognized type and whether a
301    /// conversion from `expr`'s type exists is unchecked here — deferred to the type-checking
302    /// phase (see the module doc comment), same as `TupleIndex`'s deferred bounds check.
303    Cast {
304        /// The expression being converted.
305        expr: Box<Expr>,
306        /// The target type's name, as written (e.g. `"i32"`) - unresolved.
307        type_name: String,
308        /// The span of the whole `expr as Type` construct.
309        span: ExprSpan,
310    },
311    /// A closure literal (`|x: i32| x + 1`, or `|| 1i32` with no parameters).
312    Closure {
313        /// The closure's declared parameters, in source order.
314        params: Vec<ClosureParam>,
315        /// The closure's body expression.
316        body: Box<Expr>,
317        /// The span of the whole closure literal, from its opening `|`/`||` through `body`.
318        span: ExprSpan,
319    },
320}
321
322impl Expr {
323    /// Returns this node's source span.
324    ///
325    /// # Examples
326    /// ```
327    /// use cel_parser::{AstContext, OpLookup, Parser};
328    /// let expr = Parser::<AstContext>::new(OpLookup::new()).parse_str_ast("1 + 2").unwrap();
329    /// assert_eq!(expr.span().start.start().column, 0);
330    /// assert_eq!(expr.span().end.end().column, 5);
331    /// ```
332    pub fn span(&self) -> ExprSpan {
333        match self {
334            Expr::Literal { span, .. }
335            | Expr::Struct { span, .. }
336            | Expr::Ident { span, .. }
337            | Expr::Op { span, .. }
338            | Expr::Apply { span, .. }
339            | Expr::Member { span, .. }
340            | Expr::Tuple { span, .. }
341            | Expr::Array { span, .. }
342            | Expr::TupleIndex { span, .. }
343            | Expr::If { span, .. }
344            | Expr::Logical { span, .. }
345            | Expr::Cast { span, .. }
346            | Expr::Closure { span, .. } => *span,
347        }
348    }
349}
350
351/// One required record field initializer, retaining its written name, span, and value.
352///
353/// # Examples
354/// ```
355/// use cel_parser::{AstContext, Expr, OpLookup, Parser};
356/// let expr = Parser::<AstContext>::new(OpLookup::new())
357///     .parse_str_ast("Point { x: value }").unwrap();
358/// if let Expr::Struct { fields, .. } = expr {
359///     assert_eq!(fields[0].name, "x");
360/// }
361/// ```
362#[derive(Clone, Debug)]
363pub struct RecordInit {
364    /// The field label, not an identifier reference.
365    pub name: String,
366    /// The field label token's span.
367    pub name_span: ExprSpan,
368    /// The source-order value expression.
369    pub value: Expr,
370}
371
372/// One `closure_param = identifier ":" type_expression` — a closure literal's declared
373/// parameter.
374///
375/// # Examples
376/// ```
377/// use cel_parser::{AstContext, Expr, OpLookup, Parser};
378/// let expr = Parser::<AstContext>::new(OpLookup::new()).parse_str_ast("|x: i32| x").unwrap();
379/// let Expr::Closure { params, .. } = expr else { unreachable!() };
380/// assert_eq!(params[0].name, "x");
381/// ```
382#[derive(Clone, Debug)]
383pub struct ClosureParam {
384    /// The parameter's declared name.
385    pub name: String,
386    /// The name token's span.
387    pub name_span: ExprSpan,
388    /// The parameter's declared, unresolved type.
389    pub type_expr: crate::TypeExpr,
390}
391
392/// Reports reserved or repeated parameter names without resolving their type annotations.
393///
394/// - Complexity: Expected O(n + b) time and O(n) space for n parameters and b name bytes.
395pub(crate) fn closure_parameter_diagnostics(params: &[ClosureParam]) -> Vec<crate::ParseError> {
396    let mut names = std::collections::HashSet::with_capacity(params.len());
397    params
398        .iter()
399        .filter_map(|param| {
400            let message = if matches!(param.name.as_str(), "if" | "else" | "as" | "true" | "false")
401            {
402                "reserved keyword is not a closure parameter name"
403            } else if !names.insert(param.name.as_str()) {
404                "duplicate closure parameter name"
405            } else {
406                return None;
407            };
408            Some(crate::ParseError::new_range(
409                message,
410                param.name_span.start,
411                param.name_span.end,
412            ))
413        })
414        .collect()
415}
416
417/// Converts a statically-known literal value into its [`Literal`] variant, transferring owned buffers.
418///
419/// - Precondition: `T` is one of the concrete types `push_literal_token` (`lib.rs`) pushes:
420///   the signed/unsigned integer widths, `f32`/`f64`, `bool`, `char`, `String`, `Vec<u8>`,
421///   `CString`, or `()`.
422fn to_literal<T: 'static>(value: T) -> Literal {
423    // Taking an Option transfers even CString without constructing a replacement buffer.
424    let mut value = Some(value);
425    let any = &mut value as &mut dyn Any;
426    macro_rules! map {
427        ($($t:ty => $variant:path),+ $(,)?) => {
428            $( if let Some(x) = any.downcast_mut::<Option<$t>>() {
429                return $variant(x.take().expect("literal is transferred once"));
430            } )+
431        };
432    }
433    map! {
434        i8 => Literal::I8, i16 => Literal::I16, i32 => Literal::I32, i64 => Literal::I64,
435        i128 => Literal::I128, isize => Literal::Isize,
436        u8 => Literal::U8, u16 => Literal::U16, u32 => Literal::U32, u64 => Literal::U64,
437        u128 => Literal::U128, usize => Literal::Usize,
438        f32 => Literal::F32, f64 => Literal::F64,
439        bool => Literal::Bool, char => Literal::Char, String => Literal::Str,
440        Vec<u8> => Literal::ByteStr, CString => Literal::CStr,
441    }
442    if any.is::<Option<()>>() {
443        return Literal::Unit;
444    }
445    unreachable!("push_literal called with an unsupported literal type")
446}
447
448/// [`ParserContext`] implementation that builds a span-carrying [`Expr`] tree instead of
449/// executing. Consults no [`OpLookup`], inspects no runtime types, and never fails on semantic
450/// grounds — resolution and type/range checking are deferred to a later, separate
451/// type-checking phase (see the module doc comment).
452///
453/// # Examples
454///
455/// ```rust
456/// use cel_parser::{AstContext, ParserContext};
457/// use proc_macro2::Span;
458///
459/// let mut ctx = AstContext::new_context();
460/// ctx.push_literal(10i32, Span::call_site());
461/// ```
462#[derive(Debug, Default)]
463pub struct AstContext {
464    /// Finished sub-trees; a completed parse leaves exactly one.
465    values: Vec<Expr>,
466}
467
468impl AstContext {
469    /// Removes and returns the top node.
470    ///
471    /// - Precondition: at least one node is present.
472    fn pop(&mut self) -> Expr {
473        self.values
474            .pop()
475            .expect("operand present on AST value stack")
476    }
477
478    /// Removes and returns the top `n` nodes, in source order.
479    ///
480    /// - Precondition: at least `n` nodes are present.
481    /// - Complexity: O(n) time and space for the returned nodes.
482    fn pop_n(&mut self, n: usize) -> Vec<Expr> {
483        let at = self.values.len() - n;
484        self.values.split_off(at)
485    }
486
487    /// Consumes the context, returning the single parsed expression.
488    ///
489    /// - Precondition: parsing completed successfully (exactly one node remains).
490    ///
491    /// # Examples
492    /// ```
493    /// use cel_parser::{AstContext, ParserContext, Expr, Literal};
494    /// let mut context = AstContext::new_context();
495    /// context.push_literal(7i32, proc_macro2::Span::call_site());
496    /// assert!(matches!(context.into_expr(), Expr::Literal { value: Literal::I32(7), .. }));
497    /// ```
498    pub fn into_expr(mut self) -> Expr {
499        debug_assert_eq!(
500            self.values.len(),
501            1,
502            "a successfully parsed expression leaves exactly one node"
503        );
504        self.pop()
505    }
506}
507
508impl ParserContext for AstContext {
509    /// Defers closure annotation resolution to semantic checking or compilation.
510    fn resolves_closure_types() -> bool {
511        false
512    }
513
514    /// Creates an empty syntax stack.
515    fn new_context() -> Self {
516        AstContext { values: Vec::new() }
517    }
518
519    /// Creates an independent empty syntax stack.
520    fn new_fragment(&self) -> Self {
521        AstContext { values: Vec::new() }
522    }
523
524    /// Transfers one supported literal into a leaf node without copying owned buffers.
525    fn push_literal<T: 'static + Clone>(&mut self, value: T, span: Span) {
526        self.values.push(Expr::Literal {
527            value: to_literal(value),
528            span: ExprSpan::point(span),
529        });
530    }
531
532    /// Records an identifier, ordinary call, or operator with source-order operands.
533    ///
534    /// - Precondition: the stack contains `arity` operands, including a callee for `"()"`.
535    /// - Complexity: O(a + b) time and space for a operands and b name bytes.
536    fn apply_op(
537        &mut self,
538        _op_lookup: &OpLookup,
539        name: &str,
540        arity: usize,
541        start: Span,
542        end: Span,
543    ) -> crate::Result<()> {
544        let span = ExprSpan { start, end };
545        if arity == 0 {
546            self.values.push(Expr::Ident {
547                name: name.to_string(),
548                span,
549            });
550        } else if name == "()" {
551            let mut operands = self.pop_n(arity); // [callee, arg1, ...]
552            let callee = operands.remove(0);
553            self.values.push(Expr::Apply {
554                callee: Box::new(callee),
555                args: operands,
556                span,
557            });
558        } else {
559            let operands = self.pop_n(arity); // arity 1 = prefix, 2 = infix
560            self.values.push(Expr::Op {
561                name: name.to_string(),
562                operands,
563                span,
564            });
565        }
566        Ok(())
567    }
568
569    /// Records member spelling and source-order operands without resolving their types.
570    ///
571    /// - Complexity: O(n) in the number of explicit arguments.
572    fn apply_member(
573        &mut self,
574        _lookup: &OpLookup,
575        name: &str,
576        name_span: ExprSpan,
577        explicit_args: Option<usize>,
578        start: Span,
579        end: Span,
580    ) -> crate::Result<()> {
581        debug_assert!(self.values.len() > explicit_args.unwrap_or(0));
582        let args = explicit_args.map(|count| self.pop_n(count));
583        let receiver = self.pop();
584        self.values.push(Expr::Member {
585            receiver: Box::new(receiver),
586            name: name.to_string(),
587            name_span,
588            args,
589            span: ExprSpan { start, end },
590        });
591        Ok(())
592    }
593
594    /// Records source-order field names and values without resolving a constructor.
595    ///
596    /// - Complexity: O(f) in the number of fields.
597    fn make_record(
598        &mut self,
599        _lookup: &OpLookup,
600        name: &str,
601        name_span: ExprSpan,
602        fields: Vec<(String, ExprSpan)>,
603        _ambient_start: usize,
604        start: Span,
605        end: Span,
606    ) -> crate::Result<()> {
607        let values = self.pop_n(fields.len());
608        let fields = fields
609            .into_iter()
610            .zip(values)
611            .map(|((name, name_span), value)| RecordInit {
612                name,
613                name_span,
614                value,
615            })
616            .collect();
617        self.values.push(Expr::Struct {
618            name: name.to_string(),
619            name_span,
620            fields,
621            span: ExprSpan { start, end },
622        });
623        Ok(())
624    }
625
626    /// Records a lazy logical operation without replacing it with conditional syntax.
627    ///
628    /// - Precondition: `name` is `"&&"` or `"||"`; each side contains one expression.
629    fn apply_logical(
630        &mut self,
631        name: &str,
632        mut rhs: Self,
633        start: Span,
634        end: Span,
635    ) -> crate::Result<()> {
636        let op = match name {
637            "||" => LogicalOp::Or,
638            "&&" => LogicalOp::And,
639            other => unreachable!("apply_logical called with unsupported operator `{other}`"),
640        };
641        let lhs = self.pop();
642        debug_assert_eq!(
643            rhs.values.len(),
644            1,
645            "rhs fragment produces exactly one value"
646        );
647        let rhs_expr = rhs.pop();
648        self.values.push(Expr::Logical {
649            op,
650            lhs: Box::new(lhs),
651            rhs: Box::new(rhs_expr),
652            span: ExprSpan { start, end },
653        });
654        Ok(())
655    }
656
657    /// Records one conditional, preserving an absent `else` rather than synthesizing syntax.
658    ///
659    /// - Precondition: the condition and each supplied branch contain one expression.
660    fn join2(
661        &mut self,
662        mut then_fragment: Self,
663        else_fragment: Option<Self>,
664        start: Span,
665        end: Span,
666    ) -> anyhow::Result<()> {
667        let cond = self.pop();
668        debug_assert_eq!(
669            then_fragment.values.len(),
670            1,
671            "then fragment produces exactly one value"
672        );
673        let then_branch = then_fragment.pop();
674        let else_branch = else_fragment.map(|mut fragment| {
675            debug_assert_eq!(
676                fragment.values.len(),
677                1,
678                "else fragment produces exactly one value"
679            );
680            Box::new(fragment.pop())
681        });
682        self.values.push(Expr::If {
683            cond: Box::new(cond),
684            then_branch: Box::new(then_branch),
685            else_branch,
686            span: ExprSpan { start, end },
687        });
688        Ok(())
689    }
690
691    /// Collects source-order elements into a tuple, representing the empty tuple as unit.
692    ///
693    /// - Precondition: exactly `n` nodes follow `ambient_start`.
694    /// - Complexity: O(n) time and space for n elements.
695    fn make_tuple(
696        &mut self,
697        n: usize,
698        ambient_start: usize,
699        start: Span,
700        end: Span,
701    ) -> crate::Result<()> {
702        if n == 0 {
703            self.values.push(Expr::Literal {
704                value: Literal::Unit,
705                span: ExprSpan { start, end },
706            });
707            return Ok(());
708        }
709        let elements = self.values.split_off(ambient_start);
710        debug_assert_eq!(
711            elements.len(),
712            n,
713            "make_tuple splits off exactly n elements"
714        );
715        self.values.push(Expr::Tuple {
716            elements,
717            span: ExprSpan { start, end },
718        });
719        Ok(())
720    }
721
722    /// Collects source-order elements into an unannotated array node.
723    ///
724    /// - Precondition: exactly `n` nodes follow `ambient_start`.
725    /// - Complexity: O(n) time and space for n elements.
726    fn make_array(
727        &mut self,
728        n: usize,
729        ambient_start: usize,
730        start: Span,
731        end: Span,
732    ) -> crate::Result<()> {
733        let elements = self.values.split_off(ambient_start);
734        debug_assert_eq!(
735            elements.len(),
736            n,
737            "make_array splits off exactly n elements"
738        );
739        self.values.push(Expr::Array {
740            elements,
741            type_annotation: None,
742            annotation_span: None,
743            span: ExprSpan { start, end },
744        });
745        Ok(())
746    }
747
748    /// Collects elements and retains unresolved annotation syntax and its source boundary.
749    ///
750    /// - Precondition: exactly `n` nodes follow `ambient_start`.
751    /// - Complexity: O(n) time and space for n elements.
752    fn make_annotated_array(
753        &mut self,
754        n: usize,
755        ambient_start: usize,
756        annotation: crate::parser_context::AnnotatedArray<'_>,
757        start: Span,
758        end: Span,
759    ) -> crate::Result<()> {
760        let elements = self.values.split_off(ambient_start);
761        debug_assert_eq!(
762            elements.len(),
763            n,
764            "make_annotated_array splits off exactly n elements"
765        );
766        let (type_annotation, annotation_span) = annotation.into_parts();
767        self.values.push(Expr::Array {
768            elements,
769            type_annotation,
770            annotation_span,
771            span: ExprSpan { start, end },
772        });
773        Ok(())
774    }
775
776    /// Reports no semantic tuple arity during syntax-only parsing.
777    fn peek_tuple_arity(&self) -> Option<usize> {
778        None
779    }
780
781    /// Records every representable tuple index without prematurely checking its base type.
782    ///
783    /// - Precondition: one base expression is present.
784    fn tuple_index(&mut self, index: usize, start: Span, end: Span) -> crate::Result<()> {
785        let base = self.pop();
786        self.values.push(Expr::TupleIndex {
787            base: Box::new(base),
788            index,
789            span: ExprSpan { start, end },
790        });
791        Ok(())
792    }
793
794    /// Returns the node offset used to delimit the current element list.
795    fn current_stack_offset(&self) -> usize {
796        self.values.len()
797    }
798
799    /// Records a cast without resolving its target type.
800    ///
801    /// - Precondition: one operand is present.
802    /// - Complexity: O(b) time and space for b type-name bytes.
803    fn apply_cast(
804        &mut self,
805        _op_lookup: &OpLookup,
806        type_name: &str,
807        start: Span,
808        end: Span,
809    ) -> crate::Result<()> {
810        let expr = self.pop();
811        self.values.push(Expr::Cast {
812            expr: Box::new(expr),
813            type_name: type_name.to_string(),
814            span: ExprSpan { start, end },
815        });
816        Ok(())
817    }
818
819    /// Transfers parameter syntax and its sole body expression into a closure node.
820    ///
821    /// - Precondition: `body` contains exactly one expression.
822    fn push_closure(
823        &mut self,
824        param_types: Vec<cel_runtime::ValueType>,
825        params: Vec<ClosureParam>,
826        body: Self,
827        span: Span,
828    ) -> crate::Result<()> {
829        let _ = param_types;
830        let body_expr = body.into_expr();
831        let end = body_expr.span().end;
832        self.values.push(Expr::Closure {
833            params,
834            body: Box::new(body_expr),
835            span: ExprSpan { start: span, end },
836        });
837        Ok(())
838    }
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844    use crate::Parser;
845
846    /// Name validation reports the duplicate or reserved formal at its own source location.
847    #[test]
848    fn parameter_diagnostics_use_the_offending_formals_span() {
849        let Expr::Closure { mut params, .. } = Parser::<AstContext>::new(OpLookup::new())
850            .parse_str_ast("|x: i32, y: bool| x")
851            .unwrap()
852        else {
853            panic!("closure")
854        };
855        assert!(closure_parameter_diagnostics(&params).is_empty());
856        params[1].name = "x".to_owned();
857        let errors = closure_parameter_diagnostics(&params);
858        assert_eq!(errors.len(), 1);
859        assert!(errors[0].message().contains("duplicate"));
860        assert_eq!(errors[0].span().start().column, 9);
861        params[1].name = "as".to_owned();
862        assert!(
863            closure_parameter_diagnostics(&params)[0]
864                .message()
865                .contains("reserved")
866        );
867    }
868
869    /// Converting owned literals retains their existing allocations and contents.
870    #[test]
871    fn owned_literal_buffers_transfer_without_copying() {
872        let text = String::from("owned");
873        let pointer = text.as_ptr();
874        let Literal::Str(text) = to_literal(text) else {
875            panic!("string literal")
876        };
877        assert_eq!(text, "owned");
878        assert_eq!(text.as_ptr(), pointer);
879        let bytes = vec![1u8, 2, 3];
880        let pointer = bytes.as_ptr();
881        let Literal::ByteStr(bytes) = to_literal(bytes) else {
882            panic!("byte string literal")
883        };
884        assert_eq!(bytes, [1, 2, 3]);
885        assert_eq!(bytes.as_ptr(), pointer);
886        let text = CString::new("owned").unwrap();
887        let pointer = text.as_ptr();
888        let Literal::CStr(text) = to_literal(text) else {
889            panic!("C string literal")
890        };
891        assert_eq!(text.to_bytes(), b"owned");
892        assert_eq!(text.as_ptr(), pointer);
893    }
894
895    /// Supplies a real range distinct from call-site and nested fixture spans.
896    fn distinct_span() -> ExprSpan {
897        let tokens: proc_macro2::TokenStream = "\n outer\n end".parse().unwrap();
898        let mut tokens = tokens.into_iter();
899        ExprSpan {
900            start: tokens.next().unwrap().span(),
901            end: tokens.next().unwrap().span(),
902        }
903    }
904    use proc_macro2::Span;
905
906    #[test]
907    fn span_returns_the_range_stored_on_a_leaf_variant() {
908        let target = distinct_span();
909        let expr = Expr::Ident {
910            name: "x".to_string(),
911            span: target,
912        };
913        assert_eq!(
914            crate::SourceSpan::from_proc_macro2_range(expr.span().start, expr.span().end),
915            crate::SourceSpan::new(2, 1, 3, 4)
916        );
917    }
918
919    #[test]
920    fn span_returns_the_range_stored_on_a_composite_variant() {
921        let target = distinct_span();
922        let expr = Expr::If {
923            cond: Box::new(Expr::Literal {
924                value: Literal::Bool(true),
925                span: ExprSpan::point(Span::call_site()),
926            }),
927            then_branch: Box::new(Expr::Literal {
928                value: Literal::I32(1),
929                span: ExprSpan::point(Span::call_site()),
930            }),
931            else_branch: Some(Box::new(Expr::Literal {
932                value: Literal::I32(2),
933                span: ExprSpan::point(Span::call_site()),
934            })),
935            span: target,
936        };
937        assert_eq!(
938            crate::SourceSpan::from_proc_macro2_range(expr.span().start, expr.span().end),
939            crate::SourceSpan::new(2, 1, 3, 4)
940        );
941    }
942
943    #[test]
944    fn closure_span_is_the_stored_span() {
945        let target = distinct_span();
946        let expr = Expr::Closure {
947            params: Vec::new(),
948            body: Box::new(Expr::Literal {
949                value: Literal::I32(1),
950                span: ExprSpan::point(Span::call_site()),
951            }),
952            span: target,
953        };
954        assert_eq!(
955            crate::SourceSpan::from_proc_macro2_range(expr.span().start, expr.span().end),
956            crate::SourceSpan::new(2, 1, 3, 4)
957        );
958    }
959
960    #[test]
961    fn literal_variants_are_distinguishable_and_comparable() {
962        assert_eq!(Literal::I32(1), Literal::I32(1));
963        assert_ne!(Literal::I32(1), Literal::I32(2));
964        assert_ne!(Literal::I32(1), Literal::U8(1));
965        assert_eq!(Literal::Unit, Literal::Unit);
966    }
967
968    #[test]
969    fn logical_op_equality() {
970        assert_eq!(LogicalOp::And, LogicalOp::And);
971        assert_ne!(LogicalOp::And, LogicalOp::Or);
972    }
973
974    #[test]
975    fn push_literal_dispatches_every_concrete_literal_type() {
976        fn literal_of<T: 'static + Clone>(value: T) -> Literal {
977            let mut ctx = AstContext::new_context();
978            ctx.push_literal(value, Span::call_site());
979            match ctx.into_expr() {
980                Expr::Literal { value, .. } => value,
981                other => panic!("expected Literal, got {other:?}"),
982            }
983        }
984        assert_eq!(literal_of(1i8), Literal::I8(1));
985        assert_eq!(literal_of(1i16), Literal::I16(1));
986        assert_eq!(literal_of(1i32), Literal::I32(1));
987        assert_eq!(literal_of(1i64), Literal::I64(1));
988        assert_eq!(literal_of(1i128), Literal::I128(1));
989        assert_eq!(literal_of(1isize), Literal::Isize(1));
990        assert_eq!(literal_of(1u8), Literal::U8(1));
991        assert_eq!(literal_of(1u16), Literal::U16(1));
992        assert_eq!(literal_of(1u32), Literal::U32(1));
993        assert_eq!(literal_of(1u64), Literal::U64(1));
994        assert_eq!(literal_of(1u128), Literal::U128(1));
995        assert_eq!(literal_of(1usize), Literal::Usize(1));
996        assert_eq!(literal_of(1.0f32), Literal::F32(1.0));
997        assert_eq!(literal_of(1.0f64), Literal::F64(1.0));
998        assert_eq!(literal_of(true), Literal::Bool(true));
999        assert_eq!(literal_of('a'), Literal::Char('a'));
1000        assert_eq!(literal_of("s".to_string()), Literal::Str("s".to_string()));
1001        assert_eq!(literal_of(vec![1u8, 2u8]), Literal::ByteStr(vec![1, 2]));
1002        assert_eq!(
1003            literal_of(CString::new("c").unwrap()),
1004            Literal::CStr(CString::new("c").unwrap())
1005        );
1006        assert_eq!(literal_of(()), Literal::Unit);
1007    }
1008
1009    #[test]
1010    fn apply_op_with_arity_zero_records_an_ident_node() {
1011        let mut ctx = AstContext::new_context();
1012        let lookup = OpLookup::new();
1013        ctx.apply_op(&lookup, "x", 0, Span::call_site(), Span::call_site())
1014            .unwrap();
1015        assert!(matches!(ctx.into_expr(), Expr::Ident { name, .. } if name == "x"));
1016    }
1017
1018    #[test]
1019    fn apply_op_with_the_call_operator_records_an_apply_node() {
1020        let mut ctx = AstContext::new_context();
1021        let lookup = OpLookup::new();
1022        ctx.apply_op(&lookup, "x", 0, Span::call_site(), Span::call_site())
1023            .unwrap(); // callee
1024        ctx.push_literal(1i32, Span::call_site()); // arg
1025        ctx.apply_op(&lookup, "()", 2, Span::call_site(), Span::call_site())
1026            .unwrap();
1027        match ctx.into_expr() {
1028            Expr::Apply { callee, args, .. } => {
1029                assert!(matches!(*callee, Expr::Ident { ref name, .. } if name == "x"));
1030                assert_eq!(args.len(), 1);
1031            }
1032            other => panic!("expected Apply, got {other:?}"),
1033        }
1034    }
1035
1036    #[test]
1037    fn apply_op_with_arity_two_records_an_op_node() {
1038        let mut ctx = AstContext::new_context();
1039        let lookup = OpLookup::new();
1040        ctx.push_literal(1i32, Span::call_site());
1041        ctx.push_literal(2i32, Span::call_site());
1042        ctx.apply_op(&lookup, "+", 2, Span::call_site(), Span::call_site())
1043            .unwrap();
1044        match ctx.into_expr() {
1045            Expr::Op { name, operands, .. } => {
1046                assert_eq!(name, "+");
1047                assert_eq!(operands.len(), 2);
1048            }
1049            other => panic!("expected Op, got {other:?}"),
1050        }
1051    }
1052
1053    #[test]
1054    fn apply_logical_records_a_logical_node() {
1055        let mut ctx = AstContext::new_context();
1056        ctx.push_literal(true, Span::call_site());
1057        let mut rhs = ctx.new_fragment();
1058        rhs.push_literal(false, Span::call_site());
1059        ctx.apply_logical("||", rhs, Span::call_site(), Span::call_site())
1060            .unwrap();
1061        match ctx.into_expr() {
1062            Expr::Logical { op, lhs, rhs, .. } => {
1063                assert_eq!(op, LogicalOp::Or);
1064                assert!(matches!(
1065                    *lhs,
1066                    Expr::Literal {
1067                        value: Literal::Bool(true),
1068                        ..
1069                    }
1070                ));
1071                assert!(matches!(
1072                    *rhs,
1073                    Expr::Literal {
1074                        value: Literal::Bool(false),
1075                        ..
1076                    }
1077                ));
1078            }
1079            other => panic!("expected Logical, got {other:?}"),
1080        }
1081    }
1082
1083    #[test]
1084    fn join2_records_an_if_node() {
1085        let mut ctx = AstContext::new_context();
1086        ctx.push_literal(true, Span::call_site());
1087        let mut then_fragment = ctx.new_fragment();
1088        then_fragment.push_literal(1i32, Span::call_site());
1089        let mut else_fragment = ctx.new_fragment();
1090        else_fragment.push_literal(2i32, Span::call_site());
1091        ctx.join2(
1092            then_fragment,
1093            Some(else_fragment),
1094            Span::call_site(),
1095            Span::call_site(),
1096        )
1097        .unwrap();
1098        match ctx.into_expr() {
1099            Expr::If {
1100                cond,
1101                then_branch,
1102                else_branch,
1103                ..
1104            } => {
1105                assert!(matches!(
1106                    *cond,
1107                    Expr::Literal {
1108                        value: Literal::Bool(true),
1109                        ..
1110                    }
1111                ));
1112                assert!(matches!(
1113                    *then_branch,
1114                    Expr::Literal {
1115                        value: Literal::I32(1),
1116                        ..
1117                    }
1118                ));
1119                let else_branch = else_branch.expect("explicit else was given");
1120                assert!(matches!(
1121                    *else_branch,
1122                    Expr::Literal {
1123                        value: Literal::I32(2),
1124                        ..
1125                    }
1126                ));
1127            }
1128            other => panic!("expected If, got {other:?}"),
1129        }
1130    }
1131
1132    #[test]
1133    fn join2_with_none_records_no_else_branch() {
1134        let mut ctx = AstContext::new_context();
1135        ctx.push_literal(true, Span::call_site());
1136        let mut then_fragment = ctx.new_fragment();
1137        then_fragment.push_literal(1i32, Span::call_site());
1138        ctx.join2(then_fragment, None, Span::call_site(), Span::call_site())
1139            .unwrap();
1140        match ctx.into_expr() {
1141            Expr::If { else_branch, .. } => assert!(else_branch.is_none()),
1142            other => panic!("expected If, got {other:?}"),
1143        }
1144    }
1145
1146    #[test]
1147    fn make_tuple_and_tuple_index_roundtrip() {
1148        let mut ctx = AstContext::new_context();
1149        let ambient_start = ctx.current_stack_offset();
1150        ctx.push_literal(1i32, Span::call_site());
1151        ctx.push_literal(2i32, Span::call_site());
1152        ctx.make_tuple(2, ambient_start, Span::call_site(), Span::call_site())
1153            .unwrap();
1154        assert_eq!(ctx.peek_tuple_arity(), None);
1155        ctx.tuple_index(1, Span::call_site(), Span::call_site())
1156            .unwrap();
1157        match ctx.into_expr() {
1158            Expr::TupleIndex { base, index, .. } => {
1159                assert_eq!(index, 1);
1160                assert!(matches!(*base, Expr::Tuple { ref elements, .. } if elements.len() == 2));
1161            }
1162            other => panic!("expected TupleIndex, got {other:?}"),
1163        }
1164    }
1165
1166    #[test]
1167    fn tuple_arity_is_unknown_during_syntax_only_parsing() {
1168        let mut ctx = AstContext::new_context();
1169        ctx.push_literal(5i32, Span::call_site());
1170        assert_eq!(ctx.peek_tuple_arity(), None);
1171    }
1172
1173    #[test]
1174    fn additive_binds_looser_than_multiplicative() {
1175        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1176        let expr = parser.parse_str_ast("1 + 2 * 3").unwrap();
1177        let Expr::Op { name, operands, .. } = expr else {
1178            panic!("expected Op");
1179        };
1180        assert_eq!(name, "+");
1181        assert!(matches!(
1182            operands[0],
1183            Expr::Literal {
1184                value: Literal::I32(1),
1185                ..
1186            }
1187        ));
1188        let Expr::Op {
1189            name: inner_name,
1190            operands: mul_operands,
1191            ..
1192        } = &operands[1]
1193        else {
1194            panic!("expected nested Op");
1195        };
1196        assert_eq!(inner_name, "*");
1197        assert!(matches!(
1198            mul_operands[0],
1199            Expr::Literal {
1200                value: Literal::I32(2),
1201                ..
1202            }
1203        ));
1204        assert!(matches!(
1205            mul_operands[1],
1206            Expr::Literal {
1207                value: Literal::I32(3),
1208                ..
1209            }
1210        ));
1211    }
1212
1213    #[test]
1214    fn unary_minus_and_not_are_recorded_as_arity_one_op_nodes() {
1215        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1216        let expr = parser.parse_str_ast("-1i32").unwrap();
1217        let Expr::Op { name, operands, .. } = expr else {
1218            panic!("expected Op");
1219        };
1220        assert_eq!(name, "-");
1221        assert_eq!(operands.len(), 1);
1222
1223        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1224        let expr = parser.parse_str_ast("!true").unwrap();
1225        let Expr::Op { name, operands, .. } = expr else {
1226            panic!("expected Op");
1227        };
1228        assert_eq!(name, "!");
1229        assert_eq!(operands.len(), 1);
1230    }
1231
1232    #[test]
1233    fn bare_identifier_is_an_ident_node() {
1234        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1235        let expr = parser.parse_str_ast("x").unwrap();
1236        assert!(matches!(expr, Expr::Ident { name, .. } if name == "x"));
1237    }
1238
1239    #[test]
1240    fn call_with_no_args_and_with_args() {
1241        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1242        let expr = parser.parse_str_ast("f()").unwrap();
1243        let Expr::Apply { callee, args, .. } = expr else {
1244            panic!("expected Apply");
1245        };
1246        assert!(matches!(*callee, Expr::Ident { ref name, .. } if name == "f"));
1247        assert_eq!(args.len(), 0);
1248
1249        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1250        let expr = parser.parse_str_ast("f(1i32, 2i32)").unwrap();
1251        let Expr::Apply { args, .. } = expr else {
1252            panic!("expected Apply");
1253        };
1254        assert_eq!(args.len(), 2);
1255    }
1256
1257    #[test]
1258    fn chained_calls_nest_apply_nodes() {
1259        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1260        let expr = parser.parse_str_ast("f()()").unwrap();
1261        let Expr::Apply { callee, args, .. } = expr else {
1262            panic!("expected outer Apply");
1263        };
1264        assert_eq!(args.len(), 0);
1265        assert!(matches!(*callee, Expr::Apply { .. }));
1266    }
1267
1268    #[test]
1269    fn unit_grouping_and_tuples() {
1270        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1271        assert!(matches!(
1272            parser.parse_str_ast("()").unwrap(),
1273            Expr::Literal {
1274                value: Literal::Unit,
1275                ..
1276            }
1277        ));
1278
1279        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1280        // Grouping: (1i32 + 2i32) has no Tuple wrapper, just the inner Op.
1281        assert!(matches!(
1282            parser.parse_str_ast("(1i32 + 2i32)").unwrap(),
1283            Expr::Op { .. }
1284        ));
1285
1286        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1287        let expr = parser.parse_str_ast("(1i32,)").unwrap();
1288        assert!(matches!(expr, Expr::Tuple { ref elements, .. } if elements.len() == 1));
1289
1290        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1291        let expr = parser.parse_str_ast("(1i32, 2i32, 3i32)").unwrap();
1292        assert!(matches!(expr, Expr::Tuple { ref elements, .. } if elements.len() == 3));
1293    }
1294
1295    #[test]
1296    fn tuple_index_single_and_chained() {
1297        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1298        let expr = parser.parse_str_ast("(1i32, 2i32).1").unwrap();
1299        let Expr::TupleIndex { base, index, .. } = expr else {
1300            panic!("expected TupleIndex");
1301        };
1302        assert_eq!(index, 1);
1303        assert!(matches!(*base, Expr::Tuple { ref elements, .. } if elements.len() == 2));
1304
1305        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1306        // Chained `.0.1` tokenizes as one float literal `0.1` split back into two indices.
1307        let expr = parser.parse_str_ast("((1i32, 2i32), 3i32).0.1").unwrap();
1308        let Expr::TupleIndex { base, index, .. } = expr else {
1309            panic!("expected outer TupleIndex");
1310        };
1311        assert_eq!(index, 1);
1312        assert!(matches!(*base, Expr::TupleIndex { index: 0, .. }));
1313    }
1314
1315    #[test]
1316    fn tuple_index_on_a_call_result_still_builds_a_tree() {
1317        // Deferred validation: DynSegmentContext would reject an out-of-range index at parse
1318        // time, but AstContext can't know a call's return arity without type inference, so it
1319        // must still build a tree here rather than fail to parse.
1320        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1321        let expr = parser.parse_str_ast("f().5").unwrap();
1322        assert!(matches!(expr, Expr::TupleIndex { index: 5, .. }));
1323    }
1324
1325    #[test]
1326    fn if_without_else_has_no_else_branch() {
1327        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1328        let expr = parser.parse_str_ast("if true { 1i32 }").unwrap();
1329        let Expr::If {
1330            cond,
1331            then_branch,
1332            else_branch,
1333            ..
1334        } = expr
1335        else {
1336            panic!("expected If");
1337        };
1338        assert!(matches!(
1339            *cond,
1340            Expr::Literal {
1341                value: Literal::Bool(true),
1342                ..
1343            }
1344        ));
1345        assert!(matches!(
1346            *then_branch,
1347            Expr::Literal {
1348                value: Literal::I32(1),
1349                ..
1350            }
1351        ));
1352        assert!(else_branch.is_none());
1353    }
1354
1355    #[test]
1356    fn if_else_and_else_if_chain() {
1357        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1358        let expr = parser
1359            .parse_str_ast("if true { 1i32 } else { 2i32 }")
1360            .unwrap();
1361        let Expr::If { else_branch, .. } = expr else {
1362            panic!("expected If");
1363        };
1364        assert!(matches!(
1365            else_branch.as_deref(),
1366            Some(Expr::Literal {
1367                value: Literal::I32(2),
1368                ..
1369            })
1370        ));
1371
1372        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1373        let expr = parser
1374            .parse_str_ast("if true { 1i32 } else if false { 2i32 } else { 3i32 }")
1375            .unwrap();
1376        let Expr::If { else_branch, .. } = expr else {
1377            panic!("expected outer If");
1378        };
1379        assert!(matches!(else_branch.as_deref(), Some(Expr::If { .. })));
1380    }
1381
1382    #[test]
1383    fn logical_or_is_not_desugared_to_if() {
1384        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1385        let expr = parser.parse_str_ast("a || b").unwrap();
1386        let Expr::Logical { op, lhs, rhs, .. } = expr else {
1387            panic!("expected Logical");
1388        };
1389        assert_eq!(op, LogicalOp::Or);
1390        assert!(matches!(*lhs, Expr::Ident { ref name, .. } if name == "a"));
1391        assert!(matches!(*rhs, Expr::Ident { ref name, .. } if name == "b"));
1392    }
1393
1394    #[test]
1395    fn logical_and_is_not_desugared_to_if() {
1396        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1397        let expr = parser.parse_str_ast("a && b").unwrap();
1398        let Expr::Logical { op, .. } = expr else {
1399            panic!("expected Logical");
1400        };
1401        assert_eq!(op, LogicalOp::And);
1402    }
1403
1404    #[test]
1405    fn logical_mixes_with_comparison_at_the_right_precedence() {
1406        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1407        // `&&` binds looser than `==`, so this is `(a == 1i32) && (b == 2i32)`.
1408        let expr = parser.parse_str_ast("a == 1i32 && b == 2i32").unwrap();
1409        let Expr::Logical { op, lhs, rhs, .. } = expr else {
1410            panic!("expected Logical");
1411        };
1412        assert_eq!(op, LogicalOp::And);
1413        assert!(matches!(*lhs, Expr::Op { ref name, .. } if name == "=="));
1414        assert!(matches!(*rhs, Expr::Op { ref name, .. } if name == "=="));
1415    }
1416
1417    #[test]
1418    fn for_text_produces_a_span_whose_source_text_matches() {
1419        let span = ExprSpan::for_text("i64");
1420        assert_eq!(span.start.source_text().as_deref(), Some("i64"));
1421        assert_eq!(span.end.source_text().as_deref(), Some("i64"));
1422    }
1423
1424    #[test]
1425    fn for_text_works_for_a_string_literal() {
1426        let span = ExprSpan::for_text("\"hello\"");
1427        assert_eq!(span.start.source_text().as_deref(), Some("\"hello\""));
1428    }
1429
1430    #[test]
1431    fn make_array_pushes_one_array_node_holding_the_elements_in_source_order() {
1432        let mut ctx = AstContext::new_context();
1433        let ambient_start = ctx.current_stack_offset();
1434        ctx.push_literal(1i32, Span::call_site());
1435        ctx.push_literal(2i32, Span::call_site());
1436        ctx.make_array(2, ambient_start, Span::call_site(), Span::call_site())
1437            .unwrap();
1438        match ctx.into_expr() {
1439            Expr::Array { elements, .. } => {
1440                assert!(matches!(
1441                    elements[0],
1442                    Expr::Literal {
1443                        value: Literal::I32(1),
1444                        ..
1445                    }
1446                ));
1447                assert!(matches!(
1448                    elements[1],
1449                    Expr::Literal {
1450                        value: Literal::I32(2),
1451                        ..
1452                    }
1453                ));
1454            }
1455            other => panic!("expected Array, got {other:?}"),
1456        }
1457    }
1458
1459    #[test]
1460    fn array_literal_records_elements_in_source_order_with_an_enclosing_span() {
1461        let source = "[0i32, 1i32, 2i32]";
1462        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1463        let expr = parser.parse_str_ast(source).unwrap();
1464        let Expr::Array { elements, span, .. } = expr else {
1465            panic!("expected Array");
1466        };
1467        assert_eq!(elements.len(), 3);
1468        for (index, element) in elements.iter().enumerate() {
1469            let Expr::Literal { value, .. } = element else {
1470                panic!("expected a literal element");
1471            };
1472            assert_eq!(*value, Literal::I32(i32::try_from(index).unwrap()));
1473        }
1474        assert_eq!(span.start.source_text().as_deref(), Some(source));
1475        assert_eq!(span.end.source_text().as_deref(), Some(source));
1476    }
1477
1478    #[test]
1479    fn nested_array_literal_nests_array_nodes() {
1480        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1481        let expr = parser.parse_str_ast("[[0i32], [1i32]]").unwrap();
1482        let Expr::Array { elements, .. } = expr else {
1483            panic!("expected outer Array");
1484        };
1485        assert_eq!(elements.len(), 2);
1486        for element in &elements {
1487            assert!(matches!(element, Expr::Array { elements, .. } if elements.len() == 1));
1488        }
1489    }
1490
1491    #[test]
1492    fn typed_array_literal_still_builds_an_array_node() {
1493        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1494        let expr = parser.parse_str_ast("[0, 1]: [i32]").unwrap();
1495        let Expr::Array {
1496            elements,
1497            type_annotation,
1498            annotation_span,
1499            ..
1500        } = expr
1501        else {
1502            panic!("expected Array");
1503        };
1504        assert_eq!(elements.len(), 2);
1505        assert!(annotation_span.is_some());
1506        match type_annotation {
1507            Some(TypeExpr::Array { element, .. }) => {
1508                assert!(matches!(*element, TypeExpr::Named { ref name, .. } if name == "i32"));
1509            }
1510            other => panic!("expected an array type annotation, got {other:?}"),
1511        }
1512    }
1513
1514    #[test]
1515    fn typed_empty_array_literal_builds_an_empty_array_node() {
1516        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1517        let expr = parser.parse_str_ast("[]: [i32]").unwrap();
1518        assert!(matches!(
1519            expr,
1520            Expr::Array {
1521                ref elements,
1522                type_annotation: Some(_),
1523                annotation_span: Some(_),
1524                ..
1525            } if elements.is_empty()
1526        ));
1527    }
1528
1529    #[test]
1530    fn typed_array_literal_preserves_the_exact_annotation_span() {
1531        let source = "[1.0, 42.5]: [f64]";
1532        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1533        let expr = parser.parse_str_ast(source).unwrap();
1534        let Expr::Array {
1535            type_annotation: Some(_),
1536            annotation_span: Some(annotation_span),
1537            ..
1538        } = expr
1539        else {
1540            panic!("expected Array");
1541        };
1542        let start = annotation_span.start.start();
1543        let end = annotation_span.end.end();
1544        assert_eq!(&source[start.column..end.column], ": [f64]");
1545    }
1546
1547    #[test]
1548    fn array_literals_build_nodes_in_call_argument_and_tuple_positions() {
1549        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1550        let expr = parser.parse_str_ast("f([1i32], (2i32, [3i32]))").unwrap();
1551        let Expr::Apply { args, .. } = expr else {
1552            panic!("expected Apply");
1553        };
1554        assert!(matches!(args[0], Expr::Array { ref elements, .. } if elements.len() == 1));
1555        let Expr::Tuple { elements, .. } = &args[1] else {
1556            panic!("expected a tuple argument");
1557        };
1558        assert!(matches!(elements[1], Expr::Array { .. }));
1559    }
1560
1561    #[test]
1562    fn array_literals_build_nodes_in_if_branch_and_closure_body_positions() {
1563        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1564        let expr = parser
1565            .parse_str_ast("if true { [1i32] } else { [2i32] }")
1566            .unwrap();
1567        let Expr::If {
1568            then_branch,
1569            else_branch,
1570            ..
1571        } = expr
1572        else {
1573            panic!("expected If");
1574        };
1575        assert!(matches!(*then_branch, Expr::Array { .. }));
1576        assert!(matches!(else_branch.as_deref(), Some(Expr::Array { .. })));
1577
1578        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1579        let expr = parser.parse_str_ast("|x: i32| [x]").unwrap();
1580        let Expr::Closure { body, .. } = expr else {
1581            panic!("expected Closure");
1582        };
1583        assert!(matches!(*body, Expr::Array { .. }));
1584    }
1585
1586    #[test]
1587    fn array_literal_with_a_tuple_element_still_builds_a_tree() {
1588        // Syntax-only parsing records tuple elements without resolving their shapes.
1589        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1590        let expr = parser.parse_str_ast("[(1i32, 2i32)]").unwrap();
1591        assert!(matches!(expr, Expr::Array { ref elements, .. } if elements.len() == 1));
1592    }
1593
1594    #[test]
1595    fn ast_context_still_rejects_an_empty_array_literal() {
1596        // The empty-literal diagnostic is a grammar-level rejection (see #212), so it applies to
1597        // every `ParserContext`, not only the one that resolves runtime types.
1598        let mut parser = Parser::<AstContext>::new(OpLookup::new());
1599        let err = parser
1600            .parse_str_ast("[]")
1601            .expect_err("an empty array literal has no inferable element type");
1602        assert!(
1603            err.message().contains("empty array"),
1604            "got: {}",
1605            err.message()
1606        );
1607    }
1608}