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}