Skip to main content

cel_parser/
registered_ops.rs

1//! Persistent operation and record metadata shared by independent host libraries.
2//!
3//! Register a function marker once, then add disjoint operand signatures from any
4//! installer. Ordinary calls and direct member lookup share the same emitters:
5//!
6//! ```
7//! use cel_parser::{OpLookup, OperationSignature, ResolvedLeafType};
8//! use cel_runtime::ValueType;
9//! #[derive(Clone)]
10//! struct Identity;
11//! let mut lookup = OpLookup::new();
12//! lookup.register_function("identity", Identity).unwrap();
13//! let ty = ResolvedLeafType::new("u32", ValueType::leaf::<u32>());
14//! lookup.register_operation(OperationSignature {
15//!     name: "identity", operands: vec![ty.clone().into()], result: ty.into(),
16//!     emit: Box::new(|segment, _| segment.op1(|value: u32| value)),
17//! }).unwrap();
18//! assert_eq!(lookup.operation_signatures("identity").count(), 1);
19//! ```
20
21use std::any::TypeId;
22
23use anyhow::{Result, ensure};
24use cel_runtime::{DynSegment, ValueType};
25
26use crate::{ExprSpan, OpLookup, ParseError, ResolvedLeafType, ResolvedType, SourceSpan};
27
28/// Emits an operation after the complete operand signature has matched.
29///
30/// - Precondition: the stack ends with the declared operand descriptors.
31/// - Postcondition: success replaces those operands with the declared result.
32///
33/// Emitters validate fallible setup before changing the segment. The lookup uniquely owns
34/// each emitter and invokes it only while compiling; segments own the emitted operations.
35/// Boxing a zero-sized, captureless closure does not allocate.
36///
37/// # Examples
38/// See [`OpLookup::register_operation`] for a registered emitter.
39pub type OperationEmitter = Box<dyn Fn(&mut DynSegment, SourceSpan) -> Result<()> + Send + Sync>;
40
41/// Consumes source-order fields using source-position to schema-position indices,
42/// the ambient stack byte offset, and the expression span.
43///
44/// The lookup uniquely owns this compile-time callback. Boxing a zero-sized,
45/// captureless closure does not allocate.
46///
47/// # Examples
48/// See [`OpLookup::register_record`] for source-order field construction.
49pub type RecordEmitter =
50    Box<dyn Fn(&mut DynSegment, &[usize], usize, SourceSpan) -> Result<()> + Send + Sync>;
51
52/// Describes one named operation with complete concrete operand and result identities.
53///
54/// The emitter consumes exactly `operands` and produces exactly `result`.
55///
56/// # Examples
57/// See [`OpLookup::register_operation`] for signature construction.
58pub struct OperationSignature {
59    /// The shared operation name.
60    pub name: &'static str,
61    /// Operands in source order, including the receiver for member lookup.
62    pub operands: Vec<ResolvedType>,
63    /// The single result's complete type.
64    pub result: ResolvedType,
65    /// The implementation used by both direct lookup and ordinary calls.
66    pub emit: OperationEmitter,
67}
68
69/// Describes one required record field.
70///
71/// # Examples
72/// See [`OpLookup::register_record`] for declaring required fields.
73pub struct RecordField {
74    /// The source field name.
75    pub name: &'static str,
76    /// The field's complete concrete type.
77    pub ty: ResolvedType,
78}
79
80/// Describes a named record's required fields and construction implementation.
81///
82/// # Examples
83/// See [`OpLookup::register_record`] for constructing and installing a schema.
84pub struct RecordSchema {
85    /// The constructed type, whose source name keys this schema.
86    pub ty: ResolvedLeafType,
87    /// Required fields in canonical constructor order.
88    pub fields: Vec<RecordField>,
89    /// The constructor, invoked only after field validation.
90    pub emit: RecordEmitter,
91}
92
93/// Stores the first marker selected for a function name.
94pub(crate) struct RegisteredFunction {
95    pub(crate) name: &'static str,
96    pub(crate) marker: ResolvedLeafType,
97    pub(crate) emit: OperationEmitter,
98}
99
100/// Holds permanent registrations independently of transient parser scopes.
101#[derive(Default)]
102pub(crate) struct RegisteredOps {
103    operations: Vec<OperationSignature>,
104    functions: Vec<RegisteredFunction>,
105    marker_names: Vec<(TypeId, &'static str)>,
106    records: Vec<RecordSchema>,
107}
108
109/// Compares concrete recursive identities without treating diagnostic names as identity.
110///
111/// - Complexity: O(n) in recursive descriptor nodes.
112fn same_type(left: &ResolvedType, right: &ResolvedType) -> bool {
113    match (left.complete_value_type(), right.complete_value_type()) {
114        (Ok(left), Ok(right)) => left.same_shape(&right),
115        _ => false,
116    }
117}
118
119/// Matches the complete runtime descriptor, never just an erased array marker.
120///
121/// - Complexity: O(n) in recursive descriptor nodes.
122fn matches_type(expected: &ResolvedType, actual: &ValueType) -> bool {
123    expected
124        .complete_value_type()
125        .is_ok_and(|expected| expected.same_shape(actual))
126}
127
128impl OpLookup {
129    /// Returns the concrete marker emitted by a bare registered function name.
130    ///
131    /// - Complexity: O(f) for f registered functions.
132    pub(crate) fn registered_function_type(&self, name: &str) -> Option<&ResolvedLeafType> {
133        self.registered
134            .functions
135            .iter()
136            .find(|function| function.name == name)
137            .map(|function| &function.marker)
138    }
139
140    /// Returns the callable name selected by executable dispatch for a concrete marker.
141    ///
142    /// - Complexity: O(f) for f registered functions.
143    pub(crate) fn registered_function_name(&self, marker: TypeId) -> Option<&'static str> {
144        self.registered
145            .functions
146            .iter()
147            .find(|function| function.marker.type_id() == marker)
148            .map(|function| function.name)
149    }
150
151    /// Validates required named fields and emits one record from their original stack order.
152    ///
153    /// Field descriptors must match exactly, including recursive array identities. Validation
154    /// never changes the segment; only a fully validated constructor invokes its emitter.
155    ///
156    /// - Precondition: the stack ends with one value per field, starting at `ambient_start`.
157    /// - Complexity: O(r + f² + f * d) for r schemas, f fields and descriptor depth d.
158    ///
159    /// # Errors
160    /// Returns an error for unknown records, unknown/duplicate/missing fields, mismatched
161    /// field types, or a constructor emitter's setup failure.
162    ///
163    /// # Examples
164    /// ```
165    /// use cel_parser::{CELParser, OpLookup, RecordSchema, ResolvedLeafType};
166    /// use cel_runtime::ValueType;
167    /// let mut lookup = OpLookup::new();
168    /// lookup.register_record(RecordSchema {
169    ///     ty: ResolvedLeafType::new("Empty", ValueType::leaf::<()>()),
170    ///     fields: vec![],
171    ///     emit: Box::new(|s, _, _, _| { s.just(()); Ok(()) }),
172    /// }).unwrap();
173    /// CELParser::new(lookup).parse_str("Empty {}").unwrap().call0::<()>().unwrap();
174    /// ```
175    pub fn construct_record(
176        &self,
177        name: &str,
178        fields: &[(String, ExprSpan)],
179        ambient_start: usize,
180        segment: &mut DynSegment,
181        start: proc_macro2::Span,
182        end: proc_macro2::Span,
183    ) -> crate::Result<()> {
184        let error = |message| ParseError::new_range(message, start, end);
185        let schema = self
186            .record_schema(name)
187            .ok_or_else(|| error(format!("unknown record `{name}`")))?;
188        let mut mapping = Vec::with_capacity(fields.len());
189        for (index, (field, span)) in fields.iter().enumerate() {
190            let schema_index = schema
191                .fields
192                .iter()
193                .position(|candidate| candidate.name == field)
194                .ok_or_else(|| {
195                    ParseError::new_range(
196                        format!("unknown field `{field}` in record `{name}`"),
197                        span.start,
198                        span.end,
199                    )
200                })?;
201            if fields[..index]
202                .iter()
203                .any(|(previous, _)| previous == field)
204            {
205                return Err(ParseError::new_range(
206                    format!("duplicate field `{field}` in record `{name}`"),
207                    span.start,
208                    span.end,
209                ));
210            }
211            mapping.push(schema_index);
212        }
213        if let Some(missing) = schema
214            .fields
215            .iter()
216            .enumerate()
217            .find(|(index, _)| !mapping.contains(index))
218        {
219            return Err(error(format!(
220                "missing field `{}` in record `{name}`",
221                missing.1.name
222            )));
223        }
224        let operands = segment.peek_stack_infos(fields.len());
225        debug_assert_eq!(operands.len(), fields.len());
226        debug_assert_eq!(
227            segment.stack_offset_before_top(fields.len()).ok(),
228            Some(ambient_start)
229        );
230        for ((actual, &schema_index), (field, span)) in operands.iter().zip(&mapping).zip(fields) {
231            let expected = &schema.fields[schema_index].ty;
232            if !matches_type(expected, &actual.value_type) {
233                return Err(ParseError::new_range(
234                    format!(
235                        "field `{field}`: type mismatch: expected `{}`",
236                        expected.display_name()
237                    ),
238                    span.start,
239                    span.end,
240                ));
241            }
242        }
243        (schema.emit)(
244            segment,
245            &mapping,
246            ambient_start,
247            SourceSpan::from_proc_macro2_range(start, end),
248        )
249        .map_err(|err| error(err.to_string()))?;
250        segment
251            .annotate_top(
252                schema
253                    .ty
254                    .complete_value_type()
255                    .map_err(|err| error(err.to_string()))?
256                    .clone(),
257            )
258            .map_err(|err| error(err.to_string()))
259    }
260
261    /// Registers one persistent named operation without replacing existing overloads.
262    ///
263    /// # Errors
264    /// Returns an error for unavailable complete factories or unrepresentable layouts, and
265    /// duplicate operand identities, including return-type-only overloads.
266    ///
267    /// - Complexity: O(s * a * d) for s signatures, a operands and descriptor depth d.
268    ///
269    /// # Examples
270    /// See the [registration tutorial](crate::registered_ops).
271    pub fn register_operation(&mut self, signature: OperationSignature) -> Result<()> {
272        for operand in &signature.operands {
273            operand.complete_value_type()?;
274        }
275        signature.result.complete_value_type()?;
276        ensure!(
277            !self.operation_signatures(signature.name).any(|existing| {
278                existing.operands.len() == signature.operands.len()
279                    && existing
280                        .operands
281                        .iter()
282                        .zip(&signature.operands)
283                        .all(|(a, b)| same_type(a, b))
284            }),
285            "duplicate operation signature for `{}`",
286            signature.name
287        );
288        self.registered.operations.push(signature);
289        Ok(())
290    }
291
292    /// Binds a persistent callable name, retaining its first marker across installers.
293    ///
294    /// Markers require `Send + Sync` so lookup retains its thread-safety guarantees.
295    ///
296    /// # Errors
297    /// Returns an error if this marker type has already named a distinct function.
298    ///
299    /// - Complexity: O(f) for f registered marker types.
300    ///
301    /// # Examples
302    /// See the [registration tutorial](crate::registered_ops).
303    pub fn register_function<M: Clone + Send + Sync + 'static>(
304        &mut self,
305        name: &'static str,
306        marker: M,
307    ) -> Result<()> {
308        let marker_type = TypeId::of::<M>();
309        ensure!(
310            self.registered
311                .marker_names
312                .iter()
313                .all(|(ty, bound_name)| *ty != marker_type || *bound_name == name),
314            "function marker already names another function"
315        );
316        if !self.registered.marker_names.contains(&(marker_type, name)) {
317            self.registered.marker_names.push((marker_type, name));
318        }
319        if !self
320            .registered
321            .functions
322            .iter()
323            .any(|function| function.name == name)
324        {
325            self.registered.functions.push(RegisteredFunction {
326                name,
327                marker: ResolvedLeafType::new(name, ValueType::cloneable_leaf::<M>()),
328                emit: Box::new(move |segment, _| {
329                    segment.just(marker.clone());
330                    Ok(())
331                }),
332            });
333        }
334        Ok(())
335    }
336
337    /// Registers a persistent record schema keyed by its source type name.
338    ///
339    /// # Errors
340    /// Returns an error for unavailable complete factories or unrepresentable layouts, a
341    /// duplicate schema name, or duplicate field names.
342    ///
343    /// - Complexity: O(r + f²) for r schemas and f fields.
344    ///
345    /// # Examples
346    /// ```
347    /// use cel_parser::{OpLookup, RecordSchema, ResolvedLeafType};
348    /// use cel_runtime::ValueType;
349    /// let mut lookup = OpLookup::new();
350    /// lookup.register_record(RecordSchema {
351    ///     ty: ResolvedLeafType::new("Unit", ValueType::leaf::<()>()),
352    ///     fields: vec![],
353    ///     emit: Box::new(|segment, _, _, _| { segment.just(()); Ok(()) }),
354    /// }).unwrap();
355    /// assert!(lookup.record_schema("Unit").is_some());
356    /// ```
357    pub fn register_record(&mut self, schema: RecordSchema) -> Result<()> {
358        schema.ty.complete_value_type()?;
359        for field in &schema.fields {
360            field.ty.complete_value_type()?;
361        }
362        ensure!(
363            self.record_schema(schema.ty.type_name()).is_none(),
364            "duplicate record schema `{}`",
365            schema.ty.type_name()
366        );
367        for (index, field) in schema.fields.iter().enumerate() {
368            ensure!(
369                !schema.fields[..index]
370                    .iter()
371                    .any(|other| other.name == field.name),
372                "duplicate record field `{}`",
373                field.name
374            );
375        }
376        self.registered.records.push(schema);
377        Ok(())
378    }
379
380    /// Iterates persistent signatures named `name` in registration order.
381    ///
382    /// - Complexity: O(s) to exhaust s registered signatures.
383    ///
384    /// # Examples
385    /// See the [registration tutorial](crate::registered_ops).
386    pub fn operation_signatures(&self, name: &str) -> impl Iterator<Item = &OperationSignature> {
387        self.registered
388            .operations
389            .iter()
390            .filter(move |signature| signature.name == name)
391    }
392
393    /// Returns the persistent schema with the given source type name.
394    ///
395    /// - Complexity: O(r) for r registered schemas.
396    ///
397    /// # Examples
398    /// See [`Self::register_record`].
399    pub fn record_schema(&self, name: &str) -> Option<&RecordSchema> {
400        self.registered
401            .records
402            .iter()
403            .find(|schema| schema.ty.type_name() == name)
404    }
405
406    /// Dispatches a registered signature only after every operand descriptor matches.
407    ///
408    /// Declining a signature leaves operand descriptors and emitted operations unchanged.
409    ///
410    /// # Errors
411    /// Returns the selected emitter's setup error.
412    ///
413    /// - Complexity: O(s * a * d) for s signatures, a operands and descriptor depth d.
414    pub(crate) fn lookup_registered_operation(
415        &self,
416        name: &str,
417        segment: &mut DynSegment,
418        arity: usize,
419        span: SourceSpan,
420    ) -> Result<bool> {
421        let operands = segment.peek_stack_infos(arity);
422        if operands.len() != arity {
423            return Ok(false);
424        }
425        for signature in self.operation_signatures(name) {
426            if signature.operands.len() == arity
427                && signature
428                    .operands
429                    .iter()
430                    .zip(operands)
431                    .all(|(expected, actual)| matches_type(expected, &actual.value_type))
432            {
433                (signature.emit)(segment, span)?;
434                segment.annotate_top(signature.result.complete_value_type()?.clone())?;
435                return Ok(true);
436            }
437        }
438        Ok(false)
439    }
440
441    /// Resolves a bare registered name or a marker-qualified ordinary call.
442    ///
443    /// # Errors
444    /// Returns a selected scope/emitter error or a checked stack-offset error.
445    ///
446    /// - Complexity: O(f + k + s * a * d + n) for f functions, k scopes,
447    ///   s signatures, a operands, descriptor depth d and n ambient stack entries.
448    pub(crate) fn lookup_registered(
449        &self,
450        name: &str,
451        segment: &mut DynSegment,
452        arity: usize,
453        span: SourceSpan,
454        library_only: bool,
455    ) -> Result<bool> {
456        if arity == 0
457            && let Some(function) = self.registered.functions.iter().find(|f| f.name == name)
458        {
459            (function.emit)(segment, span)?;
460            return Ok(true);
461        }
462        if name == "()" && arity > 0 {
463            let operands = segment.peek_stack_infos(arity);
464            if operands.len() != arity {
465                return Ok(false);
466            }
467            let marker_type = operands[0].value_type.type_id();
468            if let Some(function) = self
469                .registered
470                .functions
471                .iter()
472                .find(|f| f.marker.type_id() == marker_type)
473            {
474                let ambient_start = segment.stack_offset_before_top(arity)?;
475                if self.lookup_direct_operation_scoped(
476                    function.name,
477                    segment,
478                    arity - 1,
479                    span,
480                    library_only,
481                )? {
482                    segment.make_tuple(2, ambient_start)?;
483                    segment.tuple_index(1)?;
484                    return Ok(true);
485                }
486                return Ok(false);
487            }
488        }
489        self.lookup_registered_operation(name, segment, arity, span)
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    /// Supplies a complete scalar descriptor for registration contracts.
498    fn scalar<T: 'static>(name: &'static str) -> ResolvedType {
499        ResolvedLeafType::new(name, ValueType::leaf::<T>()).into()
500    }
501
502    /// Schema validation rejects duplicate fields and successful construction retains its descriptor.
503    #[test]
504    fn record_schema_validation_precedes_construction() {
505        let mut lookup = OpLookup::new();
506        let invalid = RecordSchema {
507            ty: ResolvedLeafType::new("Invalid", ValueType::leaf::<u8>()),
508            fields: vec![
509                RecordField {
510                    name: "x",
511                    ty: scalar::<i32>("i32"),
512                },
513                RecordField {
514                    name: "x",
515                    ty: scalar::<i32>("i32"),
516                },
517            ],
518            emit: Box::new(|_, _, _, _| panic!("invalid schema must not be installed")),
519        };
520        assert!(lookup.register_record(invalid).is_err());
521        assert!(lookup.record_schema("Invalid").is_none());
522        lookup
523            .register_record(RecordSchema {
524                ty: ResolvedLeafType::new("Empty", ValueType::leaf::<u8>()),
525                fields: Vec::new(),
526                emit: Box::new(|segment, mapping, ambient, _| {
527                    assert!(mapping.is_empty());
528                    assert_eq!(ambient, 0);
529                    segment.just(9u8);
530                    Ok(())
531                }),
532            })
533            .unwrap();
534        let mut segment = DynSegment::new::<()>();
535        lookup
536            .construct_record(
537                "Empty",
538                &[],
539                0,
540                &mut segment,
541                proc_macro2::Span::call_site(),
542                proc_macro2::Span::call_site(),
543            )
544            .unwrap();
545        assert_eq!(
546            segment.peek_stack_infos(1)[0].value_type.type_name(),
547            "Empty"
548        );
549        assert_eq!(segment.call0::<u8>().unwrap(), 9);
550    }
551
552    /// Builds one signature whose execution adds an observable offset.
553    fn increment() -> OperationSignature {
554        OperationSignature {
555            name: "increment",
556            operands: vec![scalar::<i32>("i32")],
557            result: scalar::<i32>("i32"),
558            emit: Box::new(|segment, _| {
559                segment.op1r(|value: i32| {
560                    value
561                        .checked_add(1)
562                        .ok_or_else(|| anyhow::anyhow!("overflow"))
563                })
564            }),
565        }
566    }
567
568    /// Aliases compare by complete identity, while nested element mismatches remain distinct.
569    #[test]
570    fn complete_identity_does_not_depend_on_diagnostic_names() {
571        assert!(same_type(&scalar::<i32>("i32"), &scalar::<i32>("Alias")));
572        assert!(!same_type(&scalar::<i32>("i32"), &scalar::<u32>("i32")));
573        let array = ResolvedType::Array {
574            element: Box::new(scalar::<i32>("i32")),
575        };
576        assert!(matches_type(
577            &array,
578            &ValueType::array(ValueType::leaf::<i32>())
579        ));
580        assert!(!matches_type(
581            &array,
582            &ValueType::array(ValueType::leaf::<u32>())
583        ));
584    }
585
586    /// Duplicate rejection preserves the first signature and its working emitter.
587    #[test]
588    fn registration_and_dispatch_preserve_the_first_overload() {
589        let mut lookup = OpLookup::new();
590        lookup.register_operation(increment()).unwrap();
591        let mut duplicate = increment();
592        duplicate.result = scalar::<bool>("bool");
593        assert!(lookup.register_operation(duplicate).is_err());
594        assert_eq!(lookup.operation_signatures("increment").count(), 1);
595        assert_eq!(lookup.operation_signatures("missing").count(), 0);
596        let mut segment = DynSegment::new::<()>();
597        segment.just(41i32);
598        assert!(
599            !lookup
600                .lookup_registered_operation(
601                    "missing",
602                    &mut segment,
603                    1,
604                    SourceSpan::new(1, 0, 1, 1)
605                )
606                .unwrap()
607        );
608        assert!(
609            lookup
610                .lookup_registered_operation(
611                    "increment",
612                    &mut segment,
613                    1,
614                    SourceSpan::new(1, 0, 1, 1)
615                )
616                .unwrap()
617        );
618        assert_eq!(segment.call0::<i32>().unwrap(), 42);
619    }
620
621    /// A marker cannot identify two functions and metadata agrees with executable dispatch.
622    #[test]
623    fn function_markers_have_one_name_and_emit_the_registered_value() {
624        let mut lookup = OpLookup::new();
625        lookup.register_function("marker", 17u8).unwrap();
626        assert!(lookup.register_function("other", 18u8).is_err());
627        assert_eq!(
628            lookup.registered_function_name(TypeId::of::<u8>()),
629            Some("marker")
630        );
631        assert_eq!(
632            lookup.registered_function_type("marker").unwrap().type_id(),
633            TypeId::of::<u8>()
634        );
635        let mut segment = DynSegment::new::<()>();
636        assert!(
637            lookup
638                .lookup_registered(
639                    "marker",
640                    &mut segment,
641                    0,
642                    SourceSpan::new(1, 0, 1, 1),
643                    false
644                )
645                .unwrap()
646        );
647        assert_eq!(segment.call0::<u8>().unwrap(), 17);
648    }
649}