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