Skip to main content

cel_parser/
parser_context.rs

1//! `ParserContext`: the pluggable target a CEL grammar production emits into.
2//!
3//! The recursive-descent grammar in `lib.rs` is generic over `C: ParserContext` so the same
4//! grammar can drive different backends without duplicating it. [`DynSegmentContext`] is the
5//! first implementation: it reproduces exactly what `CELParser` did before this trait existed,
6//! wrapping a [`DynSegment`] one-for-one. [`crate::AstContext`] retains syntax for the language
7//! server, formatter, and retained compiler.
8
9use cel_runtime::DynSegment;
10use proc_macro2::Span;
11
12use crate::ExprSpan;
13use crate::ast::ClosureParam;
14use crate::op_table::OpLookup;
15use crate::type_expr::TypeExpr;
16
17/// Bundles the optional type-ascription inputs for array construction.
18///
19/// A [`ParserContext`] implementation receives one of these in
20/// [`ParserContext::make_annotated_array`] and decides how to consume it: an implementation that
21/// needs runtime array metadata immediately resolves it with
22/// [`resolve_element_type`](Self::resolve_element_type); one that defers semantic checking keeps
23/// the syntax with [`into_parts`](Self::into_parts).
24///
25/// # Examples
26///
27/// ```rust
28/// use cel_parser::{ExprSpan, TypeExpr};
29/// use cel_parser::parser_context::AnnotatedArray;
30/// use proc_macro2::Span;
31///
32/// fn ignore(_: &TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
33///     unreachable!("this example only inspects stored syntax")
34/// }
35///
36/// let span = ExprSpan {
37///     start: Span::call_site(),
38///     end: Span::call_site(),
39/// };
40/// let mut resolve = ignore;
41/// let annotation = AnnotatedArray::new(
42///     &mut resolve,
43///     Some(TypeExpr::Array {
44///         element: Box::new(TypeExpr::Named {
45///             name: "i32".to_string(),
46///             args: Vec::new(),
47///             span,
48///         }),
49///         span,
50///     }),
51///     Some(span),
52/// );
53///
54/// assert!(matches!(annotation.type_annotation(), Some(TypeExpr::Array { .. })));
55/// assert!(annotation.annotation_span().is_some());
56/// ```
57pub struct AnnotatedArray<'a> {
58    resolve_array_type: &'a mut dyn FnMut(&TypeExpr) -> crate::Result<crate::ResolvedArrayType>,
59    type_annotation: Option<TypeExpr>,
60    annotation_span: Option<ExprSpan>,
61}
62
63impl<'a> AnnotatedArray<'a> {
64    /// Creates a bundle from the parsed array annotation inputs.
65    ///
66    /// `type_annotation` is the syntax written after `:` (or `None` for an unannotated literal),
67    /// `annotation_span` covers `: type_expr`, and `resolve_array_type` resolves the annotation
68    /// against the parser's configured type resolver on demand.
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// use cel_parser::{ExprSpan, TypeExpr};
74    /// use cel_parser::parser_context::AnnotatedArray;
75    /// use proc_macro2::Span;
76    ///
77    /// fn ignore(_: &TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
78    ///     unreachable!("this example only bundles parsed inputs")
79    /// }
80    ///
81    /// let span = ExprSpan {
82    ///     start: Span::call_site(),
83    ///     end: Span::call_site(),
84    /// };
85    /// let mut resolve = ignore;
86    /// let annotation = AnnotatedArray::new(
87    ///     &mut resolve,
88    ///     Some(TypeExpr::Array {
89    ///         element: Box::new(TypeExpr::Named {
90    ///             name: "i32".to_string(),
91    ///             args: Vec::new(),
92    ///             span,
93    ///         }),
94    ///         span,
95    ///     }),
96    ///     Some(span),
97    /// );
98    ///
99    /// assert!(annotation.annotation_span().is_some());
100    /// ```
101    pub fn new(
102        resolve_array_type: &'a mut dyn FnMut(&TypeExpr) -> crate::Result<crate::ResolvedArrayType>,
103        type_annotation: Option<TypeExpr>,
104        annotation_span: Option<ExprSpan>,
105    ) -> Self {
106        Self {
107            resolve_array_type,
108            type_annotation,
109            annotation_span,
110        }
111    }
112
113    /// Returns the unresolved annotation syntax, or `None` for an unannotated array literal.
114    ///
115    /// # Examples
116    ///
117    /// ```rust
118    /// use cel_parser::{ExprSpan, TypeExpr};
119    /// use cel_parser::parser_context::AnnotatedArray;
120    /// use proc_macro2::Span;
121    ///
122    /// fn ignore(_: &TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
123    ///     unreachable!("this example only reads the stored annotation")
124    /// }
125    ///
126    /// let span = ExprSpan {
127    ///     start: Span::call_site(),
128    ///     end: Span::call_site(),
129    /// };
130    /// let mut resolve = ignore;
131    /// let annotation = AnnotatedArray::new(
132    ///     &mut resolve,
133    ///     Some(TypeExpr::Array {
134    ///         element: Box::new(TypeExpr::Named {
135    ///             name: "i32".to_string(),
136    ///             args: Vec::new(),
137    ///             span,
138    ///         }),
139    ///         span,
140    ///     }),
141    ///     Some(span),
142    /// );
143    ///
144    /// match annotation.type_annotation() {
145    ///     Some(TypeExpr::Array { element, .. }) => assert!(matches!(
146    ///         element.as_ref(),
147    ///         TypeExpr::Named { name, .. } if name == "i32"
148    ///     )),
149    ///     other => panic!("expected [i32], got {other:?}"),
150    /// }
151    /// ```
152    #[must_use]
153    pub fn type_annotation(&self) -> Option<&TypeExpr> {
154        self.type_annotation.as_ref()
155    }
156
157    /// Returns the span covering `: type_expr`, or `None` for an unannotated array literal.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use cel_parser::ExprSpan;
163    /// use cel_parser::parser_context::AnnotatedArray;
164    /// use proc_macro2::Span;
165    ///
166    /// fn ignore(_: &cel_parser::TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
167    ///     unreachable!("this example only reads the stored span")
168    /// }
169    ///
170    /// fn ignore_none(
171    ///     _: &cel_parser::TypeExpr,
172    /// ) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
173    ///     unreachable!("this example only checks the None case")
174    /// }
175    ///
176    /// let span = ExprSpan {
177    ///     start: Span::call_site(),
178    ///     end: Span::call_site(),
179    /// };
180    /// let mut resolve = ignore;
181    /// let annotation = AnnotatedArray::new(
182    ///     &mut resolve,
183    ///     None,
184    ///     Some(span),
185    /// );
186    ///
187    /// assert!(annotation.annotation_span().is_some());
188    /// let mut without_span = ignore_none;
189    /// assert!(
190    ///     AnnotatedArray::new(&mut without_span, None, None)
191    ///         .annotation_span()
192    ///         .is_none()
193    /// );
194    /// ```
195    #[must_use]
196    pub fn annotation_span(&self) -> Option<ExprSpan> {
197        self.annotation_span
198    }
199
200    /// Resolves the declared array element metadata when a type ascription is present.
201    ///
202    /// - Postcondition: returns `Ok(None)` exactly when
203    ///   [`type_annotation`](Self::type_annotation) is `None`.
204    ///
205    /// # Errors
206    ///
207    /// Returns `Err` if the annotation names an unknown type, does not name a complete array
208    /// type, or has an unrepresentable canonical layout.
209    ///
210    /// - Complexity: O(1) bundling overhead plus the configured annotation resolver's cost.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// use cel_parser::{ExprSpan, ResolvedArrayType, TypeExpr};
216    /// use cel_parser::parser_context::AnnotatedArray;
217    /// use cel_runtime::ValueType;
218    /// use proc_macro2::Span;
219    ///
220    /// fn resolve_i32(_: &TypeExpr) -> cel_parser::Result<ResolvedArrayType> {
221    ///     Ok(ResolvedArrayType::from_element_type(
222    ///         ValueType::leaf::<i32>(),
223    ///     ))
224    /// }
225    ///
226    /// let span = ExprSpan {
227    ///     start: Span::call_site(),
228    ///     end: Span::call_site(),
229    /// };
230    /// let mut resolve = resolve_i32;
231    /// let mut annotation = AnnotatedArray::new(
232    ///     &mut resolve,
233    ///     Some(TypeExpr::Array {
234    ///         element: Box::new(TypeExpr::Named {
235    ///             name: "i32".to_string(),
236    ///             args: Vec::new(),
237    ///             span,
238    ///         }),
239    ///         span,
240    ///     }),
241    ///     Some(span),
242    /// );
243    ///
244    /// let element_type = annotation.resolve_element_type().unwrap().unwrap();
245    /// assert_eq!(element_type.type_name(), "i32");
246    /// ```
247    pub fn resolve_element_type(&mut self) -> crate::Result<Option<cel_runtime::ValueType>> {
248        self.type_annotation
249            .as_ref()
250            .map(|type_annotation| {
251                (self.resolve_array_type)(type_annotation)
252                    .map(|resolved| resolved.into_element_type())
253            })
254            .transpose()
255    }
256
257    /// Returns the parsed syntax-level annotation inputs unchanged.
258    ///
259    /// # Examples
260    ///
261    /// ```rust
262    /// use cel_parser::{ExprSpan, TypeExpr};
263    /// use cel_parser::parser_context::AnnotatedArray;
264    /// use proc_macro2::Span;
265    ///
266    /// fn ignore(_: &TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
267    ///     unreachable!("this example only unpacks stored parts")
268    /// }
269    ///
270    /// let span = ExprSpan {
271    ///     start: Span::call_site(),
272    ///     end: Span::call_site(),
273    /// };
274    /// let mut resolve = ignore;
275    /// let annotation = AnnotatedArray::new(
276    ///     &mut resolve,
277    ///     Some(TypeExpr::Array {
278    ///         element: Box::new(TypeExpr::Named {
279    ///             name: "i32".to_string(),
280    ///             args: Vec::new(),
281    ///             span,
282    ///         }),
283    ///         span,
284    ///     }),
285    ///     Some(span),
286    /// );
287    ///
288    /// let (type_annotation, annotation_span) = annotation.into_parts();
289    /// assert!(annotation_span.is_some());
290    /// match type_annotation {
291    ///     Some(TypeExpr::Array { element, .. }) => assert!(matches!(
292    ///         element.as_ref(),
293    ///         TypeExpr::Named { name, .. } if name == "i32"
294    ///     )),
295    ///     other => panic!("expected [i32], got {other:?}"),
296    /// }
297    /// ```
298    #[must_use]
299    pub fn into_parts(self) -> (Option<TypeExpr>, Option<ExprSpan>) {
300        (self.type_annotation, self.annotation_span)
301    }
302}
303
304/// The pluggable target a grammar production emits into.
305///
306/// Each method mirrors one operation the grammar in `lib.rs` needs. Implementations decide what
307/// "emitting" means: [`DynSegmentContext`] builds stack-machine operations;
308/// [`crate::AstContext`] records syntax without executing it.
309///
310/// Emission costs depend on the selected context, including its storage, descriptor traversal,
311/// literal cloning and host callbacks.
312///
313/// # Examples
314/// See [`Self::apply_op`] for runtime emission and [`Self::apply_member`] for syntax construction.
315pub trait ParserContext: Sized {
316    /// Returns whether closure annotations must resolve while parsing rather than remain syntax.
317    ///
318    /// # Examples
319    /// ```
320    /// use cel_parser::{AstContext, parser_context::ParserContext};
321    /// assert!(!AstContext::resolves_closure_types());
322    /// ```
323    fn resolves_closure_types() -> bool {
324        true
325    }
326
327    /// Creates a fresh, empty context with no operations recorded yet.
328    ///
329    /// # Examples
330    /// ```
331    /// use cel_parser::{DynSegmentContext, ParserContext};
332    /// assert_eq!(DynSegmentContext::new_context().current_stack_offset(), 0);
333    /// ```
334    fn new_context() -> Self;
335
336    /// Creates an empty fragment for building an alternate branch (one side of a
337    /// short-circuiting `||`/`&&`, or an `if`/`else` branch), independent of `self`.
338    ///
339    /// - Precondition: `self` matches whatever precondition the implementation's equivalent of
340    ///   `DynSegment::new_fragment` requires (for `DynSegmentContext`, a condition value already
341    ///   present).
342    ///
343    /// # Examples
344    /// See [`Self::apply_logical`] for an independent right-hand-side fragment.
345    fn new_fragment(&self) -> Self;
346
347    /// Pushes a literal value with the source span of the token it came from.
348    ///
349    /// - Precondition: the selected context supports `T` as a literal type.
350    ///
351    /// # Examples
352    /// See [`Self::apply_op`] for literal operands.
353    fn push_literal<T: 'static + Clone>(&mut self, value: T, span: Span);
354
355    /// Applies a named operator or zero-arity identifier lookup, using `op_lookup` to resolve it
356    /// against whatever this context currently holds.
357    ///
358    /// # Errors
359    ///
360    /// Returns `Err` if `op_lookup` cannot resolve `name` for `arity` operands.
361    ///
362    /// # Examples
363    /// ```
364    /// use cel_parser::{DynSegmentContext, OpLookup, ParserContext};
365    /// use proc_macro2::Span;
366    /// let mut context = DynSegmentContext::new_context();
367    /// let span = Span::call_site();
368    /// context.push_literal(40i32, span);
369    /// context.push_literal(2i32, span);
370    /// context.apply_op(&OpLookup::new(), "+", 2, span, span).unwrap();
371    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 42);
372    /// ```
373    fn apply_op(
374        &mut self,
375        op_lookup: &OpLookup,
376        name: &str,
377        arity: usize,
378        start: Span,
379        end: Span,
380    ) -> crate::Result<()>;
381
382    /// Applies a direct named operation to the receiver and explicit arguments in source order.
383    ///
384    /// `None` preserves `.name`; `Some(0)` preserves `.name()`. Both consume one receiver.
385    /// `name_span` identifies the member label for diagnostics; `start`/`end` cover the expression.
386    ///
387    /// - Precondition: the receiver and `explicit_args.unwrap_or(0)` arguments are emitted.
388    /// - Complexity: O(n) for n operands, plus operation lookup in runtime contexts.
389    ///
390    /// # Errors
391    /// Returns an error if a runtime context cannot resolve the operation.
392    ///
393    /// # Examples
394    /// ```
395    /// use cel_parser::{AstContext, ExprSpan, OpLookup};
396    /// use cel_parser::parser_context::ParserContext;
397    /// use proc_macro2::Span;
398    /// let mut context = AstContext::new_context();
399    /// let span = Span::call_site();
400    /// context.push_literal(1i32, span);
401    /// context.apply_member(&OpLookup::new(), "custom", ExprSpan { start: span, end: span },
402    ///     None, span, span).unwrap();
403    /// ```
404    fn apply_member(
405        &mut self,
406        lookup: &OpLookup,
407        name: &str,
408        name_span: ExprSpan,
409        explicit_args: Option<usize>,
410        start: Span,
411        end: Span,
412    ) -> crate::Result<()>;
413
414    /// Constructs a record from named fields emitted exactly once in source order.
415    ///
416    /// - Precondition: one value per field follows `ambient_start` in this context.
417    /// - Complexity: O(f² + f * d + r) for f fields, descriptor depth d and r schemas.
418    ///
419    /// # Errors
420    /// Runtime contexts reject unknown schemas, invalid field sets, mismatched types,
421    /// or constructor emitter failures; syntax-only contexts preserve unresolved names.
422    ///
423    /// # Examples
424    /// See [`OpLookup::construct_record`].
425    #[allow(clippy::too_many_arguments)]
426    fn make_record(
427        &mut self,
428        lookup: &OpLookup,
429        name: &str,
430        name_span: ExprSpan,
431        fields: Vec<(String, ExprSpan)>,
432        ambient_start: usize,
433        start: Span,
434        end: Span,
435    ) -> crate::Result<()>;
436
437    /// Applies a short-circuiting logical operator (`"||"` or `"&&"`), consuming a leading
438    /// condition value already present on `self` and folding in `rhs`, the already-parsed
439    /// right-hand-side fragment.
440    ///
441    /// - Precondition: `name` is `"||"` or `"&&"`, and `rhs` produces exactly one value.
442    ///
443    /// # Errors
444    ///
445    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
446    /// return `Err` if the leading condition value isn't a `bool`. Implementations that defer
447    /// type validation to a later phase (e.g. [`crate::ast::AstContext`]) never return `Err`
448    /// here.
449    ///
450    /// # Examples
451    /// ```
452    /// use cel_parser::{DynSegmentContext, ParserContext};
453    /// use proc_macro2::Span;
454    /// let mut context = DynSegmentContext::new_context();
455    /// let span = Span::call_site();
456    /// context.push_literal(false, span);
457    /// let mut rhs = context.new_fragment();
458    /// rhs.push_literal(true, span);
459    /// context.apply_logical("&&", rhs, span, span).unwrap();
460    /// assert!(!context.into_inner().call0::<bool>().unwrap());
461    /// ```
462    fn apply_logical(&mut self, name: &str, rhs: Self, start: Span, end: Span)
463    -> crate::Result<()>;
464
465    /// Joins a previously-built fragment into `self`, consuming a leading condition value already
466    /// present on `self`. `then_fragment`'s contribution is used when the condition is `true`;
467    /// `else_fragment`'s when `false`, or `None` if the source had no `else`/`else if` at all.
468    /// `start`/`end` cover the whole `if`/`else` construct.
469    ///
470    /// - Precondition: neither fragment takes arguments, and each produces exactly one value.
471    ///
472    /// # Errors
473    ///
474    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
475    /// return `Err` if the leading condition value isn't a `bool` or if the fragments' produced
476    /// types don't match — including when `else_fragment` is `None`, which such implementations
477    /// treat as an implicit `()` fragment (so a non-`()` then-branch with no `else` is still an
478    /// error). Implementations that defer type validation to a later phase (e.g.
479    /// [`crate::ast::AstContext`]) never return `Err` here, and record whether `else_fragment` was
480    /// `None` directly (see [`crate::Expr::If`]) instead of synthesizing anything.
481    ///
482    /// # Examples
483    /// ```
484    /// use cel_parser::{DynSegmentContext, ParserContext};
485    /// use proc_macro2::Span;
486    /// let mut context = DynSegmentContext::new_context();
487    /// let span = Span::call_site();
488    /// context.push_literal(true, span);
489    /// let mut yes = context.new_fragment();
490    /// yes.push_literal(42i32, span);
491    /// let mut no = context.new_fragment();
492    /// no.push_literal(0i32, span);
493    /// context.join2(yes, Some(no), span, span).unwrap();
494    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 42);
495    /// ```
496    fn join2(
497        &mut self,
498        then_fragment: Self,
499        else_fragment: Option<Self>,
500        start: Span,
501        end: Span,
502    ) -> anyhow::Result<()>;
503
504    /// Combines the last `n` emitted values into a single tuple value. `start`/`end` cover the
505    /// whole `(...)` construct.
506    ///
507    /// # Errors
508    /// Returns an error if the tuple's frame or canonical layout cannot be represented.
509    ///
510    /// # Examples
511    /// ```
512    /// use cel_parser::{DynSegmentContext, ParserContext};
513    /// use proc_macro2::Span;
514    /// let mut context = DynSegmentContext::new_context();
515    /// let span = Span::call_site();
516    /// let ambient = context.current_stack_offset();
517    /// context.push_literal(42i32, span);
518    /// context.push_literal(false, span);
519    /// context.make_tuple(2, ambient, span, span).unwrap();
520    /// assert_eq!(context.peek_tuple_arity(), Some(2));
521    /// context.tuple_index(0, span, span).unwrap();
522    /// assert_eq!(context.into_inner().call0::<i32>().unwrap(), 42);
523    /// ```
524    fn make_tuple(
525        &mut self,
526        n: usize,
527        ambient_start: usize,
528        start: Span,
529        end: Span,
530    ) -> crate::Result<()>;
531
532    /// Combines the last `n` emitted values into a single homogeneous array value. `start`/`end`
533    /// cover the whole `[...]` construct, and `ambient_start` is the stack offset the first
534    /// element was emitted at (as reported by [`Self::current_stack_offset`] before parsing it).
535    ///
536    /// - Precondition: `n >= 1` and exactly `n` values have been emitted since `ambient_start`.
537    ///
538    /// # Errors
539    ///
540    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
541    /// return `Err` if the elements' complete recursive runtime shapes differ, or if the array's storage
542    /// layout cannot be represented. Implementations that defer type validation to a later phase
543    /// (e.g. [`crate::ast::AstContext`]) never return `Err` here.
544    ///
545    /// # Examples
546    /// ```
547    /// use cel_parser::{AstContext, Expr, ParserContext};
548    /// use proc_macro2::Span;
549    /// let mut context = AstContext::new_context();
550    /// let span = Span::call_site();
551    /// let ambient = context.current_stack_offset();
552    /// context.push_literal(1i32, span);
553    /// context.push_literal(2i32, span);
554    /// context.make_array(2, ambient, span, span).unwrap();
555    /// assert!(matches!(context.into_expr(), Expr::Array { elements, .. } if elements.len() == 2));
556    /// ```
557    fn make_array(
558        &mut self,
559        n: usize,
560        ambient_start: usize,
561        start: Span,
562        end: Span,
563    ) -> crate::Result<()>;
564
565    /// Combines the last `n` emitted values into a single array value, honoring `annotation`'s
566    /// optional type ascription.
567    ///
568    /// This is the method the grammar actually calls for every array literal; [`Self::make_array`]
569    /// is the unannotated special case an implementation may delegate to. There is deliberately
570    /// no default implementation: an annotation changes what the literal means (`[]: [Celsius]`
571    /// is an empty array *of a specific type*), so an implementation that silently dropped it
572    /// would misreport the literal's type rather than fail to compile.
573    ///
574    /// An implementation that needs runtime array metadata immediately (e.g.
575    /// [`DynSegmentContext`]) calls [`AnnotatedArray::resolve_element_type`]; one that defers
576    /// semantic checking (e.g. [`crate::ast::AstContext`]) records the syntax returned by
577    /// [`AnnotatedArray::into_parts`].
578    ///
579    /// - Precondition: exactly `n` values have been emitted since `ambient_start`.
580    ///
581    /// # Errors
582    ///
583    /// Returns `Err` under [`Self::make_array`]'s conditions, and additionally — for an
584    /// implementation that resolves the annotation here — if the annotation names an unknown
585    /// type, does not name a complete array type, or disagrees with the collected elements.
586    ///
587    /// # Examples
588    /// See [`AnnotatedArray::resolve_element_type`] for annotation resolution and
589    /// [`Self::make_array`] for element emission.
590    fn make_annotated_array(
591        &mut self,
592        n: usize,
593        ambient_start: usize,
594        annotation: AnnotatedArray<'_>,
595        start: Span,
596        end: Span,
597    ) -> crate::Result<()>;
598
599    /// Returns the known tuple arity, or `None` for a non-tuple or unresolved value.
600    ///
601    /// # Examples
602    /// See [`Self::make_tuple`] for a resolved two-field tuple.
603    fn peek_tuple_arity(&self) -> Option<usize>;
604
605    /// Replaces the tuple on top with its `index`-th element. `start`/`end` cover the base
606    /// expression through the index token.
607    ///
608    /// # Errors
609    /// Returns an error if a resolved value is not a tuple, its index is out of bounds,
610    /// or the selected field's transfer frame cannot be represented.
611    /// Syntax-only contexts retain the index without imposing semantic bounds.
612    ///
613    /// # Examples
614    /// See [`Self::make_tuple`] for selection from a resolved tuple.
615    fn tuple_index(&mut self, index: usize, start: Span, end: Span) -> crate::Result<()>;
616
617    /// Returns the current stack offset, used to compute tuple layouts.
618    ///
619    /// # Examples
620    /// See [`Self::make_tuple`] for recording the offset before emitting fields.
621    fn current_stack_offset(&self) -> usize;
622
623    /// Applies a cast (`expr as Type`), consuming the operand already present on `self`
624    /// and replacing it with the converted value. `type_name` is the target type's bare
625    /// identifier text (e.g. `"i32"`, `"bool"`, `"String"`), unresolved by the grammar itself.
626    ///
627    /// # Errors
628    ///
629    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
630    /// return `Err` if `type_name` isn't a recognized cast-target type, or if no conversion
631    /// from the operand's current type to it is registered. Implementations that defer type
632    /// validation to a later phase (e.g. [`crate::ast::AstContext`]) never return `Err` here.
633    ///
634    /// # Examples
635    /// ```
636    /// use cel_parser::{DynSegmentContext, OpLookup, ParserContext};
637    /// use proc_macro2::Span;
638    /// let mut context = DynSegmentContext::new_context();
639    /// let span = Span::call_site();
640    /// context.push_literal(7i32, span);
641    /// context.apply_cast(&OpLookup::new(), "i64", span, span).unwrap();
642    /// assert_eq!(context.output_type_id(), Some(std::any::TypeId::of::<i64>()));
643    /// assert_eq!(context.into_inner().call0::<i64>().unwrap(), 7);
644    /// ```
645    fn apply_cast(
646        &mut self,
647        op_lookup: &OpLookup,
648        type_name: &str,
649        start: Span,
650        end: Span,
651    ) -> crate::Result<()>;
652
653    /// Packages a fully-parsed, independent nested context — the body of a closure literal — as
654    /// a value pushed onto `self`, given the closure's declared parameter types in two parallel
655    /// forms: `param_types` (each parameter's complete runtime shape, in order) for an implementation
656    /// that executes, and `params` (each parameter's name, span, and unresolved type expression)
657    /// for one that builds an AST instead. An implementation uses whichever it needs and ignores
658    /// the other.
659    ///
660    /// The default implementation reports closures as unsupported, so a `ParserContext`
661    /// implementation that has no use for them needs no changes to keep compiling.
662    ///
663    /// - Precondition: `body` was built via `Self::new_context()` and its own argument-binding
664    ///   mechanism, in the same style [`Self::new_context`]'s other consumers already use.
665    ///
666    /// # Errors
667    ///
668    /// Returns `Err` if this `ParserContext` implementation doesn't support closures, or (for an
669    /// implementation that validates during parsing) if `body` is otherwise unsuitable — e.g.
670    /// [`DynSegmentContext`] rejects a `body` that doesn't produce exactly one value.
671    ///
672    /// # Examples
673    /// ```
674    /// use cel_parser::{AstContext, Expr, ParserContext};
675    /// use proc_macro2::Span;
676    /// let mut context = AstContext::new_context();
677    /// let span = Span::call_site();
678    /// let mut body = AstContext::new_context();
679    /// body.push_literal(42i32, span);
680    /// context.push_closure(Vec::new(), Vec::new(), body, span).unwrap();
681    /// assert!(matches!(context.into_expr(), Expr::Closure { params, .. } if params.is_empty()));
682    /// ```
683    fn push_closure(
684        &mut self,
685        param_types: Vec<cel_runtime::ValueType>,
686        params: Vec<ClosureParam>,
687        body: Self,
688        span: Span,
689    ) -> crate::Result<()> {
690        let _ = (param_types, params, body);
691        Err(crate::ParseError::new_range(
692            "closures are not supported in this context".to_string(),
693            span,
694            span,
695        ))
696    }
697
698    /// Returns the `TypeId` of the single value `self` currently holds, or `None` if that isn't
699    /// known/applicable for this `ParserContext` implementation.
700    ///
701    /// Used by the closure-literal grammar production to infer a closure's return type from its
702    /// already-compiled body, mirroring [`Self::peek_tuple_arity`]'s "ask the context what it
703    /// currently holds" shape, generalized from "is it a tuple" to "what type is it".
704    ///
705    /// The default implementation returns `None`. A syntax-only context can retain closures
706    /// without answering this runtime query; [`Self::resolves_closure_types`] selects that policy.
707    ///
708    /// # Examples
709    /// See [`Self::apply_cast`] for a known runtime output type.
710    fn output_type_id(&self) -> Option<std::any::TypeId> {
711        None
712    }
713}
714
715/// [`ParserContext`] implementation that executes directly into a [`DynSegment`], reproducing
716/// the runtime-execution behavior `CELParser` always had before this trait existed.
717///
718/// # Examples
719///
720/// ```rust
721/// use cel_parser::parser_context::{DynSegmentContext, ParserContext};
722/// use proc_macro2::Span;
723///
724/// let mut ctx = DynSegmentContext::new_context();
725/// ctx.push_literal(10i32, Span::call_site());
726/// ```
727pub struct DynSegmentContext(pub(crate) DynSegment);
728
729impl DynSegmentContext {
730    /// Returns the wrapped [`DynSegment`], consuming `self`.
731    ///
732    /// # Examples
733    /// See [`ParserContext::apply_op`] for executing the returned segment.
734    pub fn into_inner(self) -> DynSegment {
735        self.0
736    }
737
738    /// Emits ordinary or member dispatch through the selected borrowed scope environment.
739    ///
740    /// # Errors
741    /// Returns a spanned lookup or output annotation error.
742    /// - Complexity: O(k + s * a * d) for k scopes, s signatures, a operands and depth d.
743    pub(crate) fn apply_scoped_op(
744        &mut self,
745        lookup: &OpLookup,
746        name: &str,
747        arity: usize,
748        span: ExprSpan,
749        member: bool,
750        library_only: bool,
751    ) -> crate::Result<()> {
752        lookup.lookup_scoped(name, &mut self.0, arity, span, member, library_only)?;
753        if !member
754            && let Some(ty) = self
755                .0
756                .peek_output_type_id()
757                .and_then(crate::op_table::builtin_value_type)
758        {
759            self.0.annotate_top(ty).map_err(|error| {
760                crate::ParseError::new_range(error.to_string(), span.start, span.end)
761            })?;
762        }
763        Ok(())
764    }
765
766    /// Collects array elements using an already resolved optional annotation.
767    ///
768    /// # Errors
769    /// Returns a spanned error if element shapes differ or storage cannot be represented.
770    /// - Complexity: O(n * d) for n elements of descriptor depth d.
771    pub(crate) fn make_resolved_array(
772        &mut self,
773        n: usize,
774        ambient_start: usize,
775        element_type: Option<cel_runtime::ValueType>,
776        span: ExprSpan,
777    ) -> crate::Result<()> {
778        match element_type {
779            Some(element_type) => self
780                .0
781                .make_typed_array(n, ambient_start, element_type)
782                .map_err(|error| {
783                    crate::ParseError::new_range(error.to_string(), span.start, span.end)
784                }),
785            None => self.make_array(n, ambient_start, span.start, span.end),
786        }
787    }
788}
789
790impl std::ops::Deref for DynSegmentContext {
791    /// The execution owner borrowed through this context.
792    type Target = DynSegment;
793
794    /// Borrows the execution segment without transferring ownership.
795    fn deref(&self) -> &DynSegment {
796        &self.0
797    }
798}
799
800impl std::ops::DerefMut for DynSegmentContext {
801    /// Exclusively borrows the execution segment.
802    fn deref_mut(&mut self) -> &mut DynSegment {
803        &mut self.0
804    }
805}
806
807impl ParserContext for DynSegmentContext {
808    /// Creates an empty execution owner with no arguments.
809    fn new_context() -> Self {
810        DynSegmentContext(DynSegment::new::<()>())
811    }
812
813    /// Creates an independent branch owner preserving the argument layout.
814    fn new_fragment(&self) -> Self {
815        DynSegmentContext(self.0.new_fragment())
816    }
817
818    /// Emits an owned literal and attaches its builtin descriptor when available.
819    fn push_literal<T: 'static + Clone>(&mut self, value: T, _span: Span) {
820        self.0.just(value);
821        if let Some(ty) = crate::op_table::builtin_value_type(std::any::TypeId::of::<T>()) {
822            self.0.annotate_top(ty).expect("same concrete literal");
823        }
824    }
825
826    /// Emits a matching operation or reports a source-anchored dispatch error.
827    fn apply_op(
828        &mut self,
829        op_lookup: &OpLookup,
830        name: &str,
831        arity: usize,
832        start: Span,
833        end: Span,
834    ) -> crate::Result<()> {
835        self.apply_scoped_op(
836            op_lookup,
837            name,
838            arity,
839            ExprSpan { start, end },
840            false,
841            false,
842        )
843    }
844
845    /// Emits receiver-first direct dispatch with diagnostics anchored to the member name.
846    fn apply_member(
847        &mut self,
848        lookup: &OpLookup,
849        name: &str,
850        name_span: ExprSpan,
851        explicit_args: Option<usize>,
852        _start: Span,
853        _end: Span,
854    ) -> crate::Result<()> {
855        let arity = explicit_args.unwrap_or(0) + 1;
856        debug_assert_eq!(self.0.peek_stack_infos(arity).len(), arity);
857        self.apply_scoped_op(lookup, name, arity, name_span, true, false)
858    }
859
860    /// Validates and emits the registered constructor without rearranging field values.
861    fn make_record(
862        &mut self,
863        lookup: &OpLookup,
864        name: &str,
865        _name_span: ExprSpan,
866        fields: Vec<(String, ExprSpan)>,
867        ambient_start: usize,
868        start: Span,
869        end: Span,
870    ) -> crate::Result<()> {
871        lookup.construct_record(name, &fields, ambient_start, &mut self.0, start, end)
872    }
873
874    /// Emits lazy branching with the nonselected branch left unexecuted.
875    fn apply_logical(
876        &mut self,
877        name: &str,
878        rhs: Self,
879        start: Span,
880        end: Span,
881    ) -> crate::Result<()> {
882        let mut bypass = self.new_fragment();
883        let result = match name {
884            "||" => {
885                bypass.0.just(true);
886                self.0.join2(bypass.0, rhs.0)
887            }
888            "&&" => {
889                bypass.0.just(false);
890                self.0.join2(rhs.0, bypass.0)
891            }
892            other => unreachable!("apply_logical called with unsupported operator `{other}`"),
893        };
894        result.map_err(|e| crate::ParseError::new_range(e.to_string(), start, end))
895    }
896
897    /// Joins compatible branch outputs, supplying unit for an absent alternative.
898    fn join2(
899        &mut self,
900        then_fragment: Self,
901        else_fragment: Option<Self>,
902        _start: Span,
903        _end: Span,
904    ) -> anyhow::Result<()> {
905        // No `else`/`else if` in the source: synthesize the implicit `()` fragment here instead
906        // of receiving one pre-built from `is_if_expression` (lib.rs) — execution still needs a
907        // concrete fragment to select on `false`, even though `AstContext::join2` (ast.rs)
908        // records this case as `None` rather than a synthesized node.
909        let else_fragment = if let Some(fragment) = else_fragment {
910            fragment
911        } else {
912            let mut fragment = self.new_fragment();
913            fragment
914                .0
915                .make_tuple(0, fragment.0.current_stack_offset())?;
916            fragment
917        };
918        self.0.join2(then_fragment.0, else_fragment.0)
919    }
920
921    /// Builds a checked tuple layout from the current source-order fields.
922    fn make_tuple(
923        &mut self,
924        n: usize,
925        ambient_start: usize,
926        start: Span,
927        end: Span,
928    ) -> crate::Result<()> {
929        self.0
930            .make_tuple(n, ambient_start)
931            .map_err(|error| crate::ParseError::new_range(error.to_string(), start, end))
932    }
933
934    /// Builds an array or reports a source-anchored element-shape error.
935    fn make_array(
936        &mut self,
937        n: usize,
938        ambient_start: usize,
939        start: Span,
940        end: Span,
941    ) -> crate::Result<()> {
942        self.0
943            .make_array(n, ambient_start)
944            // The error spans the whole `[...]` literal: `DynSegment::make_array` names the
945            // offending element's index only in its message text, with no structured index to map
946            // back to that element's own span (see https://github.com/stlab/cel-rs/issues/215).
947            .map_err(|e| crate::ParseError::new_range(e.to_string(), start, end))
948    }
949
950    /// Resolves an annotation before constructing its checked array representation.
951    fn make_annotated_array(
952        &mut self,
953        n: usize,
954        ambient_start: usize,
955        mut annotation: AnnotatedArray<'_>,
956        start: Span,
957        end: Span,
958    ) -> crate::Result<()> {
959        self.make_resolved_array(
960            n,
961            ambient_start,
962            annotation.resolve_element_type()?,
963            ExprSpan { start, end },
964        )
965    }
966
967    /// Returns the execution stack's known tuple arity.
968    fn peek_tuple_arity(&self) -> Option<usize> {
969        self.0.peek_tuple_arity()
970    }
971
972    /// Validates a resolved tuple index before emitting its checked field transfer.
973    ///
974    /// # Errors
975    /// Returns a spanned error for a non-tuple, out-of-bounds index, or unrepresentable frame.
976    fn tuple_index(&mut self, index: usize, start: Span, end: Span) -> crate::Result<()> {
977        let arity = self
978            .0
979            .peek_tuple_arity()
980            .ok_or_else(|| crate::ParseError::new_range("'.N' requires a tuple", start, end))?;
981        if index >= arity {
982            return Err(crate::ParseError::new_range(
983                format!("tuple index `{index}` out of range for tuple of arity {arity}"),
984                start,
985                end,
986            ));
987        }
988        self.0
989            .tuple_index(index)
990            .map_err(|error| crate::ParseError::new_range(error.to_string(), start, end))
991    }
992
993    /// Returns the execution stack's current byte offset.
994    fn current_stack_offset(&self) -> usize {
995        self.0.current_stack_offset()
996    }
997
998    /// Emits a checked cast or reports its source-anchored dispatch error.
999    fn apply_cast(
1000        &mut self,
1001        op_lookup: &OpLookup,
1002        type_name: &str,
1003        start: Span,
1004        end: Span,
1005    ) -> crate::Result<()> {
1006        op_lookup.lookup_cast(type_name, &mut self.0, start, end)
1007    }
1008
1009    /// Installs a typed noncapturing body with its complete parameter and result shapes.
1010    fn push_closure(
1011        &mut self,
1012        param_types: Vec<cel_runtime::ValueType>,
1013        params: Vec<ClosureParam>,
1014        body: Self,
1015        span: Span,
1016    ) -> crate::Result<()> {
1017        let _ = params;
1018        let closure = cel_runtime::DynClosure::typed(param_types, body.into_inner())
1019            .map_err(|error| crate::ParseError::new_range(error.to_string(), span, span))?;
1020        self.0
1021            .push_closure(closure)
1022            .map_err(|error| crate::ParseError::new_range(error.to_string(), span, span))
1023    }
1024
1025    /// Returns the sole execution result's known concrete identity.
1026    fn output_type_id(&self) -> Option<std::any::TypeId> {
1027        self.0.peek_output_type_id()
1028    }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034    use crate::op_table::OpLookup;
1035    use proc_macro2::Span;
1036
1037    #[test]
1038    fn new_context_is_empty_and_ready_for_literals() {
1039        let mut ctx = DynSegmentContext::new_context();
1040        ctx.push_literal(10i32, Span::call_site());
1041        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 10);
1042    }
1043
1044    #[test]
1045    fn apply_op_dispatches_builtin_addition() {
1046        let mut ctx = DynSegmentContext::new_context();
1047        ctx.push_literal(10i32, Span::call_site());
1048        ctx.push_literal(20i32, Span::call_site());
1049        let lookup = OpLookup::new();
1050        ctx.apply_op(&lookup, "+", 2, Span::call_site(), Span::call_site())
1051            .unwrap();
1052        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 30);
1053    }
1054
1055    #[test]
1056    fn apply_op_propagates_lookup_error() {
1057        let mut ctx = DynSegmentContext::new_context();
1058        ctx.push_literal(10i32, Span::call_site());
1059        ctx.push_literal("hi".to_string(), Span::call_site());
1060        let lookup = OpLookup::new();
1061        let err = ctx
1062            .apply_op(&lookup, "+", 2, Span::call_site(), Span::call_site())
1063            .expect_err("mismatched operand types must fail");
1064        assert!(err.message().starts_with("no operation"));
1065    }
1066
1067    /// Renders a type expression as its source-level text, for recording in assertions.
1068    fn render(type_expr: &TypeExpr) -> String {
1069        match type_expr {
1070            TypeExpr::Named { name, .. } => name.clone(),
1071            TypeExpr::Array { element, .. } => format!("[{}]", render(element)),
1072            TypeExpr::Tuple { elements, .. } => format!(
1073                "({})",
1074                elements.iter().map(render).collect::<Vec<_>>().join(", ")
1075            ),
1076        }
1077    }
1078
1079    /// Stands in for an out-of-tree [`ParserContext`] implementation: it records only what array
1080    /// construction hands it, so a dropped annotation would be observable.
1081    #[derive(Default)]
1082    struct RecordingContext {
1083        values: usize,
1084        arrays: Vec<(usize, Option<String>)>,
1085    }
1086
1087    impl ParserContext for RecordingContext {
1088        fn new_context() -> Self {
1089            Self::default()
1090        }
1091
1092        fn new_fragment(&self) -> Self {
1093            Self::default()
1094        }
1095
1096        fn push_literal<T: 'static + Clone>(&mut self, _value: T, _span: Span) {
1097            self.values += 1;
1098        }
1099
1100        fn apply_op(
1101            &mut self,
1102            _op_lookup: &OpLookup,
1103            _name: &str,
1104            _arity: usize,
1105            _start: Span,
1106            _end: Span,
1107        ) -> crate::Result<()> {
1108            unimplemented!("RecordingContext only records array construction")
1109        }
1110
1111        /// Rejects records outside this array-only test context.
1112        fn make_record(
1113            &mut self,
1114            _lookup: &OpLookup,
1115            _name: &str,
1116            _name_span: ExprSpan,
1117            _fields: Vec<(String, ExprSpan)>,
1118            _ambient_start: usize,
1119            _start: Span,
1120            _end: Span,
1121        ) -> crate::Result<()> {
1122            unimplemented!("RecordingContext only records array construction")
1123        }
1124
1125        /// Rejects member operations outside this array-only test context.
1126        fn apply_member(
1127            &mut self,
1128            _lookup: &OpLookup,
1129            _name: &str,
1130            _name_span: ExprSpan,
1131            _explicit_args: Option<usize>,
1132            _start: Span,
1133            _end: Span,
1134        ) -> crate::Result<()> {
1135            unimplemented!("RecordingContext only records array construction")
1136        }
1137
1138        fn apply_logical(
1139            &mut self,
1140            _name: &str,
1141            _rhs: Self,
1142            _start: Span,
1143            _end: Span,
1144        ) -> crate::Result<()> {
1145            unimplemented!("RecordingContext only records array construction")
1146        }
1147
1148        fn join2(
1149            &mut self,
1150            _then_fragment: Self,
1151            _else_fragment: Option<Self>,
1152            _start: Span,
1153            _end: Span,
1154        ) -> anyhow::Result<()> {
1155            unimplemented!("RecordingContext only records array construction")
1156        }
1157
1158        fn make_tuple(
1159            &mut self,
1160            _n: usize,
1161            _ambient_start: usize,
1162            _start: Span,
1163            _end: Span,
1164        ) -> crate::Result<()> {
1165            unimplemented!("RecordingContext only records array construction")
1166        }
1167
1168        fn make_array(
1169            &mut self,
1170            n: usize,
1171            ambient_start: usize,
1172            _start: Span,
1173            _end: Span,
1174        ) -> crate::Result<()> {
1175            self.values = ambient_start;
1176            self.arrays.push((n, None));
1177            self.values += 1;
1178            Ok(())
1179        }
1180
1181        fn make_annotated_array(
1182            &mut self,
1183            n: usize,
1184            ambient_start: usize,
1185            annotation: AnnotatedArray<'_>,
1186            start: Span,
1187            end: Span,
1188        ) -> crate::Result<()> {
1189            let (type_annotation, _span) = annotation.into_parts();
1190            let Some(type_annotation) = type_annotation else {
1191                return self.make_array(n, ambient_start, start, end);
1192            };
1193            self.values = ambient_start;
1194            self.arrays.push((n, Some(render(&type_annotation))));
1195            self.values += 1;
1196            Ok(())
1197        }
1198
1199        fn peek_tuple_arity(&self) -> Option<usize> {
1200            None
1201        }
1202
1203        fn tuple_index(&mut self, _index: usize, _start: Span, _end: Span) -> crate::Result<()> {
1204            unimplemented!("RecordingContext only records array construction")
1205        }
1206
1207        fn current_stack_offset(&self) -> usize {
1208            self.values
1209        }
1210
1211        fn apply_cast(
1212            &mut self,
1213            _op_lookup: &OpLookup,
1214            _type_name: &str,
1215            _start: Span,
1216            _end: Span,
1217        ) -> crate::Result<()> {
1218            unimplemented!("RecordingContext only records array construction")
1219        }
1220    }
1221
1222    /// The grammar hands every array annotation to the context, and a context that keeps it sees
1223    /// the complete declared type — including for the nested literal inside an annotated one.
1224    #[test]
1225    fn the_grammar_delivers_array_annotations_to_any_parser_context() {
1226        let mut parser = crate::Parser::<RecordingContext>::new(OpLookup::new());
1227        let ctx = parser
1228            .parse_str_ctx("[[]: [i32]]: [[i32]]")
1229            .expect("annotated nested array literal parses");
1230
1231        assert_eq!(
1232            ctx.arrays,
1233            vec![
1234                (0, Some("[i32]".to_string())),
1235                (1, Some("[[i32]]".to_string()))
1236            ]
1237        );
1238    }
1239
1240    /// An unannotated literal still reaches the same method, with no annotation to record.
1241    #[test]
1242    fn the_grammar_reports_no_annotation_for_an_unannotated_array() {
1243        let mut parser = crate::Parser::<RecordingContext>::new(OpLookup::new());
1244        let ctx = parser
1245            .parse_str_ctx("[1i32, 2i32]")
1246            .expect("unannotated array literal parses");
1247
1248        assert_eq!(ctx.arrays, vec![(2, None)]);
1249    }
1250
1251    #[test]
1252    fn make_tuple_and_tuple_index_roundtrip() {
1253        let mut ctx = DynSegmentContext::new_context();
1254        let ambient_start = ctx.current_stack_offset();
1255        ctx.push_literal(1i32, Span::call_site());
1256        ctx.push_literal(2i32, Span::call_site());
1257        ctx.make_tuple(2, ambient_start, Span::call_site(), Span::call_site())
1258            .unwrap();
1259        assert_eq!(ctx.peek_tuple_arity(), Some(2));
1260        ctx.tuple_index(1, Span::call_site(), Span::call_site())
1261            .unwrap();
1262        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 2);
1263    }
1264
1265    #[test]
1266    fn make_array_collects_homogeneous_values_into_one_array() {
1267        let mut ctx = DynSegmentContext::new_context();
1268        let ambient_start = ctx.current_stack_offset();
1269        ctx.push_literal(1i32, Span::call_site());
1270        ctx.push_literal(2i32, Span::call_site());
1271        ctx.make_array(2, ambient_start, Span::call_site(), Span::call_site())
1272            .unwrap();
1273        let array = ctx
1274            .into_inner()
1275            .call0::<cel_runtime::OwnedValue>()
1276            .unwrap()
1277            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1278            .unwrap();
1279        assert_eq!(array.into_vec(), vec![1, 2]);
1280    }
1281
1282    #[test]
1283    fn make_array_reports_a_mismatched_element_as_a_parse_error() {
1284        let mut ctx = DynSegmentContext::new_context();
1285        let ambient_start = ctx.current_stack_offset();
1286        ctx.push_literal(1i32, Span::call_site());
1287        ctx.push_literal(2.0f64, Span::call_site());
1288        let err = ctx
1289            .make_array(2, ambient_start, Span::call_site(), Span::call_site())
1290            .expect_err("array elements must share one type");
1291        assert!(
1292            err.message().contains("array element 1"),
1293            "got: {}",
1294            err.message()
1295        );
1296    }
1297
1298    #[test]
1299    fn make_annotated_array_with_none_preserves_unannotated_array_inference() {
1300        let mut ctx = DynSegmentContext::new_context();
1301        let ambient_start = ctx.current_stack_offset();
1302        ctx.push_literal(1i32, Span::call_site());
1303        ctx.push_literal(2i32, Span::call_site());
1304        ctx.make_annotated_array(
1305            2,
1306            ambient_start,
1307            AnnotatedArray::new(
1308                &mut |_| unreachable!("unannotated arrays do not resolve a declared type"),
1309                None,
1310                None,
1311            ),
1312            Span::call_site(),
1313            Span::call_site(),
1314        )
1315        .unwrap();
1316        let array = ctx
1317            .into_inner()
1318            .call0::<cel_runtime::OwnedValue>()
1319            .unwrap()
1320            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1321            .unwrap();
1322        assert_eq!(array.into_vec(), vec![1, 2]);
1323    }
1324
1325    #[test]
1326    fn make_annotated_array_with_a_resolved_descriptor_collects_a_typed_empty_array() {
1327        let mut ctx = DynSegmentContext::new_context();
1328        let ambient_start = ctx.current_stack_offset();
1329        let annotation = TypeExpr::Array {
1330            element: Box::new(TypeExpr::Named {
1331                name: "i32".to_string(),
1332                args: Vec::new(),
1333                span: ExprSpan {
1334                    start: Span::call_site(),
1335                    end: Span::call_site(),
1336                },
1337            }),
1338            span: ExprSpan {
1339                start: Span::call_site(),
1340                end: Span::call_site(),
1341            },
1342        };
1343        ctx.make_annotated_array(
1344            0,
1345            ambient_start,
1346            AnnotatedArray::new(
1347                &mut |_| {
1348                    Ok(crate::ResolvedArrayType::from_element_type(
1349                        cel_runtime::ValueType::leaf::<i32>(),
1350                    ))
1351                },
1352                Some(annotation),
1353                Some(ExprSpan {
1354                    start: Span::call_site(),
1355                    end: Span::call_site(),
1356                }),
1357            ),
1358            Span::call_site(),
1359            Span::call_site(),
1360        )
1361        .unwrap();
1362        let array = ctx
1363            .into_inner()
1364            .call0::<cel_runtime::OwnedValue>()
1365            .unwrap()
1366            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1367            .unwrap();
1368        assert!(array.into_vec().is_empty());
1369    }
1370
1371    #[test]
1372    fn peek_tuple_arity_is_none_for_non_tuple() {
1373        let mut ctx = DynSegmentContext::new_context();
1374        ctx.push_literal(5i32, Span::call_site());
1375        assert_eq!(ctx.peek_tuple_arity(), None);
1376    }
1377
1378    #[test]
1379    fn join2_selects_then_fragment_when_condition_true() {
1380        let mut ctx = DynSegmentContext::new_context();
1381        ctx.push_literal(true, Span::call_site());
1382        let mut then_fragment = ctx.new_fragment();
1383        then_fragment.push_literal(1i32, Span::call_site());
1384        let mut else_fragment = ctx.new_fragment();
1385        else_fragment.push_literal(2i32, Span::call_site());
1386        ctx.join2(
1387            then_fragment,
1388            Some(else_fragment),
1389            Span::call_site(),
1390            Span::call_site(),
1391        )
1392        .unwrap();
1393        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 1);
1394    }
1395
1396    #[test]
1397    fn join2_selects_else_fragment_when_condition_false() {
1398        let mut ctx = DynSegmentContext::new_context();
1399        ctx.push_literal(false, Span::call_site());
1400        let mut then_fragment = ctx.new_fragment();
1401        then_fragment.push_literal(1i32, Span::call_site());
1402        let mut else_fragment = ctx.new_fragment();
1403        else_fragment.push_literal(2i32, Span::call_site());
1404        ctx.join2(
1405            then_fragment,
1406            Some(else_fragment),
1407            Span::call_site(),
1408            Span::call_site(),
1409        )
1410        .unwrap();
1411        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 2);
1412    }
1413
1414    #[test]
1415    fn join2_with_none_synthesizes_an_implicit_unit_else_fragment() {
1416        let mut ctx = DynSegmentContext::new_context();
1417        ctx.push_literal(false, Span::call_site());
1418        let mut then_fragment = ctx.new_fragment();
1419        then_fragment
1420            .make_tuple(
1421                0,
1422                then_fragment.current_stack_offset(),
1423                Span::call_site(),
1424                Span::call_site(),
1425            )
1426            .unwrap();
1427        ctx.join2(then_fragment, None, Span::call_site(), Span::call_site())
1428            .unwrap();
1429        ctx.into_inner().call0::<()>().unwrap();
1430    }
1431
1432    #[test]
1433    fn deref_gives_transparent_access_to_dyn_segment_methods() {
1434        // Proves DynSegmentContext doesn't need `.into_inner()` for read-only DynSegment
1435        // methods not part of ParserContext itself (e.g. peek_output_type_id).
1436        let mut ctx = DynSegmentContext::new_context();
1437        ctx.push_literal(7i32, Span::call_site());
1438        assert_eq!(
1439            ctx.peek_output_type_id(),
1440            Some(std::any::TypeId::of::<i32>())
1441        );
1442        ctx.op1r(|value: i32| {
1443            value
1444                .checked_add(1)
1445                .ok_or_else(|| anyhow::anyhow!("overflow"))
1446        })
1447        .unwrap();
1448        assert_eq!(ctx.call0::<i32>().unwrap(), 8);
1449    }
1450
1451    /// Defaults resolve runtime annotations, report unknown output, and reject closures.
1452    #[test]
1453    fn default_context_policy_is_explicit() {
1454        assert!(RecordingContext::resolves_closure_types());
1455        let mut context = RecordingContext::new_context();
1456        assert_eq!(context.output_type_id(), None);
1457        let error = context
1458            .push_closure(
1459                Vec::new(),
1460                Vec::new(),
1461                RecordingContext::new_context(),
1462                Span::call_site(),
1463            )
1464            .unwrap_err();
1465        assert!(error.message().contains("not supported"));
1466    }
1467
1468    /// Annotation accessors retain syntax and source while resolution reports host failures.
1469    #[test]
1470    fn annotation_accessors_and_resolution_errors_are_observable() {
1471        let syntax = crate::CELParser::new(OpLookup::new())
1472            .parse_type_expr_str("[i32]")
1473            .unwrap();
1474        let span = syntax.span();
1475        let mut resolve = |_: &TypeExpr| -> crate::Result<crate::ResolvedArrayType> {
1476            Err(crate::ParseError::new(
1477                "host resolution failed",
1478                Span::call_site(),
1479            ))
1480        };
1481        let mut annotation = AnnotatedArray::new(&mut resolve, Some(syntax), Some(span));
1482        assert!(matches!(
1483            annotation.type_annotation(),
1484            Some(TypeExpr::Array { .. })
1485        ));
1486        assert_eq!(
1487            annotation.annotation_span().unwrap().start.start().column,
1488            0
1489        );
1490        assert!(
1491            annotation
1492                .resolve_element_type()
1493                .unwrap_err()
1494                .message()
1495                .contains("host resolution failed")
1496        );
1497    }
1498
1499    #[test]
1500    fn apply_logical_or_short_circuits_to_lhs_when_true() {
1501        let mut ctx = DynSegmentContext::new_context();
1502        ctx.push_literal(true, Span::call_site());
1503        let mut rhs = ctx.new_fragment();
1504        rhs.push_literal(false, Span::call_site());
1505        ctx.apply_logical("||", rhs, Span::call_site(), Span::call_site())
1506            .unwrap();
1507        assert!(ctx.into_inner().call0::<bool>().unwrap());
1508    }
1509
1510    #[test]
1511    fn apply_logical_and_short_circuits_to_false_when_lhs_false() {
1512        let mut ctx = DynSegmentContext::new_context();
1513        ctx.push_literal(false, Span::call_site());
1514        let mut rhs = ctx.new_fragment();
1515        rhs.push_literal(true, Span::call_site());
1516        ctx.apply_logical("&&", rhs, Span::call_site(), Span::call_site())
1517            .unwrap();
1518        assert!(!ctx.into_inner().call0::<bool>().unwrap());
1519    }
1520
1521    #[test]
1522    fn dyn_segment_context_push_closure_builds_a_callable_closure() {
1523        let mut outer = DynSegmentContext::new_context();
1524        let mut body = DynSegmentContext::new_context();
1525        body.0.push_arg::<i32>(0);
1526        body.0.op1(|x: i32| x + 1).unwrap();
1527
1528        outer
1529            .push_closure(
1530                vec![cel_runtime::ValueType::leaf::<i32>()],
1531                Vec::new(),
1532                body,
1533                Span::call_site(),
1534            )
1535            .unwrap();
1536
1537        let closure: cel_runtime::DynClosure = outer.into_inner().call0().unwrap();
1538        let x = 5i32;
1539        assert_eq!(closure.call::<i32>(&[&x]).unwrap(), 6);
1540    }
1541}