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. A future AST-building context (for the language
7//! server, formatter, and eventual macro-compilation backend) is expected to be the second.
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    /// # Examples
211    ///
212    /// ```rust
213    /// use cel_parser::{ExprSpan, ResolvedArrayType, TypeExpr};
214    /// use cel_parser::parser_context::AnnotatedArray;
215    /// use cel_runtime::ValueType;
216    /// use proc_macro2::Span;
217    ///
218    /// fn resolve_i32(_: &TypeExpr) -> cel_parser::Result<ResolvedArrayType> {
219    ///     Ok(ResolvedArrayType::from_element_type(
220    ///         ValueType::leaf::<i32>(),
221    ///     ))
222    /// }
223    ///
224    /// let span = ExprSpan {
225    ///     start: Span::call_site(),
226    ///     end: Span::call_site(),
227    /// };
228    /// let mut resolve = resolve_i32;
229    /// let mut annotation = AnnotatedArray::new(
230    ///     &mut resolve,
231    ///     Some(TypeExpr::Array {
232    ///         element: Box::new(TypeExpr::Named {
233    ///             name: "i32".to_string(),
234    ///             args: Vec::new(),
235    ///             span,
236    ///         }),
237    ///         span,
238    ///     }),
239    ///     Some(span),
240    /// );
241    ///
242    /// let element_type = annotation.resolve_element_type().unwrap().unwrap();
243    /// assert_eq!(element_type.type_name(), "i32");
244    /// ```
245    pub fn resolve_element_type(&mut self) -> crate::Result<Option<cel_runtime::ValueType>> {
246        self.type_annotation
247            .as_ref()
248            .map(|type_annotation| {
249                (self.resolve_array_type)(type_annotation)
250                    .map(|resolved| resolved.into_element_type())
251            })
252            .transpose()
253    }
254
255    /// Returns the parsed syntax-level annotation inputs unchanged.
256    ///
257    /// # Examples
258    ///
259    /// ```rust
260    /// use cel_parser::{ExprSpan, TypeExpr};
261    /// use cel_parser::parser_context::AnnotatedArray;
262    /// use proc_macro2::Span;
263    ///
264    /// fn ignore(_: &TypeExpr) -> cel_parser::Result<cel_parser::ResolvedArrayType> {
265    ///     unreachable!("this example only unpacks stored parts")
266    /// }
267    ///
268    /// let span = ExprSpan {
269    ///     start: Span::call_site(),
270    ///     end: Span::call_site(),
271    /// };
272    /// let mut resolve = ignore;
273    /// let annotation = AnnotatedArray::new(
274    ///     &mut resolve,
275    ///     Some(TypeExpr::Array {
276    ///         element: Box::new(TypeExpr::Named {
277    ///             name: "i32".to_string(),
278    ///             args: Vec::new(),
279    ///             span,
280    ///         }),
281    ///         span,
282    ///     }),
283    ///     Some(span),
284    /// );
285    ///
286    /// let (type_annotation, annotation_span) = annotation.into_parts();
287    /// assert!(annotation_span.is_some());
288    /// match type_annotation {
289    ///     Some(TypeExpr::Array { element, .. }) => assert!(matches!(
290    ///         element.as_ref(),
291    ///         TypeExpr::Named { name, .. } if name == "i32"
292    ///     )),
293    ///     other => panic!("expected [i32], got {other:?}"),
294    /// }
295    /// ```
296    #[must_use]
297    pub fn into_parts(self) -> (Option<TypeExpr>, Option<ExprSpan>) {
298        (self.type_annotation, self.annotation_span)
299    }
300}
301
302/// The pluggable target a grammar production emits into.
303///
304/// Each method mirrors one operation the grammar in `lib.rs` needs. Implementations decide what
305/// "emitting" means: [`DynSegmentContext`] executes immediately into a stack machine; a future
306/// AST-building context would instead record a tree node.
307pub trait ParserContext: Sized {
308    /// Returns whether closure annotations must resolve while parsing rather than remain syntax.
309    ///
310    /// # Examples
311    /// ```
312    /// use cel_parser::{AstContext, parser_context::ParserContext};
313    /// assert!(!AstContext::resolves_closure_types());
314    /// ```
315    fn resolves_closure_types() -> bool {
316        true
317    }
318
319    /// Creates a fresh, empty context with no operations recorded yet.
320    fn new_context() -> Self;
321
322    /// Creates an empty fragment for building an alternate branch (one side of a
323    /// short-circuiting `||`/`&&`, or an `if`/`else` branch), independent of `self`.
324    ///
325    /// - Precondition: `self` matches whatever precondition the implementation's equivalent of
326    ///   `DynSegment::new_fragment` requires (for `DynSegmentContext`, a condition value already
327    ///   present).
328    fn new_fragment(&self) -> Self;
329
330    /// Pushes a literal value with the source span of the token it came from.
331    fn push_literal<T: 'static + Clone>(&mut self, value: T, span: Span);
332
333    /// Applies a named operator or zero-arity identifier lookup, using `op_lookup` to resolve it
334    /// against whatever this context currently holds.
335    ///
336    /// # Errors
337    ///
338    /// Returns `Err` if `op_lookup` cannot resolve `name` for `arity` operands.
339    fn apply_op(
340        &mut self,
341        op_lookup: &OpLookup,
342        name: &str,
343        arity: usize,
344        start: Span,
345        end: Span,
346    ) -> crate::Result<()>;
347
348    /// Applies a direct named operation to the receiver and explicit arguments in source order.
349    ///
350    /// `None` preserves `.name`; `Some(0)` preserves `.name()`. Both consume one receiver.
351    /// `name_span` identifies the member label for diagnostics; `start`/`end` cover the expression.
352    ///
353    /// - Precondition: the receiver and `explicit_args.unwrap_or(0)` arguments are emitted.
354    /// - Complexity: O(n) for n operands, plus operation lookup in runtime contexts.
355    ///
356    /// # Errors
357    /// Returns an error if a runtime context cannot resolve the operation.
358    ///
359    /// # Examples
360    /// ```
361    /// use cel_parser::{AstContext, ExprSpan, OpLookup};
362    /// use cel_parser::parser_context::ParserContext;
363    /// use proc_macro2::Span;
364    /// let mut context = AstContext::new_context();
365    /// let span = Span::call_site();
366    /// context.push_literal(1i32, span);
367    /// context.apply_member(&OpLookup::new(), "custom", ExprSpan { start: span, end: span },
368    ///     None, span, span).unwrap();
369    /// ```
370    fn apply_member(
371        &mut self,
372        lookup: &OpLookup,
373        name: &str,
374        name_span: ExprSpan,
375        explicit_args: Option<usize>,
376        start: Span,
377        end: Span,
378    ) -> crate::Result<()>;
379
380    /// Constructs a record from named fields emitted exactly once in source order.
381    ///
382    /// - Precondition: one value per field follows `ambient_start` in this context.
383    /// - Complexity: O(f² + f * d + r) for f fields, descriptor depth d and r schemas.
384    ///
385    /// # Errors
386    /// Runtime contexts reject unknown schemas, invalid field sets, mismatched types,
387    /// or constructor emitter failures; syntax-only contexts preserve unresolved names.
388    ///
389    /// # Examples
390    /// See [`OpLookup::construct_record`].
391    #[allow(clippy::too_many_arguments)]
392    fn make_record(
393        &mut self,
394        lookup: &OpLookup,
395        name: &str,
396        name_span: ExprSpan,
397        fields: Vec<(String, ExprSpan)>,
398        ambient_start: usize,
399        start: Span,
400        end: Span,
401    ) -> crate::Result<()>;
402
403    /// Applies a short-circuiting logical operator (`"||"` or `"&&"`), consuming a leading
404    /// condition value already present on `self` and folding in `rhs`, the already-parsed
405    /// right-hand-side fragment.
406    ///
407    /// - Precondition: `name` is `"||"` or `"&&"`, and `rhs` produces exactly one value.
408    ///
409    /// # Errors
410    ///
411    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
412    /// return `Err` if the leading condition value isn't a `bool`. Implementations that defer
413    /// type validation to a later phase (e.g. [`crate::ast::AstContext`]) never return `Err`
414    /// here.
415    fn apply_logical(&mut self, name: &str, rhs: Self, start: Span, end: Span)
416    -> crate::Result<()>;
417
418    /// Joins a previously-built fragment into `self`, consuming a leading condition value already
419    /// present on `self`. `then_fragment`'s contribution is used when the condition is `true`;
420    /// `else_fragment`'s when `false`, or `None` if the source had no `else`/`else if` at all.
421    /// `start`/`end` cover the whole `if`/`else` construct.
422    ///
423    /// - Precondition: neither fragment takes arguments, and each produces exactly one value.
424    ///
425    /// # Errors
426    ///
427    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
428    /// return `Err` if the leading condition value isn't a `bool` or if the fragments' produced
429    /// types don't match — including when `else_fragment` is `None`, which such implementations
430    /// treat as an implicit `()` fragment (so a non-`()` then-branch with no `else` is still an
431    /// error). Implementations that defer type validation to a later phase (e.g.
432    /// [`crate::ast::AstContext`]) never return `Err` here, and record whether `else_fragment` was
433    /// `None` directly (see [`crate::Expr::If`]) instead of synthesizing anything.
434    fn join2(
435        &mut self,
436        then_fragment: Self,
437        else_fragment: Option<Self>,
438        start: Span,
439        end: Span,
440    ) -> anyhow::Result<()>;
441
442    /// Combines the last `n` emitted values into a single tuple value. `start`/`end` cover the
443    /// whole `(...)` construct.
444    ///
445    /// # Errors
446    /// Returns an error if the tuple's frame or canonical layout cannot be represented.
447    fn make_tuple(
448        &mut self,
449        n: usize,
450        ambient_start: usize,
451        start: Span,
452        end: Span,
453    ) -> crate::Result<()>;
454
455    /// Combines the last `n` emitted values into a single homogeneous array value. `start`/`end`
456    /// cover the whole `[...]` construct, and `ambient_start` is the stack offset the first
457    /// element was emitted at (as reported by [`Self::current_stack_offset`] before parsing it).
458    ///
459    /// - Precondition: `n >= 1` and exactly `n` values have been emitted since `ambient_start`.
460    ///
461    /// # Errors
462    ///
463    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
464    /// return `Err` if the elements' complete recursive runtime shapes differ, or if the array's storage
465    /// layout cannot be represented. Implementations that defer type validation to a later phase
466    /// (e.g. [`crate::ast::AstContext`]) never return `Err` here.
467    fn make_array(
468        &mut self,
469        n: usize,
470        ambient_start: usize,
471        start: Span,
472        end: Span,
473    ) -> crate::Result<()>;
474
475    /// Combines the last `n` emitted values into a single array value, honoring `annotation`'s
476    /// optional type ascription.
477    ///
478    /// This is the method the grammar actually calls for every array literal; [`Self::make_array`]
479    /// is the unannotated special case an implementation may delegate to. There is deliberately
480    /// no default implementation: an annotation changes what the literal means (`[]: [Celsius]`
481    /// is an empty array *of a specific type*), so an implementation that silently dropped it
482    /// would misreport the literal's type rather than fail to compile.
483    ///
484    /// An implementation that needs runtime array metadata immediately (e.g.
485    /// [`DynSegmentContext`]) calls [`AnnotatedArray::resolve_element_type`]; one that defers
486    /// semantic checking (e.g. [`crate::ast::AstContext`]) records the syntax returned by
487    /// [`AnnotatedArray::into_parts`].
488    ///
489    /// - Precondition: exactly `n` values have been emitted since `ambient_start`.
490    ///
491    /// # Errors
492    ///
493    /// Returns `Err` under [`Self::make_array`]'s conditions, and additionally — for an
494    /// implementation that resolves the annotation here — if the annotation names an unknown
495    /// type, does not name a complete array type, or disagrees with the collected elements.
496    fn make_annotated_array(
497        &mut self,
498        n: usize,
499        ambient_start: usize,
500        annotation: AnnotatedArray<'_>,
501        start: Span,
502        end: Span,
503    ) -> crate::Result<()>;
504
505    /// Returns the arity of the tuple currently on top, or `None` if the top value isn't a
506    /// tuple.
507    fn peek_tuple_arity(&self) -> Option<usize>;
508
509    /// Replaces the tuple on top with its `index`-th element. `start`/`end` cover the base
510    /// expression through the index token.
511    ///
512    /// - Precondition: `peek_tuple_arity()` returns `Some(arity)` with `index < arity`.
513    ///
514    /// # Errors
515    /// Returns an error if the selected field's transfer frame cannot be represented.
516    fn tuple_index(&mut self, index: usize, start: Span, end: Span) -> crate::Result<()>;
517
518    /// Returns the current stack offset, used to compute tuple layouts.
519    fn current_stack_offset(&self) -> usize;
520
521    /// Applies a cast (`expr as Type`), consuming the operand already present on `self`
522    /// and replacing it with the converted value. `type_name` is the target type's bare
523    /// identifier text (e.g. `"i32"`, `"bool"`, `"String"`), unresolved by the grammar itself.
524    ///
525    /// # Errors
526    ///
527    /// Implementations that validate operand types during parsing (e.g. [`DynSegmentContext`])
528    /// return `Err` if `type_name` isn't a recognized cast-target type, or if no conversion
529    /// from the operand's current type to it is registered. Implementations that defer type
530    /// validation to a later phase (e.g. [`crate::ast::AstContext`]) never return `Err` here.
531    fn apply_cast(
532        &mut self,
533        op_lookup: &OpLookup,
534        type_name: &str,
535        start: Span,
536        end: Span,
537    ) -> crate::Result<()>;
538
539    /// Packages a fully-parsed, independent nested context — the body of a closure literal — as
540    /// a value pushed onto `self`, given the closure's declared parameter types in two parallel
541    /// forms: `param_types` (each parameter's complete runtime shape, in order) for an implementation
542    /// that executes, and `params` (each parameter's name, span, and unresolved type expression)
543    /// for one that builds an AST instead. An implementation uses whichever it needs and ignores
544    /// the other.
545    ///
546    /// The default implementation reports closures as unsupported, so a `ParserContext`
547    /// implementation that has no use for them needs no changes to keep compiling.
548    ///
549    /// - Precondition: `body` was built via `Self::new_context()` and its own argument-binding
550    ///   mechanism, in the same style [`Self::new_context`]'s other consumers already use.
551    ///
552    /// # Errors
553    ///
554    /// Returns `Err` if this `ParserContext` implementation doesn't support closures, or (for an
555    /// implementation that validates during parsing) if `body` is otherwise unsuitable — e.g.
556    /// [`DynSegmentContext`] rejects a `body` that doesn't produce exactly one value.
557    fn push_closure(
558        &mut self,
559        param_types: Vec<cel_runtime::ValueType>,
560        params: Vec<ClosureParam>,
561        body: Self,
562        span: Span,
563    ) -> crate::Result<()> {
564        let _ = (param_types, params, body);
565        Err(crate::ParseError::new_range(
566            "closures are not supported in this context".to_string(),
567            span,
568            span,
569        ))
570    }
571
572    /// Returns the `TypeId` of the single value `self` currently holds, or `None` if that isn't
573    /// known/applicable for this `ParserContext` implementation.
574    ///
575    /// Used by the closure-literal grammar production to infer a closure's return type from its
576    /// already-compiled body, mirroring [`Self::peek_tuple_arity`]'s "ask the context what it
577    /// currently holds" shape, generalized from "is it a tuple" to "what type is it".
578    ///
579    /// The default implementation returns `None`, matching [`Self::push_closure`]'s default
580    /// "unsupported" behavior — an implementation with no notion of a parse-time runtime type
581    /// (e.g. an AST-building context) simply can't answer this, which surfaces to the caller the
582    /// same way an empty or multi-valued body does (a plain parse error), and in practice never
583    /// matters for such an implementation anyway, since its `push_closure` override (if any)
584    /// would reject the closure regardless.
585    fn output_type_id(&self) -> Option<std::any::TypeId> {
586        None
587    }
588}
589
590/// [`ParserContext`] implementation that executes directly into a [`DynSegment`], reproducing
591/// the runtime-execution behavior `CELParser` always had before this trait existed.
592///
593/// # Examples
594///
595/// ```rust
596/// use cel_parser::parser_context::{DynSegmentContext, ParserContext};
597/// use proc_macro2::Span;
598///
599/// let mut ctx = DynSegmentContext::new_context();
600/// ctx.push_literal(10i32, Span::call_site());
601/// ```
602pub struct DynSegmentContext(pub(crate) DynSegment);
603
604impl DynSegmentContext {
605    /// Returns the wrapped [`DynSegment`], consuming `self`.
606    pub fn into_inner(self) -> DynSegment {
607        self.0
608    }
609}
610
611impl std::ops::Deref for DynSegmentContext {
612    type Target = DynSegment;
613
614    fn deref(&self) -> &DynSegment {
615        &self.0
616    }
617}
618
619impl std::ops::DerefMut for DynSegmentContext {
620    fn deref_mut(&mut self) -> &mut DynSegment {
621        &mut self.0
622    }
623}
624
625impl ParserContext for DynSegmentContext {
626    fn new_context() -> Self {
627        DynSegmentContext(DynSegment::new::<()>())
628    }
629
630    fn new_fragment(&self) -> Self {
631        DynSegmentContext(self.0.new_fragment())
632    }
633
634    fn push_literal<T: 'static + Clone>(&mut self, value: T, _span: Span) {
635        self.0.just(value);
636        if let Some(ty) = crate::op_table::builtin_value_type(std::any::TypeId::of::<T>()) {
637            self.0.annotate_top(ty).expect("same concrete literal");
638        }
639    }
640
641    fn apply_op(
642        &mut self,
643        op_lookup: &OpLookup,
644        name: &str,
645        arity: usize,
646        start: Span,
647        end: Span,
648    ) -> crate::Result<()> {
649        op_lookup.lookup(name, &mut self.0, arity, start, end)?;
650        if let Some(ty) = self
651            .0
652            .peek_output_type_id()
653            .and_then(crate::op_table::builtin_value_type)
654        {
655            self.0
656                .annotate_top(ty)
657                .map_err(|error| crate::ParseError::new_range(error.to_string(), start, end))?;
658        }
659        Ok(())
660    }
661
662    /// Emits receiver-first direct dispatch with diagnostics anchored to the member name.
663    fn apply_member(
664        &mut self,
665        lookup: &OpLookup,
666        name: &str,
667        name_span: ExprSpan,
668        explicit_args: Option<usize>,
669        _start: Span,
670        _end: Span,
671    ) -> crate::Result<()> {
672        let arity = explicit_args.unwrap_or(0) + 1;
673        debug_assert_eq!(self.0.peek_stack_infos(arity).len(), arity);
674        lookup.lookup_member(name, &mut self.0, arity, name_span.start, name_span.end)
675    }
676
677    /// Validates and emits the registered constructor without rearranging field values.
678    fn make_record(
679        &mut self,
680        lookup: &OpLookup,
681        name: &str,
682        _name_span: ExprSpan,
683        fields: Vec<(String, ExprSpan)>,
684        ambient_start: usize,
685        start: Span,
686        end: Span,
687    ) -> crate::Result<()> {
688        lookup.construct_record(name, &fields, ambient_start, &mut self.0, start, end)
689    }
690
691    fn apply_logical(
692        &mut self,
693        name: &str,
694        rhs: Self,
695        start: Span,
696        end: Span,
697    ) -> crate::Result<()> {
698        let mut bypass = self.new_fragment();
699        let result = match name {
700            "||" => {
701                bypass.0.just(true);
702                self.0.join2(bypass.0, rhs.0)
703            }
704            "&&" => {
705                bypass.0.just(false);
706                self.0.join2(rhs.0, bypass.0)
707            }
708            other => unreachable!("apply_logical called with unsupported operator `{other}`"),
709        };
710        result.map_err(|e| crate::ParseError::new_range(e.to_string(), start, end))
711    }
712
713    fn join2(
714        &mut self,
715        then_fragment: Self,
716        else_fragment: Option<Self>,
717        _start: Span,
718        _end: Span,
719    ) -> anyhow::Result<()> {
720        // No `else`/`else if` in the source: synthesize the implicit `()` fragment here instead
721        // of receiving one pre-built from `is_if_expression` (lib.rs) — execution still needs a
722        // concrete fragment to select on `false`, even though `AstContext::join2` (ast.rs)
723        // records this case as `None` rather than a synthesized node.
724        let else_fragment = if let Some(fragment) = else_fragment {
725            fragment
726        } else {
727            let mut fragment = self.new_fragment();
728            fragment
729                .0
730                .make_tuple(0, fragment.0.current_stack_offset())?;
731            fragment
732        };
733        self.0.join2(then_fragment.0, else_fragment.0)
734    }
735
736    fn make_tuple(
737        &mut self,
738        n: usize,
739        ambient_start: usize,
740        start: Span,
741        end: Span,
742    ) -> crate::Result<()> {
743        self.0
744            .make_tuple(n, ambient_start)
745            .map_err(|error| crate::ParseError::new_range(error.to_string(), start, end))
746    }
747
748    fn make_array(
749        &mut self,
750        n: usize,
751        ambient_start: usize,
752        start: Span,
753        end: Span,
754    ) -> crate::Result<()> {
755        self.0
756            .make_array(n, ambient_start)
757            // The error spans the whole `[...]` literal: `DynSegment::make_array` names the
758            // offending element's index only in its message text, with no structured index to map
759            // back to that element's own span (see https://github.com/stlab/cel-rs/issues/215).
760            .map_err(|e| crate::ParseError::new_range(e.to_string(), start, end))
761    }
762
763    fn make_annotated_array(
764        &mut self,
765        n: usize,
766        ambient_start: usize,
767        mut annotation: AnnotatedArray<'_>,
768        start: Span,
769        end: Span,
770    ) -> crate::Result<()> {
771        match annotation.resolve_element_type()? {
772            Some(element_type) => self
773                .0
774                .make_typed_array(n, ambient_start, element_type)
775                .map_err(|e| crate::ParseError::new_range(e.to_string(), start, end)),
776            None => self.make_array(n, ambient_start, start, end),
777        }
778    }
779
780    fn peek_tuple_arity(&self) -> Option<usize> {
781        self.0.peek_tuple_arity()
782    }
783
784    fn tuple_index(&mut self, index: usize, start: Span, end: Span) -> crate::Result<()> {
785        self.0
786            .tuple_index(index)
787            .map_err(|error| crate::ParseError::new_range(error.to_string(), start, end))
788    }
789
790    fn current_stack_offset(&self) -> usize {
791        self.0.current_stack_offset()
792    }
793
794    fn apply_cast(
795        &mut self,
796        op_lookup: &OpLookup,
797        type_name: &str,
798        start: Span,
799        end: Span,
800    ) -> crate::Result<()> {
801        op_lookup.lookup_cast(type_name, &mut self.0, start, end)
802    }
803
804    fn push_closure(
805        &mut self,
806        param_types: Vec<cel_runtime::ValueType>,
807        params: Vec<ClosureParam>,
808        body: Self,
809        span: Span,
810    ) -> crate::Result<()> {
811        let _ = params;
812        let closure = cel_runtime::DynClosure::typed(param_types, body.into_inner())
813            .map_err(|error| crate::ParseError::new_range(error.to_string(), span, span))?;
814        self.0
815            .push_closure(closure)
816            .map_err(|error| crate::ParseError::new_range(error.to_string(), span, span))
817    }
818
819    fn output_type_id(&self) -> Option<std::any::TypeId> {
820        self.0.peek_output_type_id()
821    }
822}
823
824#[cfg(test)]
825mod tests {
826    use super::*;
827    use crate::op_table::OpLookup;
828    use proc_macro2::Span;
829
830    #[test]
831    fn new_context_is_empty_and_ready_for_literals() {
832        let mut ctx = DynSegmentContext::new_context();
833        ctx.push_literal(10i32, Span::call_site());
834        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 10);
835    }
836
837    #[test]
838    fn apply_op_dispatches_builtin_addition() {
839        let mut ctx = DynSegmentContext::new_context();
840        ctx.push_literal(10i32, Span::call_site());
841        ctx.push_literal(20i32, Span::call_site());
842        let lookup = OpLookup::new();
843        ctx.apply_op(&lookup, "+", 2, Span::call_site(), Span::call_site())
844            .unwrap();
845        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 30);
846    }
847
848    #[test]
849    fn apply_op_propagates_lookup_error() {
850        let mut ctx = DynSegmentContext::new_context();
851        ctx.push_literal(10i32, Span::call_site());
852        ctx.push_literal("hi".to_string(), Span::call_site());
853        let lookup = OpLookup::new();
854        let err = ctx
855            .apply_op(&lookup, "+", 2, Span::call_site(), Span::call_site())
856            .expect_err("mismatched operand types must fail");
857        assert!(err.message().starts_with("no operation"));
858    }
859
860    /// Renders a type expression as its source-level text, for recording in assertions.
861    fn render(type_expr: &TypeExpr) -> String {
862        match type_expr {
863            TypeExpr::Named { name, .. } => name.clone(),
864            TypeExpr::Array { element, .. } => format!("[{}]", render(element)),
865            TypeExpr::Tuple { elements, .. } => format!(
866                "({})",
867                elements.iter().map(render).collect::<Vec<_>>().join(", ")
868            ),
869        }
870    }
871
872    /// Stands in for an out-of-tree [`ParserContext`] implementation: it records only what array
873    /// construction hands it, so a dropped annotation would be observable.
874    #[derive(Default)]
875    struct RecordingContext {
876        values: usize,
877        arrays: Vec<(usize, Option<String>)>,
878    }
879
880    impl ParserContext for RecordingContext {
881        fn new_context() -> Self {
882            Self::default()
883        }
884
885        fn new_fragment(&self) -> Self {
886            Self::default()
887        }
888
889        fn push_literal<T: 'static + Clone>(&mut self, _value: T, _span: Span) {
890            self.values += 1;
891        }
892
893        fn apply_op(
894            &mut self,
895            _op_lookup: &OpLookup,
896            _name: &str,
897            _arity: usize,
898            _start: Span,
899            _end: Span,
900        ) -> crate::Result<()> {
901            unimplemented!("RecordingContext only records array construction")
902        }
903
904        /// Rejects records outside this array-only test context.
905        fn make_record(
906            &mut self,
907            _lookup: &OpLookup,
908            _name: &str,
909            _name_span: ExprSpan,
910            _fields: Vec<(String, ExprSpan)>,
911            _ambient_start: usize,
912            _start: Span,
913            _end: Span,
914        ) -> crate::Result<()> {
915            unimplemented!("RecordingContext only records array construction")
916        }
917
918        /// Rejects member operations outside this array-only test context.
919        fn apply_member(
920            &mut self,
921            _lookup: &OpLookup,
922            _name: &str,
923            _name_span: ExprSpan,
924            _explicit_args: Option<usize>,
925            _start: Span,
926            _end: Span,
927        ) -> crate::Result<()> {
928            unimplemented!("RecordingContext only records array construction")
929        }
930
931        fn apply_logical(
932            &mut self,
933            _name: &str,
934            _rhs: Self,
935            _start: Span,
936            _end: Span,
937        ) -> crate::Result<()> {
938            unimplemented!("RecordingContext only records array construction")
939        }
940
941        fn join2(
942            &mut self,
943            _then_fragment: Self,
944            _else_fragment: Option<Self>,
945            _start: Span,
946            _end: Span,
947        ) -> anyhow::Result<()> {
948            unimplemented!("RecordingContext only records array construction")
949        }
950
951        fn make_tuple(
952            &mut self,
953            _n: usize,
954            _ambient_start: usize,
955            _start: Span,
956            _end: Span,
957        ) -> crate::Result<()> {
958            unimplemented!("RecordingContext only records array construction")
959        }
960
961        fn make_array(
962            &mut self,
963            n: usize,
964            ambient_start: usize,
965            _start: Span,
966            _end: Span,
967        ) -> crate::Result<()> {
968            self.values = ambient_start;
969            self.arrays.push((n, None));
970            self.values += 1;
971            Ok(())
972        }
973
974        fn make_annotated_array(
975            &mut self,
976            n: usize,
977            ambient_start: usize,
978            annotation: AnnotatedArray<'_>,
979            start: Span,
980            end: Span,
981        ) -> crate::Result<()> {
982            let (type_annotation, _span) = annotation.into_parts();
983            let Some(type_annotation) = type_annotation else {
984                return self.make_array(n, ambient_start, start, end);
985            };
986            self.values = ambient_start;
987            self.arrays.push((n, Some(render(&type_annotation))));
988            self.values += 1;
989            Ok(())
990        }
991
992        fn peek_tuple_arity(&self) -> Option<usize> {
993            None
994        }
995
996        fn tuple_index(&mut self, _index: usize, _start: Span, _end: Span) -> crate::Result<()> {
997            unimplemented!("RecordingContext only records array construction")
998        }
999
1000        fn current_stack_offset(&self) -> usize {
1001            self.values
1002        }
1003
1004        fn apply_cast(
1005            &mut self,
1006            _op_lookup: &OpLookup,
1007            _type_name: &str,
1008            _start: Span,
1009            _end: Span,
1010        ) -> crate::Result<()> {
1011            unimplemented!("RecordingContext only records array construction")
1012        }
1013    }
1014
1015    /// The grammar hands every array annotation to the context, and a context that keeps it sees
1016    /// the complete declared type — including for the nested literal inside an annotated one.
1017    #[test]
1018    fn the_grammar_delivers_array_annotations_to_any_parser_context() {
1019        let mut parser = crate::Parser::<RecordingContext>::new(OpLookup::new());
1020        let ctx = parser
1021            .parse_str_ctx("[[]: [i32]]: [[i32]]")
1022            .expect("annotated nested array literal parses");
1023
1024        assert_eq!(
1025            ctx.arrays,
1026            vec![
1027                (0, Some("[i32]".to_string())),
1028                (1, Some("[[i32]]".to_string()))
1029            ]
1030        );
1031    }
1032
1033    /// An unannotated literal still reaches the same method, with no annotation to record.
1034    #[test]
1035    fn the_grammar_reports_no_annotation_for_an_unannotated_array() {
1036        let mut parser = crate::Parser::<RecordingContext>::new(OpLookup::new());
1037        let ctx = parser
1038            .parse_str_ctx("[1i32, 2i32]")
1039            .expect("unannotated array literal parses");
1040
1041        assert_eq!(ctx.arrays, vec![(2, None)]);
1042    }
1043
1044    #[test]
1045    fn make_tuple_and_tuple_index_roundtrip() {
1046        let mut ctx = DynSegmentContext::new_context();
1047        let ambient_start = ctx.current_stack_offset();
1048        ctx.push_literal(1i32, Span::call_site());
1049        ctx.push_literal(2i32, Span::call_site());
1050        ctx.make_tuple(2, ambient_start, Span::call_site(), Span::call_site())
1051            .unwrap();
1052        assert_eq!(ctx.peek_tuple_arity(), Some(2));
1053        ctx.tuple_index(1, Span::call_site(), Span::call_site())
1054            .unwrap();
1055        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 2);
1056    }
1057
1058    #[test]
1059    fn make_array_collects_homogeneous_values_into_one_array() {
1060        let mut ctx = DynSegmentContext::new_context();
1061        let ambient_start = ctx.current_stack_offset();
1062        ctx.push_literal(1i32, Span::call_site());
1063        ctx.push_literal(2i32, Span::call_site());
1064        ctx.make_array(2, ambient_start, Span::call_site(), Span::call_site())
1065            .unwrap();
1066        let array = ctx
1067            .into_inner()
1068            .call0::<cel_runtime::OwnedValue>()
1069            .unwrap()
1070            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1071            .unwrap();
1072        assert_eq!(array.into_vec(), vec![1, 2]);
1073    }
1074
1075    #[test]
1076    fn make_array_reports_a_mismatched_element_as_a_parse_error() {
1077        let mut ctx = DynSegmentContext::new_context();
1078        let ambient_start = ctx.current_stack_offset();
1079        ctx.push_literal(1i32, Span::call_site());
1080        ctx.push_literal(2.0f64, Span::call_site());
1081        let err = ctx
1082            .make_array(2, ambient_start, Span::call_site(), Span::call_site())
1083            .expect_err("array elements must share one type");
1084        assert!(
1085            err.message().contains("array element 1"),
1086            "got: {}",
1087            err.message()
1088        );
1089    }
1090
1091    #[test]
1092    fn make_annotated_array_with_none_preserves_unannotated_array_inference() {
1093        let mut ctx = DynSegmentContext::new_context();
1094        let ambient_start = ctx.current_stack_offset();
1095        ctx.push_literal(1i32, Span::call_site());
1096        ctx.push_literal(2i32, Span::call_site());
1097        ctx.make_annotated_array(
1098            2,
1099            ambient_start,
1100            AnnotatedArray::new(
1101                &mut |_| unreachable!("unannotated arrays do not resolve a declared type"),
1102                None,
1103                None,
1104            ),
1105            Span::call_site(),
1106            Span::call_site(),
1107        )
1108        .unwrap();
1109        let array = ctx
1110            .into_inner()
1111            .call0::<cel_runtime::OwnedValue>()
1112            .unwrap()
1113            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1114            .unwrap();
1115        assert_eq!(array.into_vec(), vec![1, 2]);
1116    }
1117
1118    #[test]
1119    fn make_annotated_array_with_a_resolved_descriptor_collects_a_typed_empty_array() {
1120        let mut ctx = DynSegmentContext::new_context();
1121        let ambient_start = ctx.current_stack_offset();
1122        let annotation = TypeExpr::Array {
1123            element: Box::new(TypeExpr::Named {
1124                name: "i32".to_string(),
1125                args: Vec::new(),
1126                span: ExprSpan {
1127                    start: Span::call_site(),
1128                    end: Span::call_site(),
1129                },
1130            }),
1131            span: ExprSpan {
1132                start: Span::call_site(),
1133                end: Span::call_site(),
1134            },
1135        };
1136        ctx.make_annotated_array(
1137            0,
1138            ambient_start,
1139            AnnotatedArray::new(
1140                &mut |_| {
1141                    Ok(crate::ResolvedArrayType::from_element_type(
1142                        cel_runtime::ValueType::leaf::<i32>(),
1143                    ))
1144                },
1145                Some(annotation),
1146                Some(ExprSpan {
1147                    start: Span::call_site(),
1148                    end: Span::call_site(),
1149                }),
1150            ),
1151            Span::call_site(),
1152            Span::call_site(),
1153        )
1154        .unwrap();
1155        let array = ctx
1156            .into_inner()
1157            .call0::<cel_runtime::OwnedValue>()
1158            .unwrap()
1159            .try_into_typed::<cel_runtime::ArrayOf<cel_runtime::Leaf<i32>>>()
1160            .unwrap();
1161        assert!(array.into_vec().is_empty());
1162    }
1163
1164    #[test]
1165    fn peek_tuple_arity_is_none_for_non_tuple() {
1166        let mut ctx = DynSegmentContext::new_context();
1167        ctx.push_literal(5i32, Span::call_site());
1168        assert_eq!(ctx.peek_tuple_arity(), None);
1169    }
1170
1171    #[test]
1172    fn join2_selects_then_fragment_when_condition_true() {
1173        let mut ctx = DynSegmentContext::new_context();
1174        ctx.push_literal(true, Span::call_site());
1175        let mut then_fragment = ctx.new_fragment();
1176        then_fragment.push_literal(1i32, Span::call_site());
1177        let mut else_fragment = ctx.new_fragment();
1178        else_fragment.push_literal(2i32, Span::call_site());
1179        ctx.join2(
1180            then_fragment,
1181            Some(else_fragment),
1182            Span::call_site(),
1183            Span::call_site(),
1184        )
1185        .unwrap();
1186        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 1);
1187    }
1188
1189    #[test]
1190    fn join2_selects_else_fragment_when_condition_false() {
1191        let mut ctx = DynSegmentContext::new_context();
1192        ctx.push_literal(false, Span::call_site());
1193        let mut then_fragment = ctx.new_fragment();
1194        then_fragment.push_literal(1i32, Span::call_site());
1195        let mut else_fragment = ctx.new_fragment();
1196        else_fragment.push_literal(2i32, Span::call_site());
1197        ctx.join2(
1198            then_fragment,
1199            Some(else_fragment),
1200            Span::call_site(),
1201            Span::call_site(),
1202        )
1203        .unwrap();
1204        assert_eq!(ctx.into_inner().call0::<i32>().unwrap(), 2);
1205    }
1206
1207    #[test]
1208    fn join2_with_none_synthesizes_an_implicit_unit_else_fragment() {
1209        let mut ctx = DynSegmentContext::new_context();
1210        ctx.push_literal(false, Span::call_site());
1211        let mut then_fragment = ctx.new_fragment();
1212        then_fragment
1213            .make_tuple(
1214                0,
1215                then_fragment.current_stack_offset(),
1216                Span::call_site(),
1217                Span::call_site(),
1218            )
1219            .unwrap();
1220        ctx.join2(then_fragment, None, Span::call_site(), Span::call_site())
1221            .unwrap();
1222        ctx.into_inner().call0::<()>().unwrap();
1223    }
1224
1225    #[test]
1226    fn deref_gives_transparent_access_to_dyn_segment_methods() {
1227        // Proves DynSegmentContext doesn't need `.into_inner()` for read-only DynSegment
1228        // methods not part of ParserContext itself (e.g. peek_output_type_id).
1229        let mut ctx = DynSegmentContext::new_context();
1230        ctx.push_literal(7i32, Span::call_site());
1231        assert_eq!(
1232            ctx.peek_output_type_id(),
1233            Some(std::any::TypeId::of::<i32>())
1234        );
1235    }
1236
1237    #[test]
1238    fn apply_logical_or_short_circuits_to_lhs_when_true() {
1239        let mut ctx = DynSegmentContext::new_context();
1240        ctx.push_literal(true, Span::call_site());
1241        let mut rhs = ctx.new_fragment();
1242        rhs.push_literal(false, Span::call_site());
1243        ctx.apply_logical("||", rhs, Span::call_site(), Span::call_site())
1244            .unwrap();
1245        assert!(ctx.into_inner().call0::<bool>().unwrap());
1246    }
1247
1248    #[test]
1249    fn apply_logical_and_short_circuits_to_false_when_lhs_false() {
1250        let mut ctx = DynSegmentContext::new_context();
1251        ctx.push_literal(false, Span::call_site());
1252        let mut rhs = ctx.new_fragment();
1253        rhs.push_literal(true, Span::call_site());
1254        ctx.apply_logical("&&", rhs, Span::call_site(), Span::call_site())
1255            .unwrap();
1256        assert!(!ctx.into_inner().call0::<bool>().unwrap());
1257    }
1258
1259    #[test]
1260    fn dyn_segment_context_push_closure_builds_a_callable_closure() {
1261        let mut outer = DynSegmentContext::new_context();
1262        let mut body = DynSegmentContext::new_context();
1263        body.0.push_arg::<i32>(0);
1264        body.0.op1(|x: i32| x + 1).unwrap();
1265
1266        outer
1267            .push_closure(
1268                vec![cel_runtime::ValueType::leaf::<i32>()],
1269                Vec::new(),
1270                body,
1271                Span::call_site(),
1272            )
1273            .unwrap();
1274
1275        let closure: cel_runtime::DynClosure = outer.into_inner().call0().unwrap();
1276        let x = 5i32;
1277        assert_eq!(closure.call::<i32>(&[&x]).unwrap(), 6);
1278    }
1279}