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}