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}