1use std::any::{Any, TypeId};
27use std::collections::HashMap;
28
29use adam_rs::{CellId, ConditionalId, MatchExpr, RelationshipId, Sheet};
30use cel_parser::{ResolvedLeafType, ResolvedType, TypeResolver};
31use cel_runtime::DynSegment;
32
33#[derive(Clone, PartialEq, Eq, Hash, Debug)]
37pub enum TypeShape {
38 Named(TypeId),
40 Tuple(Vec<TypeShape>),
42 Array(Box<TypeShape>),
44 Callable(Vec<TypeShape>, Box<TypeShape>),
46}
47
48pub type PushArgFn = fn(&mut DynSegment, usize);
50
51pub type AddCellFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
53
54pub type AddSourceFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
57
58pub type CallDynFn = fn(&mut DynSegment, &[&dyn Any]) -> anyhow::Result<Box<dyn Any>>;
60
61pub type AddConditionalFn = fn(
66 &mut Sheet,
67 MatchExpr,
68 Vec<(Box<dyn Any>, Vec<RelationshipId>)>,
69 Vec<RelationshipId>,
70) -> Result<ConditionalId, adam_rs::Error>;
71
72#[allow(clippy::type_complexity)] #[derive(Clone)]
77pub(crate) struct RangeEntry {
78 pub(crate) element_type_id: TypeId,
80 pub(crate) clamp_fn: fn(cel_runtime::OwnedValue, &dyn Any) -> anyhow::Result<Box<dyn Any>>,
85 pub(crate) bounds_fn:
89 fn(cel_runtime::OwnedValue) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)>,
90}
91
92#[derive(Clone)]
94pub struct TypeEntry {
95 pub(crate) box_owned: unsafe fn(cel_runtime::OwnedValue) -> Box<dyn Any>,
97 pub type_id: TypeId,
99 pub type_name: &'static str,
101 pub push_arg_fn: PushArgFn,
103 pub add_cell_fn: AddCellFn,
105 pub add_source_fn: AddSourceFn,
108 pub call_dyn_fn: CallDynFn,
110 pub eq_dyn_fn: fn(&dyn Any, &dyn Any) -> bool,
112 pub default_fn: Option<fn() -> Box<dyn Any>>,
114 pub add_conditional_fn: AddConditionalFn,
116 pub value_type_fn: fn() -> cel_runtime::value_type::ValueType,
118}
119
120#[derive(Clone)]
132pub struct TypeRegistry {
133 by_name: HashMap<String, TypeEntry>,
134 by_type_id: HashMap<TypeId, String>,
135 range_inclusive: HashMap<TypeId, RangeEntry>,
136}
137
138fn push_arg_impl<T: 'static + Clone>(segment: &mut DynSegment, index: usize) {
139 segment.push_arg::<T>(index);
140}
141
142unsafe fn box_owned_impl<T: 'static>(value: cel_runtime::OwnedValue) -> Box<dyn Any> {
147 Box::new(unsafe { value.into_typed_bound::<cel_runtime::Leaf<T>>() })
148}
149
150fn add_conditional_impl<T: Any + PartialEq + 'static>(
154 sheet: &mut Sheet,
155 source: MatchExpr,
156 branches: Vec<(Box<dyn Any>, Vec<RelationshipId>)>,
157 default: Vec<RelationshipId>,
158) -> Result<ConditionalId, adam_rs::Error> {
159 let typed_branches: Vec<(Vec<T>, Vec<RelationshipId>)> = branches
160 .into_iter()
161 .map(|(val, rel_ids)| {
162 let v = *val
163 .downcast::<T>()
164 .expect("add_conditional_impl: type matches registration");
165 (vec![v], rel_ids)
166 })
167 .collect();
168 sheet.add_conditional::<T>(source, typed_branches, default)
169}
170
171fn add_cell_impl<T: Any + PartialEq + 'static>(sheet: &mut Sheet, value: Box<dyn Any>) -> CellId {
172 let v = value
173 .downcast::<T>()
174 .expect("add_cell_impl: type matches registration");
175 sheet.add_cell(*v)
176}
177
178fn add_source_impl<T: Any + PartialEq + 'static>(sheet: &mut Sheet, value: Box<dyn Any>) -> CellId {
183 let v = value
184 .downcast::<T>()
185 .expect("add_source_impl: type matches registration");
186 sheet.add_source(*v)
187}
188
189fn eq_dyn_impl<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
196 a.downcast_ref::<T>() == b.downcast_ref::<T>()
197}
198
199fn call_dyn_impl<T: 'static + Clone>(
200 seg: &mut DynSegment,
201 inputs: &[&dyn Any],
202) -> anyhow::Result<Box<dyn Any>> {
203 Ok(Box::new(seg.call_dyn::<T>(inputs)?))
204}
205
206fn value_type_impl<T: Clone + PartialEq + std::fmt::Debug + 'static>()
208-> cel_runtime::value_type::ValueType {
209 use cel_runtime::value_type::{Leaf, ValueType};
210 ValueType::cloneable_leaf::<T>()
211 .with_equality::<Leaf<T>>()
212 .expect("same registered leaf")
213 .with_debug::<Leaf<T>>()
214 .expect("same registered leaf")
215}
216
217fn range_clamp_impl<T: Clone + PartialOrd + 'static>(
220 result: cel_runtime::OwnedValue,
221 value: &dyn Any,
222) -> anyhow::Result<Box<dyn Any>> {
223 let range =
225 unsafe { result.into_typed_bound::<cel_runtime::Leaf<std::ops::RangeInclusive<T>>>() };
226 let v = unsafe { &*std::ptr::from_ref(value).cast::<T>() };
227 let clamped = if v < range.start() {
228 range.start().clone()
229 } else if v > range.end() {
230 range.end().clone()
231 } else {
232 v.clone()
233 };
234 Ok(Box::new(clamped) as Box<dyn Any>)
235}
236
237fn range_bounds_impl<T: Clone + 'static>(
240 result: cel_runtime::OwnedValue,
241) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)> {
242 let range =
244 unsafe { result.into_typed_bound::<cel_runtime::Leaf<std::ops::RangeInclusive<T>>>() };
245 Ok((
246 Box::new(range.start().clone()) as Box<dyn Any>,
247 Box::new(range.end().clone()) as Box<dyn Any>,
248 ))
249}
250
251impl TypeRegistry {
252 pub(crate) fn enrich_value_type(
259 &self,
260 ty: &cel_runtime::ValueType,
261 ) -> anyhow::Result<cel_runtime::ValueType> {
262 use cel_runtime::{AssociatedType, ValueKind, ValueType};
263 match ty.kind() {
264 ValueKind::Leaf(id) => Ok(self
265 .entry_by_type_id(*id)
266 .map_or_else(|| ty.clone(), |entry| (entry.value_type_fn)())),
267 ValueKind::Array(element) => Ok(ValueType::array(self.enrich_value_type(element)?)),
268 ValueKind::Tuple(fields) => ValueType::tuple(
269 fields
270 .iter()
271 .map(|field| {
272 Ok(AssociatedType {
273 offset: 0,
274 value_type: self.enrich_value_type(&field.value_type)?,
275 })
276 })
277 .collect::<anyhow::Result<_>>()?,
278 ),
279 ValueKind::Callable { .. } => Ok(ty.clone()),
280 }
281 }
282
283 pub(crate) fn resolved_leaf_type(&self, type_id: TypeId) -> Option<ResolvedLeafType> {
287 let name = self.by_type_id.get(&type_id)?;
288 let entry = self.by_name.get(name)?;
289 Some(ResolvedLeafType::new(name.clone(), (entry.value_type_fn)()))
290 }
291
292 #[must_use]
300 pub fn cel_type_resolver(&self) -> RegistryTypeResolver {
301 let by_name = self
302 .by_name
303 .iter()
304 .map(|(name, entry)| {
305 (
306 name.clone(),
307 ResolvedLeafType::new(name.clone(), (entry.value_type_fn)()),
308 )
309 })
310 .collect();
311 RegistryTypeResolver { by_name }
312 }
313
314 #[must_use]
319 pub fn new() -> Self {
320 let mut r = TypeRegistry {
321 by_name: HashMap::new(),
322 by_type_id: HashMap::new(),
323 range_inclusive: HashMap::new(),
324 };
325 r.register::<i8>("i8");
326 r.register::<i16>("i16");
327 r.register::<i32>("i32");
328 r.register::<i64>("i64");
329 r.register::<i128>("i128");
330 r.register::<isize>("isize");
331 r.register::<u8>("u8");
332 r.register::<u16>("u16");
333 r.register::<u32>("u32");
334 r.register::<u64>("u64");
335 r.register::<u128>("u128");
336 r.register::<usize>("usize");
337 r.register::<f32>("f32");
338 r.register::<f64>("f64");
339 r.register::<bool>("bool");
340 r.register::<String>("String");
341 r.register_range_inclusive::<i8>();
342 r.register_range_inclusive::<i16>();
343 r.register_range_inclusive::<i32>();
344 r.register_range_inclusive::<i64>();
345 r.register_range_inclusive::<i128>();
346 r.register_range_inclusive::<isize>();
347 r.register_range_inclusive::<u8>();
348 r.register_range_inclusive::<u16>();
349 r.register_range_inclusive::<u32>();
350 r.register_range_inclusive::<u64>();
351 r.register_range_inclusive::<u128>();
352 r.register_range_inclusive::<usize>();
353 r.register_range_inclusive::<f32>();
354 r.register_range_inclusive::<f64>();
355 r
356 }
357
358 pub fn register<T: Any + PartialEq + Default + Clone + std::fmt::Debug + 'static>(
371 &mut self,
372 name: &str,
373 ) {
374 let type_id = TypeId::of::<T>();
375 if let Some(old) = self.by_name.get(name) {
376 self.by_type_id.remove(&old.type_id);
377 }
378 self.by_name.insert(
379 name.to_owned(),
380 TypeEntry {
381 box_owned: box_owned_impl::<T>,
382 type_id,
383 type_name: std::any::type_name::<T>(),
384 push_arg_fn: push_arg_impl::<T>,
385 add_cell_fn: add_cell_impl::<T>,
386 add_source_fn: add_source_impl::<T>,
387 call_dyn_fn: call_dyn_impl::<T>,
388 eq_dyn_fn: eq_dyn_impl::<T>,
389 default_fn: Some(|| Box::new(T::default()) as Box<dyn Any>),
390 add_conditional_fn: add_conditional_impl::<T>,
391 value_type_fn: value_type_impl::<T>,
392 },
393 );
394 self.by_type_id.insert(type_id, name.to_owned());
395 }
396
397 pub fn register_no_default<T: Any + PartialEq + Clone + std::fmt::Debug + 'static>(
415 &mut self,
416 name: &str,
417 ) {
418 let type_id = TypeId::of::<T>();
419 if let Some(old) = self.by_name.get(name) {
420 self.by_type_id.remove(&old.type_id);
421 }
422 self.by_name.insert(
423 name.to_owned(),
424 TypeEntry {
425 box_owned: box_owned_impl::<T>,
426 type_id,
427 type_name: std::any::type_name::<T>(),
428 push_arg_fn: push_arg_impl::<T>,
429 add_cell_fn: add_cell_impl::<T>,
430 add_source_fn: add_source_impl::<T>,
431 call_dyn_fn: call_dyn_impl::<T>,
432 eq_dyn_fn: eq_dyn_impl::<T>,
433 default_fn: None,
434 add_conditional_fn: add_conditional_impl::<T>,
435 value_type_fn: value_type_impl::<T>,
436 },
437 );
438 self.by_type_id.insert(type_id, name.to_owned());
439 }
440
441 #[must_use]
454 pub fn get(&self, name: &str) -> Option<&TypeEntry> {
455 self.by_name.get(name)
456 }
457
458 #[must_use]
472 pub fn entry_by_type_id(&self, type_id: TypeId) -> Option<&TypeEntry> {
473 let name = self.by_type_id.get(&type_id)?;
474 self.by_name.get(name)
475 }
476
477 fn register_range_inclusive<T: Clone + PartialOrd + 'static>(&mut self) {
484 self.range_inclusive.insert(
485 TypeId::of::<std::ops::RangeInclusive<T>>(),
486 RangeEntry {
487 element_type_id: TypeId::of::<T>(),
488 clamp_fn: range_clamp_impl::<T>,
489 bounds_fn: range_bounds_impl::<T>,
490 },
491 );
492 }
493
494 #[allow(dead_code)] pub(crate) fn range_entry(&self, range_type_id: TypeId) -> Option<&RangeEntry> {
500 self.range_inclusive.get(&range_type_id)
501 }
502
503 pub fn resolve(
519 &self,
520 expr: &cel_parser::TypeExpr,
521 ) -> std::result::Result<TypeShape, (String, proc_macro2::Span)> {
522 let resolved = expr
523 .resolve(self)
524 .map_err(|error| (error.message().to_string(), error.span()))?;
525 self.shape_of(
526 &resolved
527 .into_value_type()
528 .map_err(|error| (error.to_string(), expr.span().start))?,
529 )
530 .map_err(|error| (error, expr.span().start))
531 }
532
533 #[must_use]
547 pub fn registered_name(&self, type_id: TypeId) -> Option<&str> {
548 self.by_type_id.get(&type_id).map(String::as_str)
549 }
550
551 #[must_use]
553 pub fn display_name(&self, shape: &TypeShape) -> String {
554 match shape {
555 TypeShape::Named(type_id) => self.registered_name(*type_id).unwrap_or("?").to_string(),
556 TypeShape::Tuple(elements) => {
557 let parts: Vec<String> = elements.iter().map(|e| self.display_name(e)).collect();
558 format!(
559 "({}{})",
560 parts.join(", "),
561 if elements.len() == 1 { "," } else { "" }
562 )
563 }
564 TypeShape::Array(element) => format!("[{}]", self.display_name(element)),
565 TypeShape::Callable(parameters, result) => format!(
566 "Fn({}, {})",
567 self.display_name(&TypeShape::Tuple(parameters.clone())),
568 self.display_name(result)
569 ),
570 }
571 }
572
573 fn one_associated(&self, shape: &TypeShape) -> cel_runtime::AssociatedType {
582 let value_type = match shape {
583 TypeShape::Named(type_id) => {
584 let entry = self
585 .entry_by_type_id(*type_id)
586 .expect("one_associated: type registered");
587 (entry.value_type_fn)()
588 }
589 TypeShape::Tuple(elements) => cel_runtime::ValueType::tuple(
590 elements.iter().map(|e| self.one_associated(e)).collect(),
591 )
592 .expect("registered tuple layout"),
593 TypeShape::Array(element) => cel_runtime::ValueType::array(self.value_type(element)),
594 TypeShape::Callable(parameters, result) => cel_runtime::ValueType::callable(
595 parameters.iter().map(|p| self.value_type(p)).collect(),
596 self.value_type(result),
597 ),
598 };
599 cel_runtime::AssociatedType {
600 offset: 0,
601 value_type,
602 }
603 }
604
605 pub(crate) fn value_type(&self, shape: &TypeShape) -> cel_runtime::ValueType {
610 self.one_associated(shape).value_type
611 }
612
613 pub(crate) fn shape_of(&self, ty: &cel_runtime::ValueType) -> Result<TypeShape, String> {
619 use cel_runtime::ValueKind;
620 Ok(match ty.kind() {
621 ValueKind::Leaf(id) => {
622 if self.entry_by_type_id(*id).is_none() {
623 return Err(format!(
624 "cannot infer a type: unregistered type `{}`",
625 ty.type_name()
626 ));
627 }
628 TypeShape::Named(*id)
629 }
630 ValueKind::Array(element) => TypeShape::Array(Box::new(self.shape_of(element)?)),
631 ValueKind::Tuple(fields) => TypeShape::Tuple(
632 fields
633 .iter()
634 .map(|field| self.shape_of(&field.value_type))
635 .collect::<Result<_, _>>()?,
636 ),
637 ValueKind::Callable { parameters, result } => TypeShape::Callable(
638 parameters
639 .iter()
640 .map(|p| self.shape_of(p))
641 .collect::<Result<_, _>>()?,
642 Box::new(self.shape_of(result)?),
643 ),
644 })
645 }
646
647 pub(crate) fn validate_default(&self, shape: &TypeShape) -> anyhow::Result<()> {
654 match shape {
655 TypeShape::Named(id) => {
656 anyhow::ensure!(
657 self.entry_by_type_id(*id)
658 .expect("resolved leaf")
659 .default_fn
660 .is_some(),
661 "type `{}` has no default; provide `= ...`",
662 self.display_name(shape)
663 );
664 }
665 TypeShape::Tuple(fields) => {
666 for field in fields {
667 self.validate_default(field)?;
668 }
669 }
670 TypeShape::Array(_) => {}
671 TypeShape::Callable(_, _) => {
672 anyhow::bail!("callable has no default; provide an initializer `= ...`")
673 }
674 }
675 Ok(())
676 }
677
678 pub(crate) fn emit_default(
687 &self,
688 shape: &TypeShape,
689 body: &mut DynSegment,
690 ) -> anyhow::Result<()> {
691 let origin = body.current_stack_offset();
692 match shape {
693 TypeShape::Named(id) => {
694 let entry = self.entry_by_type_id(*id).expect("resolved leaf");
695 let default = entry.default_fn.ok_or_else(|| {
696 anyhow::anyhow!(
697 "type `{}` has no default; provide `= ...`",
698 self.display_name(shape)
699 )
700 })?;
701 body.just_external(self.value_type(shape), default())?;
702 }
703 TypeShape::Tuple(fields) => {
704 for field in fields {
705 self.emit_default(field, body)?;
706 }
707 body.make_tuple(fields.len(), origin)?;
708 }
709 TypeShape::Array(element) => {
710 body.make_typed_array(0, origin, self.value_type(element))?
711 }
712 TypeShape::Callable(_, _) => {
713 anyhow::bail!("callable has no default; provide an initializer `= ...`")
714 }
715 }
716 Ok(())
717 }
718}
719
720#[derive(Clone, Default)]
722pub struct RegistryTypeResolver {
723 by_name: HashMap<String, ResolvedLeafType>,
724}
725
726impl TypeResolver for RegistryTypeResolver {
727 fn resolve_named_type(&self, name: &str, args: &[ResolvedType]) -> Option<ResolvedLeafType> {
731 let key = named_key(name, args);
732 if let Some(leaf) = self.by_name.get(key.as_ref()).cloned() {
733 return Some(leaf);
734 }
735 cel_parser::resolve_builtin_named_type(name, args)
736 }
737}
738
739impl TypeResolver for TypeRegistry {
740 fn resolve_named_type(&self, name: &str, args: &[ResolvedType]) -> Option<ResolvedLeafType> {
744 let key = named_key(name, args);
745 self.get(key.as_ref())
746 .map(|entry| ResolvedLeafType::new(key.into_owned(), (entry.value_type_fn)()))
747 .or_else(|| cel_parser::resolve_builtin_named_type(name, args))
748 }
749}
750
751fn named_key<'a>(name: &'a str, args: &[ResolvedType]) -> std::borrow::Cow<'a, str> {
755 if args.is_empty() {
756 std::borrow::Cow::Borrowed(name)
757 } else {
758 std::borrow::Cow::Owned(format!(
759 "{name}({})",
760 args.iter()
761 .map(ResolvedType::display_name)
762 .collect::<Vec<_>>()
763 .join(", ")
764 ))
765 }
766}
767
768impl Default for TypeRegistry {
769 fn default() -> Self {
771 Self::new()
772 }
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778 use proc_macro2::Span;
779 use std::any::TypeId;
780
781 fn point(span: Span) -> crate::ast::ExprSpan {
782 crate::ast::ExprSpan {
783 start: span,
784 end: span,
785 }
786 }
787
788 #[test]
789 fn new_registers_builtin_i32() {
790 let reg = TypeRegistry::new();
791 let entry = reg.get("i32").expect("i32 registered");
792 assert_eq!(entry.type_id, TypeId::of::<i32>());
793 }
794
795 #[test]
797 fn complete_factory_registered_foreign_leaf_retains_capabilities() {
798 use cel_runtime::{owned_value::OwnedValue, value_type::Leaf};
799 let mut registry = TypeRegistry::new();
800 registry.register::<std::path::PathBuf>("Path");
801 registry.register_no_default::<std::ops::RangeInclusive<i32>>("Bounds");
802 let entry = registry.get("Path").unwrap();
803 let descriptor = (entry.value_type_fn)();
804 let owner = OwnedValue::new::<Leaf<std::path::PathBuf>>("example".into())
805 .with_value_type(descriptor)
806 .unwrap();
807 assert!(owner.try_eq(&owner.try_clone().unwrap()).unwrap());
808 assert_eq!(owner.try_debug().unwrap(), "\"example\"");
809 for name in ["Path", "Bounds"] {
810 let resolved = registry
811 .cel_type_resolver()
812 .resolve_named_type(name, &[])
813 .unwrap();
814 assert_eq!(resolved.type_name(), name);
815 assert!(
816 resolved
817 .complete_value_type()
818 .unwrap()
819 .same_shape(&(registry.get(name).unwrap().value_type_fn)())
820 );
821 }
822 }
823
824 #[test]
825 fn new_registers_builtin_f64_with_default() {
826 let reg = TypeRegistry::new();
827 let entry = reg.get("f64").expect("f64 registered");
828 assert_eq!(entry.type_id, TypeId::of::<f64>());
829 assert!(entry.default_fn.is_some(), "f64 must have a default");
830 }
831
832 #[test]
833 fn new_registers_builtin_string() {
834 let reg = TypeRegistry::new();
835 let entry = reg.get("String").expect("String registered");
836 assert_eq!(entry.type_id, TypeId::of::<String>());
837 }
838
839 #[test]
840 fn register_custom_type_with_default() {
841 let mut reg = TypeRegistry::new();
842 reg.register::<u64>("my_u64");
843 let entry = reg.get("my_u64").expect("custom type registered");
844 assert_eq!(entry.type_id, TypeId::of::<u64>());
845 assert!(entry.default_fn.is_some());
846 }
847
848 #[test]
849 fn register_no_default_has_no_default_fn() {
850 #[derive(PartialEq, Clone, Debug)]
851 struct NoDefault(i32);
852
853 let mut reg = TypeRegistry::new();
854 reg.register_no_default::<NoDefault>("no_default");
855 let entry = reg.get("no_default").expect("registered");
856 assert!(entry.default_fn.is_none());
857 }
858
859 #[test]
860 fn cel_type_resolver_uses_registered_leaf_names() {
861 #[derive(PartialEq, Clone, Debug)]
862 struct Custom(i32);
863
864 let mut reg = TypeRegistry::new();
865 reg.register_no_default::<Custom>("Custom");
866
867 let resolver = reg.cel_type_resolver();
868 let leaf = resolver
869 .resolve_named_type("Custom", &[])
870 .expect("custom leaf registered");
871
872 assert_eq!(leaf.type_id(), TypeId::of::<Custom>());
873 assert_eq!(leaf.type_name(), "Custom");
874 }
875
876 #[test]
877 fn push_arg_fn_drives_call_dyn() {
878 use cel_runtime::DynSegment;
879 use std::any::Any;
880
881 let reg = TypeRegistry::new();
882 let entry = reg.get("i32").unwrap();
883 let mut seg = DynSegment::new::<()>();
884 (entry.push_arg_fn)(&mut seg, 0);
885 let x: i32 = 7;
886 let result: i32 = seg.call_dyn(&[&x as &dyn Any]).unwrap();
887 assert_eq!(result, 7);
888 }
889
890 #[test]
891 fn add_cell_fn_creates_cell() {
892 use adam_rs::Sheet;
893 use std::any::Any;
894
895 let reg = TypeRegistry::new();
896 let entry = reg.get("f64").unwrap();
897 let mut sheet = Sheet::new();
898 let val: Box<dyn Any> = Box::new(42.14_f64);
899 let _cell_id = (entry.add_cell_fn)(&mut sheet, val);
900 }
902
903 #[test]
904 fn call_dyn_fn_returns_boxed_result() {
905 use cel_runtime::DynSegment;
906 use std::any::Any;
907
908 let reg = TypeRegistry::new();
909 let entry = reg.get("i32").unwrap();
910 let mut seg = DynSegment::new::<()>();
911 (entry.push_arg_fn)(&mut seg, 0);
912 let x: i32 = 99;
913 let boxed = (entry.call_dyn_fn)(&mut seg, &[&x as &dyn Any]).unwrap();
914 let result = boxed.downcast::<i32>().expect("i32");
915 assert_eq!(*result, 99);
916 }
917
918 #[test]
919 fn entry_by_type_id_roundtrip() {
920 let reg = TypeRegistry::new();
921 let entry = reg
922 .entry_by_type_id(std::any::TypeId::of::<f64>())
923 .expect("f64 registered");
924 assert_eq!(entry.type_id, std::any::TypeId::of::<f64>());
925 }
926
927 #[test]
928 fn get_nonexistent_returns_none() {
929 let reg = TypeRegistry::new();
930 assert!(reg.get("nonexistent").is_none());
931 }
932
933 #[test]
934 fn entry_by_type_id_nonexistent_returns_none() {
935 let reg = TypeRegistry::new();
936 assert!(
938 reg.entry_by_type_id(std::any::TypeId::of::<Vec<u8>>())
939 .is_none()
940 );
941 }
942
943 #[test]
944 fn register_overwrite_removes_stale_type_id() {
945 #[derive(PartialEq, Clone, Default, Debug)]
946 struct TypeA;
947 #[derive(PartialEq, Clone, Default, Debug)]
948 struct TypeB;
949
950 let mut reg = TypeRegistry::new();
951 reg.register::<TypeA>("alias");
952 reg.register::<TypeB>("alias");
953
954 assert!(
956 reg.entry_by_type_id(TypeId::of::<TypeA>()).is_none(),
957 "stale TypeA mapping should have been removed"
958 );
959 assert!(reg.entry_by_type_id(TypeId::of::<TypeB>()).is_some());
961 assert_eq!(reg.get("alias").unwrap().type_id, TypeId::of::<TypeB>());
962 }
963
964 #[test]
965 fn register_no_default_overwrite_removes_stale_type_id() {
966 #[derive(PartialEq, Clone, Debug)]
967 struct TypeA;
968 #[derive(PartialEq, Clone, Debug)]
969 struct TypeB;
970
971 let mut reg = TypeRegistry::new();
972 reg.register_no_default::<TypeA>("alias");
973 reg.register_no_default::<TypeB>("alias");
974
975 assert!(
976 reg.entry_by_type_id(TypeId::of::<TypeA>()).is_none(),
977 "stale TypeA mapping should have been removed"
978 );
979 assert!(reg.entry_by_type_id(TypeId::of::<TypeB>()).is_some());
980 }
981
982 #[test]
984 fn registered_leaf_capabilities_operate_on_canonical_owners() {
985 let reg = TypeRegistry::new();
986 let entry = reg.get("i32").unwrap();
987 let value = cel_runtime::OwnedValue::new::<cel_runtime::Leaf<i32>>(7)
988 .with_value_type((entry.value_type_fn)())
989 .unwrap();
990 let cloned = value.try_clone().unwrap();
991 assert!(value.try_eq(&cloned).unwrap());
992 assert_eq!(cloned.try_debug().unwrap(), "7");
993 }
994
995 #[test]
996 fn resolve_named_type_expr_returns_the_matching_type_shape() {
997 let reg = TypeRegistry::new();
998 let expr = cel_parser::TypeExpr::Named {
999 name: "i32".to_string(),
1000 args: Vec::new(),
1001 span: point(Span::call_site()),
1002 };
1003 let shape = reg.resolve(&expr).unwrap();
1004 assert_eq!(shape, TypeShape::Named(TypeId::of::<i32>()));
1005 }
1006
1007 #[test]
1008 fn resolve_unknown_named_type_expr_is_an_error() {
1009 let reg = TypeRegistry::new();
1010 let expr = cel_parser::TypeExpr::Named {
1011 name: "bogus".to_string(),
1012 args: Vec::new(),
1013 span: point(Span::call_site()),
1014 };
1015 assert!(reg.resolve(&expr).is_err());
1016 }
1017
1018 #[test]
1019 fn resolve_tuple_type_expr_returns_a_nested_type_shape() {
1020 let reg = TypeRegistry::new();
1021 let span = point(Span::call_site());
1022 let expr = cel_parser::TypeExpr::Tuple {
1023 elements: vec![
1024 cel_parser::TypeExpr::Named {
1025 name: "i32".to_string(),
1026 args: Vec::new(),
1027 span,
1028 },
1029 cel_parser::TypeExpr::Tuple {
1030 elements: vec![
1031 cel_parser::TypeExpr::Named {
1032 name: "f64".to_string(),
1033 args: Vec::new(),
1034 span,
1035 },
1036 cel_parser::TypeExpr::Named {
1037 name: "String".to_string(),
1038 args: Vec::new(),
1039 span,
1040 },
1041 ],
1042 span,
1043 },
1044 ],
1045 span,
1046 };
1047 let shape = reg.resolve(&expr).unwrap();
1048 assert_eq!(
1049 shape,
1050 TypeShape::Tuple(vec![
1051 TypeShape::Named(TypeId::of::<i32>()),
1052 TypeShape::Tuple(vec![
1053 TypeShape::Named(TypeId::of::<f64>()),
1054 TypeShape::Named(TypeId::of::<String>()),
1055 ]),
1056 ])
1057 );
1058 }
1059
1060 #[test]
1061 fn resolve_accepts_structural_array_cell_type() {
1062 let registry = TypeRegistry::new();
1063 let span = point(Span::call_site());
1064 let expr = cel_parser::TypeExpr::Array {
1065 element: Box::new(cel_parser::TypeExpr::Named {
1066 name: "i32".to_string(),
1067 args: Vec::new(),
1068 span,
1069 }),
1070 span,
1071 };
1072 assert_eq!(
1073 registry.resolve(&expr).unwrap(),
1074 TypeShape::Array(Box::new(TypeShape::Named(TypeId::of::<i32>())))
1075 );
1076 }
1077
1078 #[test]
1079 fn resolve_accepts_registered_generic_leaf() {
1080 let mut registry = TypeRegistry::new();
1081 registry.register_no_default::<std::ops::RangeInclusive<f64>>("RangeInclusive(f64)");
1082 let span = point(Span::call_site());
1083 let expr = cel_parser::TypeExpr::Named {
1084 name: "RangeInclusive".to_string(),
1085 args: vec![cel_parser::TypeExpr::Named {
1086 name: "f64".to_string(),
1087 args: Vec::new(),
1088 span,
1089 }],
1090 span,
1091 };
1092 assert_eq!(
1093 registry.resolve(&expr).unwrap(),
1094 TypeShape::Named(TypeId::of::<std::ops::RangeInclusive<f64>>())
1095 );
1096 }
1097
1098 #[test]
1100 fn registered_aliases_preserve_requested_leaf_names() {
1101 for names in [["Integer", "i32"], ["i32", "Integer"]] {
1102 let mut registry = TypeRegistry::new();
1103 for name in names {
1104 registry.register::<i32>(name);
1105 }
1106 let snapshot = registry.cel_type_resolver();
1107 for name in names {
1108 let direct = registry.resolve_named_type(name, &[]).unwrap();
1109 let copied = snapshot.resolve_named_type(name, &[]).unwrap();
1110 assert_eq!(direct.type_name(), name);
1111 assert_eq!(copied.type_name(), name);
1112 assert!(
1113 direct
1114 .complete_value_type()
1115 .unwrap()
1116 .same_shape(&cel_runtime::ValueType::leaf::<i32>())
1117 );
1118 assert!(
1119 direct
1120 .complete_value_type()
1121 .unwrap()
1122 .same_shape(copied.complete_value_type().unwrap())
1123 );
1124 }
1125 }
1126 }
1127
1128 #[test]
1130 fn registered_aliases_preserve_recursive_generic_keys() {
1131 use std::ops::RangeInclusive;
1132
1133 for alias_first in [false, true] {
1134 let mut registry = TypeRegistry::new();
1135 if alias_first {
1136 registry.register::<i32>("Integer");
1137 }
1138 let names = [
1139 "Bounds(i32)",
1140 "Bounds(Integer)",
1141 "Bounds(Bounds(i32))",
1142 "Bounds([i32])",
1143 "Bounds((i32, [Integer]))",
1144 "Bounds((i32,))",
1145 "Bounds(Fn((), Integer))",
1146 "Bounds(Fn((Integer,), [i32]))",
1147 "Bounds(Fn((Integer, [i32]), ()))",
1148 ];
1149 for name in names {
1150 registry.register_no_default::<RangeInclusive<i32>>(name);
1151 }
1152 if !alias_first {
1153 registry.register::<i32>("Integer");
1154 }
1155 let snapshot = registry.cel_type_resolver();
1156 for name in names {
1157 for source in [name.to_owned(), format!("[({name}, [Integer])]")] {
1158 let expr = cel_parser::CELParser::new(cel_parser::OpLookup::new())
1159 .parse_type_expr_str(&source)
1160 .unwrap();
1161 let copied = expr.resolve(&snapshot).unwrap().into_value_type().unwrap();
1162 let direct = expr.resolve(®istry).unwrap();
1163 assert_eq!(direct.display_name(), source);
1164 let direct = direct.into_value_type().unwrap();
1165 assert!(direct.same_shape(&copied), "{source}");
1166 assert_eq!(
1167 registry.resolve(&expr).unwrap(),
1168 registry.shape_of(&copied).unwrap(),
1169 "{source}"
1170 );
1171 }
1172 }
1173 let missing = cel_parser::CELParser::new(cel_parser::OpLookup::new())
1174 .parse_type_expr_str("Bounds([Integer])")
1175 .unwrap();
1176 let direct = missing.resolve(®istry).unwrap_err();
1177 let copied = missing.resolve(&snapshot).unwrap_err();
1178 assert_eq!(direct.message(), copied.message());
1179 assert!(direct.message().contains("unknown type `Bounds`"));
1180 }
1181 }
1182
1183 #[test]
1184 fn cel_type_resolver_falls_back_to_builtin_generics() {
1185 let registry = TypeRegistry::new();
1186 let resolver = registry.cel_type_resolver();
1187 let f64_leaf = resolver.resolve_named_type("f64", &[]).expect("f64");
1188 let resolved = resolver
1189 .resolve_named_type("RangeInclusive", &[ResolvedType::Scalar(f64_leaf)])
1190 .expect("RangeInclusive(f64) should resolve via the builtin fallback");
1191 assert_eq!(resolved.type_name(), "RangeInclusive(f64)");
1192 }
1193
1194 #[test]
1195 fn display_name_formats_a_nested_tuple_shape() {
1196 let reg = TypeRegistry::new();
1197 let shape = TypeShape::Tuple(vec![
1198 TypeShape::Named(TypeId::of::<i32>()),
1199 TypeShape::Tuple(vec![
1200 TypeShape::Named(TypeId::of::<f64>()),
1201 TypeShape::Named(TypeId::of::<String>()),
1202 ]),
1203 ]);
1204 assert_eq!(reg.display_name(&shape), "(i32, (f64, String))");
1205 }
1206
1207 #[test]
1208 fn canonical_descriptor_describes_a_flat_tuple() {
1209 let reg = TypeRegistry::new();
1210 let shape = TypeShape::Tuple(vec![
1211 TypeShape::Named(TypeId::of::<i32>()),
1212 TypeShape::Named(TypeId::of::<f64>()),
1213 ]);
1214 let descriptor = reg.value_type(&shape);
1215 let prototype = descriptor.tuple_elements().unwrap();
1216 assert_eq!(prototype.len(), 2);
1217 assert_eq!(prototype[0].value_type.type_id(), TypeId::of::<i32>());
1218 assert_eq!(prototype[1].value_type.type_id(), TypeId::of::<f64>());
1219 assert_eq!(prototype[0].offset, 8);
1220 assert_eq!(prototype[1].offset, 0);
1221 }
1222
1223 #[test]
1224 fn canonical_descriptor_describes_a_nested_tuple() {
1225 let reg = TypeRegistry::new();
1226 let shape = TypeShape::Tuple(vec![
1227 TypeShape::Named(TypeId::of::<i32>()),
1228 TypeShape::Tuple(vec![
1229 TypeShape::Named(TypeId::of::<i32>()),
1230 TypeShape::Named(TypeId::of::<i32>()),
1231 ]),
1232 ]);
1233 let descriptor = reg.value_type(&shape);
1234 let prototype = descriptor.tuple_elements().unwrap();
1235 assert_eq!(prototype.len(), 2);
1236 assert_eq!(
1237 prototype[1].value_type.type_id(),
1238 TypeId::of::<cel_runtime::CNil<()>>()
1239 );
1240 assert_eq!(
1241 prototype[1]
1242 .value_type
1243 .tuple_elements()
1244 .expect("nested tuple")
1245 .len(),
1246 2
1247 );
1248 }
1249
1250 #[test]
1252 fn canonical_defaults_compose_and_report_missing_leaf_defaults() {
1253 use cel_runtime::{ArrayOf, Cons, IntoRustTuple, Leaf, Nil};
1254 let parsed = crate::AdamParser::new(TypeRegistry::new(), cel_parser::OpLookup::new())
1255 .parse_str("sheet s { cell x: (i32, ([f64],)); }")
1256 .unwrap();
1257 type Shape = Cons<Leaf<i32>, Cons<Cons<ArrayOf<Leaf<f64>>, Nil>, Nil>>;
1258 let (number, nested) = parsed
1259 .read_compound::<Shape>(parsed.cell_names["x"].0)
1260 .unwrap()
1261 .into_rust_tuple();
1262 assert_eq!(number, 0);
1263 assert!(nested.into_rust_tuple().0.is_empty());
1264 #[derive(Clone, PartialEq, Debug)]
1265 struct NoDefault;
1266 let mut registry = TypeRegistry::new();
1267 registry.register_no_default::<NoDefault>("NoDefault");
1268 let error = crate::AdamParser::new(registry, cel_parser::OpLookup::new())
1269 .parse_str("sheet s { cell x: (NoDefault,); }")
1270 .err()
1271 .unwrap();
1272 assert!(error.to_string().contains("NoDefault"));
1273 }
1274
1275 #[test]
1276 fn eq_dyn_fn_compares_equal_i32_values_as_equal() {
1277 let reg = TypeRegistry::new();
1278 let entry = reg.get("i32").unwrap();
1279 let a: i32 = 7;
1280 let b: i32 = 7;
1281 assert!((entry.eq_dyn_fn)(&a, &b));
1282 }
1283
1284 #[test]
1285 fn eq_dyn_fn_compares_unequal_i32_values_as_unequal() {
1286 let reg = TypeRegistry::new();
1287 let entry = reg.get("i32").unwrap();
1288 let a: i32 = 7;
1289 let b: i32 = 8;
1290 assert!(!(entry.eq_dyn_fn)(&a, &b));
1291 }
1292
1293 #[test]
1294 fn register_no_default_also_populates_eq_dyn_fn() {
1295 #[derive(PartialEq, Clone, Debug)]
1296 struct NoDefault(i32);
1297
1298 let mut reg = TypeRegistry::new();
1299 reg.register_no_default::<NoDefault>("NoDefault");
1300 let entry = reg.get("NoDefault").unwrap();
1301 let a = NoDefault(1);
1302 let b = NoDefault(1);
1303 let c = NoDefault(2);
1304 assert!((entry.eq_dyn_fn)(&a, &b));
1305 assert!(!(entry.eq_dyn_fn)(&a, &c));
1306 }
1307
1308 #[test]
1309 fn range_entry_recognizes_a_registered_numeric_range_inclusive_type() {
1310 let reg = TypeRegistry::new();
1311 let entry = reg
1312 .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1313 .expect("i32 range recognized");
1314 assert_eq!(entry.element_type_id, TypeId::of::<i32>());
1315 }
1316
1317 #[test]
1318 fn range_entry_returns_none_for_a_non_range_type() {
1319 let reg = TypeRegistry::new();
1320 assert!(reg.range_entry(TypeId::of::<i32>()).is_none());
1321 }
1322
1323 #[test]
1324 fn range_entry_clamp_fn_clamps_a_value_into_the_evaluated_bounds() {
1325 let reg = TypeRegistry::new();
1326 let entry = reg
1327 .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1328 .unwrap();
1329 let mut segment = DynSegment::new::<()>();
1330 segment.push_arg::<i32>(1);
1331 segment.push_arg::<i32>(2);
1332 segment.op2(|a: i32, b: i32| a..=b).unwrap();
1333 let value = 500i32;
1334 let lo = 0i32;
1335 let hi = 100i32;
1336 let evaluated = segment.call_dyn_value(&[&value, &lo, &hi]).unwrap();
1337 let result = (entry.clamp_fn)(evaluated, &value).unwrap();
1338 assert_eq!(*result.downcast_ref::<i32>().unwrap(), 100);
1339 }
1340
1341 #[test]
1342 fn range_entry_bounds_fn_returns_the_evaluated_bounds() {
1343 let reg = TypeRegistry::new();
1344 let entry = reg
1345 .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1346 .unwrap();
1347 let mut segment = DynSegment::new::<()>();
1348 segment.push_arg::<i32>(1);
1349 segment.push_arg::<i32>(2);
1350 segment.op2(|a: i32, b: i32| a..=b).unwrap();
1351 let placeholder = 0i32;
1352 let lo = 0i32;
1353 let hi = 100i32;
1354 let evaluated = segment.call_dyn_value(&[&placeholder, &lo, &hi]).unwrap();
1355 let (lo_out, hi_out) = (entry.bounds_fn)(evaluated).unwrap();
1356 assert_eq!(*lo_out.downcast_ref::<i32>().unwrap(), 0);
1357 assert_eq!(*hi_out.downcast_ref::<i32>().unwrap(), 100);
1358 }
1359}