Skip to main content

adam_lang/
type_registry.rs

1//! Type registry for adam-lang cell declarations.
2//!
3//! [`TypeRegistry`] maps DSL type-name strings to Rust types. Each registration
4//! stores type-erased function pointers covering:
5//! - `push_arg_fn` — registers a [`cel_runtime::DynSegment::push_arg`] op
6//! - `add_cell_fn` — creates a sheet cell from a `Box<dyn Any>` value
7//! - `add_source_fn` — creates a source-kind sheet cell (via [`Sheet::add_source`]) from a
8//!   `Box<dyn Any>` value
9//! - `call_dyn_fn` — an explicit checked host-call convenience for registered leaves
10//! - `default_fn`  — constructs a default `Box<dyn Any>` (when `Default` is available)
11//!
12//! Persistent compound storage owns canonical values, not external tuple wrappers
13//! wrappers. Sheet construction freezes complete descriptors and raw-frame adapters once;
14//! the legacy external tuple helpers below are host-boundary utilities only.
15//!
16//! # Example
17//!
18//! ```rust
19//! use adam_lang::TypeRegistry;
20//! use std::any::TypeId;
21//!
22//! let reg = TypeRegistry::new();
23//! assert_eq!(reg.get("f64").unwrap().type_id, TypeId::of::<f64>());
24//! ```
25
26use 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/// Identifies a declared cell recursively, independently of its erased storage carrier.
34///
35/// Only concrete registered leaves carry Rust `TypeId`s; compound identities are structural.
36#[derive(Clone, PartialEq, Eq, Hash, Debug)]
37pub enum TypeShape {
38    /// A registered leaf type, by its Rust `TypeId`.
39    Named(TypeId),
40    /// A tuple type, recursively — an empty `Vec` for `()`.
41    Tuple(Vec<TypeShape>),
42    /// A canonical array whose element identity is recursive.
43    Array(Box<TypeShape>),
44    /// A canonical callable with source-ordered parameters and one result.
45    Callable(Vec<TypeShape>, Box<TypeShape>),
46}
47
48/// Registers a `push_arg<T>(index)` op on a segment.
49pub type PushArgFn = fn(&mut DynSegment, usize);
50
51/// Adds a typed cell from a boxed value and returns its handle.
52pub type AddCellFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
53
54/// Adds a typed source cell (via [`Sheet::add_source`]) from a boxed value and returns its
55/// handle.
56pub type AddSourceFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
57
58/// Executes a compiled segment with the supplied inputs and boxes the result.
59pub type CallDynFn = fn(&mut DynSegment, &[&dyn Any]) -> anyhow::Result<Box<dyn Any>>;
60
61/// Calls `Sheet::add_conditional` with the appropriate concrete type.
62///
63/// Each branch carries a single boxed key value and the `RelationshipId`s active for that
64/// branch. The default is a list of `RelationshipId`s active when no branch key matches.
65pub 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/// Per-numeric-type support for a `RangeInclusive<T>`-typed filter expression, keyed by the
73/// range's own `TypeId` — populated in [`TypeRegistry::new`] for exactly the primitives
74/// `cel-parser`'s `..=` operator supports (`cel_parser::op_table`'s `RANGE_INCLUSIVE_SIGNATURES`).
75#[allow(clippy::type_complexity)] // `clamp_fn`/`bounds_fn` are inherently multi-argument fn pointers.
76#[derive(Clone)]
77pub(crate) struct RangeEntry {
78    /// `T`'s own `TypeId` — compared against a filtered cell's declared type by the caller.
79    pub(crate) element_type_id: TypeId,
80    /// Evaluates a compiled segment producing a `RangeInclusive<T>` against `value` (the
81    /// candidate being conformed — read only if the segment's expression actually references
82    /// `_`) and `args` (the filter's deduced cell dependencies, in declaration order), clamping
83    /// `value` into the resulting bounds.
84    pub(crate) clamp_fn: fn(cel_runtime::OwnedValue, &dyn Any) -> anyhow::Result<Box<dyn Any>>,
85    /// Evaluates the same kind of segment against `placeholder` (substituted for `_`, which a
86    /// recognized range expression's bounds never actually depend on) and `args`, returning the
87    /// resulting `(lo, hi)` bounds.
88    pub(crate) bounds_fn:
89        fn(cel_runtime::OwnedValue) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)>,
90}
91
92/// Metadata for a single type registered in a [`TypeRegistry`].
93#[derive(Clone)]
94pub struct TypeEntry {
95    /// Moves an exactly bound canonical leaf into its concrete Adam storage.
96    pub(crate) box_owned: unsafe fn(cel_runtime::OwnedValue) -> Box<dyn Any>,
97    /// Runtime type identity.
98    pub type_id: TypeId,
99    /// Rust type name for error messages.
100    pub type_name: &'static str,
101    /// Registers a `push_arg<T>` op at the given argument index.
102    pub push_arg_fn: PushArgFn,
103    /// Creates a sheet cell from a `Box<dyn Any>` holding a `T`.
104    pub add_cell_fn: AddCellFn,
105    /// Creates a source-kind sheet cell (via [`Sheet::add_source`]) from a `Box<dyn Any>` holding
106    /// a `T`.
107    pub add_source_fn: AddSourceFn,
108    /// Calls `DynSegment::call_dyn::<T>` and boxes the result.
109    pub call_dyn_fn: CallDynFn,
110    /// Compares two type-erased values of this type for equality.
111    pub eq_dyn_fn: fn(&dyn Any, &dyn Any) -> bool,
112    /// Constructs a default `T` if the type implements `Default`; otherwise `None`.
113    pub default_fn: Option<fn() -> Box<dyn Any>>,
114    /// Calls `Sheet::add_conditional::<T>` with type-erased branch keys.
115    pub add_conditional_fn: AddConditionalFn,
116    /// Builds complete concrete leaf metadata with the registered capabilities.
117    pub value_type_fn: fn() -> cel_runtime::value_type::ValueType,
118}
119
120/// Maps DSL type names to Rust types for adam-lang cell declarations.
121///
122/// # Example
123///
124/// ```rust
125/// use adam_lang::TypeRegistry;
126///
127/// let mut reg = TypeRegistry::new();
128/// assert!(reg.get("i32").is_some());
129/// assert!(reg.get("unknown").is_none());
130/// ```
131#[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
142/// Moves a leaf whose exact identity was validated when preparing the adapter.
143///
144/// # Safety
145/// `value` must have the complete representation `Leaf<T>`.
146unsafe 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
150/// Calls `Sheet::add_conditional::<T>` from type-erased branch data.
151///
152/// - Precondition: each `Box<dyn Any>` in `branches` holds a value of type `T`.
153fn 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
178/// Adds `value` to `sheet` as a source-kind cell (via [`Sheet::add_source`]), for
179/// `TypeEntry::add_source_fn`.
180///
181/// - Precondition: `value` holds a `T` matching the registration that produced this fn pointer.
182fn 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
189/// Compares two type-erased values of `T`, for `TypeEntry::eq_dyn_fn`.
190///
191/// A generic function monomorphized per registered `T`, with no captured state — this is
192/// what lets it coerce to a bare `fn` pointer despite `T` only being known via a runtime
193/// `TypeId` at the call site (exactly like `call_dyn_impl` already does for calling a
194/// compiled segment).
195fn 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
206/// Builds complete leaf ownership and the capabilities required by Adam registration.
207fn 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
217/// Evaluates `seg` (producing a `RangeInclusive<T>`) against `value` prepended to `args`, then
218/// clamps `value` into the resulting bounds. For [`RangeEntry::clamp_fn`].
219fn range_clamp_impl<T: Clone + PartialOrd + 'static>(
220    result: cel_runtime::OwnedValue,
221    value: &dyn Any,
222) -> anyhow::Result<Box<dyn Any>> {
223    // SAFETY: The registered range and cell descriptors are bound during preparation.
224    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
237/// Evaluates `seg` (producing a `RangeInclusive<T>`) against `placeholder` prepended to `args`,
238/// returning the resulting `(lo, hi)` bounds. For [`RangeEntry::bounds_fn`].
239fn range_bounds_impl<T: Clone + 'static>(
240    result: cel_runtime::OwnedValue,
241) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)> {
242    // SAFETY: This callback is selected only after binding the exact range descriptor.
243    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    /// Retains recursive identities while installing registered leaf capabilities.
253    ///
254    /// # Errors
255    /// Returns an error if a tuple layout is unrepresentable.
256    ///
257    /// - Complexity: O(n) in descriptor nodes.
258    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    /// Returns a registered leaf's source-facing name and complete CEL element descriptor.
284    ///
285    /// - Complexity: O(l) to clone the registered name of length l.
286    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    /// Builds a CEL-facing resolver snapshot from this registry's registered leaf types.
293    ///
294    /// The returned resolver owns only the name-to-descriptor metadata CEL needs for typed array
295    /// annotations, keeping sheet construction and other Adam-specific function pointers out of
296    /// `cel-parser`.
297    ///
298    /// - Complexity: O(n) in the number of registered leaf types.
299    #[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    /// Creates a registry pre-populated with all built-in CEL/Rust primitive types.
315    ///
316    /// Registered types: `i8`, `i16`, `i32`, `i64`, `i128`, `isize`,
317    /// `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `String`.
318    #[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    /// Registers `T` under `name` with default initialization support.
359    ///
360    /// - Postcondition: `self.get(name)` returns `Some(entry)` with `entry.default_fn.is_some()`.
361    ///
362    /// # Examples
363    ///
364    /// ```rust
365    /// use adam_lang::TypeRegistry;
366    /// let mut reg = TypeRegistry::new();
367    /// reg.register::<u64>("my_u64");
368    /// assert!(reg.get("my_u64").is_some());
369    /// ```
370    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    /// Registers `T` under `name` without default initialization support.
398    ///
399    /// A cell declared as `cell x: T;` (no initializer) is a parse error for this type.
400    ///
401    /// - Postcondition: `self.get(name)` returns `Some(entry)` with `entry.default_fn.is_none()`.
402    ///
403    /// # Examples
404    ///
405    /// ```rust
406    /// use adam_lang::TypeRegistry;
407    /// #[derive(PartialEq, Clone, Debug)]
408    /// struct MyType(i32);
409    /// let mut reg = TypeRegistry::new();
410    /// reg.register_no_default::<MyType>("MyType");
411    /// let entry = reg.get("MyType").unwrap();
412    /// assert!(entry.default_fn.is_none());
413    /// ```
414    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    /// Looks up a type entry by its DSL name.
442    ///
443    /// Returns `None` if `name` has not been registered.
444    ///
445    /// # Examples
446    ///
447    /// ```rust
448    /// use adam_lang::TypeRegistry;
449    /// let reg = TypeRegistry::new();
450    /// assert!(reg.get("f64").is_some());
451    /// assert!(reg.get("nonexistent").is_none());
452    /// ```
453    #[must_use]
454    pub fn get(&self, name: &str) -> Option<&TypeEntry> {
455        self.by_name.get(name)
456    }
457
458    /// Looks up a type entry by its `TypeId`.
459    ///
460    /// Returns `None` if no type with this `TypeId` has been registered.
461    ///
462    /// # Examples
463    ///
464    /// ```rust
465    /// use adam_lang::TypeRegistry;
466    /// use std::any::TypeId;
467    /// let reg = TypeRegistry::new();
468    /// assert!(reg.entry_by_type_id(TypeId::of::<f64>()).is_some());
469    /// assert!(reg.entry_by_type_id(TypeId::of::<Vec<u8>>()).is_none());
470    /// ```
471    #[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    /// Registers `RangeInclusive<T>` support for `T`, keyed by `RangeInclusive<T>`'s own
478    /// `TypeId`. Private — only called from [`TypeRegistry::new`] for the fixed set of numeric
479    /// primitives `cel-parser`'s `..=` operator supports; unlike [`TypeRegistry::register`], this
480    /// is not part of the public API a host binary extends for its own custom types, since
481    /// `RangeInclusive<T>` recognition is not extensible per-type in this codebase's current
482    /// design.
483    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    /// Looks up `RangeInclusive<T>` support by the range's own `TypeId`.
495    ///
496    /// Returns `None` if `range_type_id` is not `RangeInclusive<T>` for any `T` this registry
497    /// recognizes range support for (see [`TypeRegistry::new`]).
498    #[allow(dead_code)] // Used by Task 4 (parser.rs)
499    pub(crate) fn range_entry(&self, range_type_id: TypeId) -> Option<&RangeEntry> {
500        self.range_inclusive.get(&range_type_id)
501    }
502
503    /// Resolves a parsed `type_expr` against this registry, recursively.
504    ///
505    /// # Errors
506    /// Returns an unknown leaf name, malformed callable signature, or unrepresentable layout.
507    /// Registered generic leaves use their complete source name, e.g. `RangeInclusive(i32)`.
508    ///
509    /// - Complexity: O(n) in type nodes and registered-name bytes.
510    ///
511    /// # Examples
512    /// ```
513    /// let registry = adam_lang::TypeRegistry::new();
514    /// let ty = cel_parser::CELParser::new(cel_parser::OpLookup::new())
515    ///     .parse_type_expr_str("[(i32, [f64])]").unwrap();
516    /// assert_eq!(registry.display_name(&registry.resolve(&ty).unwrap()), "[(i32, [f64])]");
517    /// ```
518    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    /// Returns the DSL name `type_id` was registered under, or `None` if it is unregistered.
534    ///
535    /// This is the name every diagnostic should use for a live value's type: a `TypeEntry`'s own
536    /// `type_name` is the Rust type path (`alloc::string::String`), not the registered name.
537    ///
538    /// # Examples
539    ///
540    /// ```rust
541    /// use adam_lang::TypeRegistry;
542    /// let reg = TypeRegistry::new();
543    /// assert_eq!(reg.registered_name(std::any::TypeId::of::<String>()), Some("String"));
544    /// assert_eq!(reg.registered_name(std::any::TypeId::of::<Vec<u8>>()), None);
545    /// ```
546    #[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    /// Formats `shape` recursively, e.g. `"(i32, (f64, String))"`, for error messages.
552    #[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    /// Builds one `AssociatedType` entry describing `shape`: a leaf's own registered layout for
574    /// `TypeShape::Named`, or a nested tuple's own recursively-laid-out elements (via
575    /// `cel_runtime::ValueType::tuple`) for `TypeShape::Tuple`.
576    ///
577    /// - Precondition: every leaf `TypeId` reachable from `shape` is registered.
578    ///
579    /// - Postcondition: the returned entry's `offset` is always 0; the caller
580    ///   (`ValueType::tuple`) computes canonical tail-first sibling offsets.
581    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    /// Returns the canonical descriptor of a resolved shape.
606    ///
607    /// - Precondition: Every named leaf is registered.
608    /// - Complexity: O(n) in shape nodes.
609    pub(crate) fn value_type(&self, shape: &TypeShape) -> cel_runtime::ValueType {
610        self.one_associated(shape).value_type
611    }
612
613    /// Recovers structural identity without synthesizing aggregate Rust identities.
614    ///
615    /// # Errors
616    /// Names an unregistered leaf, including leaves nested in a signature.
617    /// - Complexity: O(n) in descriptor nodes.
618    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    /// Checks default availability without constructing any values.
648    ///
649    /// Arrays need no element defaults; tuple fields are checked in source order.
650    /// # Errors
651    /// Names the first missing leaf default or callable requiring an initializer.
652    /// - Complexity: O(n) in constructed shape nodes.
653    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    /// Emits recursive defaults, leaving arrays empty without constructing their elements.
679    ///
680    /// Cell builders preflight default availability and cell capabilities for the entire
681    /// descriptor before calling this effectful emitter.
682    ///
683    /// # Errors
684    /// Names the precise missing default leaf; callables require an initializer.
685    /// - Complexity: O(n) in constructed shape nodes.
686    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/// A CEL-facing snapshot of one [`TypeRegistry`]'s registered leaf types.
721#[derive(Clone, Default)]
722pub struct RegistryTypeResolver {
723    by_name: HashMap<String, ResolvedLeafType>,
724}
725
726impl TypeResolver for RegistryTypeResolver {
727    /// Resolves registered plain or generic leaves, then CEL built-ins.
728    ///
729    /// - Complexity: O(n) in generic argument nodes and name bytes.
730    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    /// Resolves a leaf without allocating a complete registry snapshot.
741    ///
742    /// - Complexity: O(n) in generic argument nodes and name bytes.
743    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
751/// Borrows simple names and formats only explicitly parameterized leaf lookups.
752///
753/// - Complexity: O(n) in generic argument nodes and name bytes.
754fn 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    /// Returns `TypeRegistry::new()`.
770    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    /// Installs complete factories while foreign concrete types are still known.
796    #[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        // Compiles and runs without panicking: add_cell_fn is callable.
901    }
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        // Vec<u8> is not a registered built-in type.
937        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        // After overwriting, TypeA's TypeId must no longer be found.
955        assert!(
956            reg.entry_by_type_id(TypeId::of::<TypeA>()).is_none(),
957            "stale TypeA mapping should have been removed"
958        );
959        // TypeB must be reachable by both name and TypeId.
960        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    /// Registered leaves retain independent canonical clone, equality, and debug plans.
983    #[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    /// Both resolvers retain requested names and concrete identities across alias orders.
1099    #[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    /// Aliases never rewrite registered keys inside recursive generic arguments.
1129    #[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(&registry).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(&registry).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    /// Compound defaults compose arrays and tuples, and reject leaves lacking defaults.
1251    #[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}