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//! 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///
37/// # Examples
38///
39/// ```
40/// use adam_lang::type_registry::TypeShape;
41/// let pair = TypeShape::Tuple(vec![
42///     TypeShape::Named(std::any::TypeId::of::<i32>()),
43///     TypeShape::Named(std::any::TypeId::of::<bool>()),
44/// ]);
45/// assert!(adam_lang::TypeRegistry::new().validate_shape(&pair).is_ok());
46/// ```
47#[derive(Clone, PartialEq, Eq, Hash, Debug)]
48pub enum TypeShape {
49    /// A registered leaf type, by its Rust `TypeId`.
50    Named(TypeId),
51    /// A tuple type, recursively — an empty `Vec` for `()`.
52    Tuple(Vec<TypeShape>),
53    /// A canonical array whose element identity is recursive.
54    Array(Box<TypeShape>),
55    /// A canonical callable with source-ordered parameters and one result.
56    Callable(Vec<TypeShape>, Box<TypeShape>),
57}
58
59/// Registers a `push_arg<T>(index)` op on a segment.
60///
61/// - Complexity: follows [`DynSegment::push_arg`], including segment storage growth.
62///
63/// # Examples
64///
65/// See [`TypeEntry`] for selecting a concrete registration; [`TypeRegistry::get`]
66/// retains the matching adapter and type identity together.
67pub type PushArgFn = fn(&mut DynSegment, usize);
68
69/// Adds a typed cell from a boxed value and returns its handle.
70///
71/// - Precondition: the box contains the exact registered concrete type.
72/// - Complexity: follows [`Sheet::add_cell`].
73///
74/// # Examples
75///
76/// See [`TypeEntry`] for creating a cell through its selected registration.
77pub type AddCellFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
78
79/// Adds a typed source cell (via [`Sheet::add_source`]) from a boxed value and returns its
80/// handle.
81///
82/// - Precondition: the box contains the exact registered concrete type.
83/// - Complexity: follows [`Sheet::add_source`].
84///
85/// # Examples
86///
87/// See [`TypeEntry`] for creating a source through its selected registration.
88pub type AddSourceFn = fn(&mut Sheet, Box<dyn Any>) -> CellId;
89
90/// Executes a compiled segment with the supplied inputs and boxes the result.
91///
92/// - Complexity: follows dynamic segment evaluation and concrete result cloning.
93///
94/// # Errors
95///
96/// Reports rejected argument or result types and evaluation failures.
97///
98/// # Examples
99///
100/// See [`TypeEntry`] for calling a segment with the registered argument and result adapters.
101pub type CallDynFn = fn(&mut DynSegment, &[&dyn Any]) -> anyhow::Result<Box<dyn Any>>;
102
103/// Calls `Sheet::add_conditional` with the appropriate concrete type.
104///
105/// Each branch carries a single boxed key value and the `RelationshipId`s active for that
106/// branch. The default is a list of `RelationshipId`s active when no branch key matches.
107///
108/// - Precondition: every boxed branch key contains the exact registered concrete type.
109/// - Complexity: O(b + r) time and space for b branches and r relationship identifiers,
110///   plus the sheet's conditional validation.
111///
112/// # Examples
113///
114/// See [`Sheet::add_conditional`] for branch activation and [`TypeRegistry::get`]
115/// for selecting the erased key adapter.
116pub type AddConditionalFn = fn(
117    &mut Sheet,
118    MatchExpr,
119    Vec<(Box<dyn Any>, Vec<RelationshipId>)>,
120    Vec<RelationshipId>,
121) -> Result<ConditionalId, adam_rs::Error>;
122
123/// Per-numeric-type support for a `RangeInclusive<T>`-typed filter expression, keyed by the
124/// range's own `TypeId` — populated in [`TypeRegistry::new`] for exactly the primitives
125/// `cel-parser`'s `..=` operator supports (`cel_parser::op_table`'s `RANGE_INCLUSIVE_SIGNATURES`).
126#[allow(clippy::type_complexity)] // `clamp_fn`/`bounds_fn` are inherently multi-argument fn pointers.
127#[derive(Clone)]
128pub(crate) struct RangeEntry {
129    /// `T`'s own `TypeId` — compared against a filtered cell's declared type by the caller.
130    pub(crate) element_type_id: TypeId,
131    /// Consumes an already evaluated range and clones the candidate or its nearest endpoint.
132    ///
133    /// - Precondition: the range is bound to this registered numeric type and the
134    ///   candidate has `element_type_id`.
135    pub(crate) clamp_fn: fn(cel_runtime::OwnedValue, &dyn Any) -> anyhow::Result<Box<dyn Any>>,
136    /// Consumes an evaluated range and clones both inclusive endpoints.
137    ///
138    /// - Precondition: the result is bound to this registered numeric range type.
139    pub(crate) bounds_fn:
140        fn(cel_runtime::OwnedValue) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)>,
141}
142
143/// Metadata for a single type registered in a [`TypeRegistry`].
144///
145/// Cell/source/conditional adapters require boxed values of `type_id`; callers establish
146/// this before dispatch. Checked host calls report runtime mismatches. Erased equality
147/// returns false if either operand has another concrete type.
148///
149/// # Examples
150///
151/// ```
152/// let registry = adam_lang::TypeRegistry::new();
153/// let entry = registry.get("i32").unwrap();
154/// let mut sheet = adam_rs::Sheet::new();
155/// let cell = (entry.add_cell_fn)(&mut sheet, Box::new(7i32));
156/// assert_eq!(*sheet.read::<i32>(cell).unwrap(), 7);
157/// assert!((entry.eq_dyn_fn)(&7i32, &7i32));
158/// assert!(!(entry.eq_dyn_fn)(&"other", &"other"));
159/// let source = (entry.add_source_fn)(&mut sheet, Box::new(8i32));
160/// assert_eq!(*sheet.read::<i32>(source).unwrap(), 8);
161/// let mut segment = cel_runtime::DynSegment::new::<()>();
162/// (entry.push_arg_fn)(&mut segment, 0);
163/// let result = (entry.call_dyn_fn)(&mut segment, &[&9i32]).unwrap();
164/// assert_eq!(*result.downcast::<i32>().unwrap(), 9);
165/// ```
166#[derive(Clone)]
167pub struct TypeEntry {
168    /// Selects native recipe constructors monomorphized for this registered leaf.
169    pub(crate) template: crate::composition::LeafAdapters,
170    /// Moves an exactly bound canonical leaf into its concrete Adam storage.
171    pub(crate) box_owned: unsafe fn(cel_runtime::OwnedValue) -> Box<dyn Any>,
172    /// Runtime type identity.
173    pub type_id: TypeId,
174    /// Rust type name for error messages.
175    pub type_name: &'static str,
176    /// Registers a `push_arg<T>` op at the given argument index.
177    pub push_arg_fn: PushArgFn,
178    /// Creates a sheet cell from a `Box<dyn Any>` holding a `T`.
179    pub add_cell_fn: AddCellFn,
180    /// Creates a source-kind sheet cell (via [`Sheet::add_source`]) from a `Box<dyn Any>` holding
181    /// a `T`.
182    pub add_source_fn: AddSourceFn,
183    /// Calls `DynSegment::call_dyn::<T>` and boxes the result.
184    pub call_dyn_fn: CallDynFn,
185    /// Compares registered values; returns false if either operand has another concrete type.
186    pub eq_dyn_fn: fn(&dyn Any, &dyn Any) -> bool,
187    /// Constructs a default `T` if the type implements `Default`; otherwise `None`.
188    pub default_fn: Option<fn() -> Box<dyn Any>>,
189    /// Calls `Sheet::add_conditional::<T>` with type-erased branch keys.
190    pub add_conditional_fn: AddConditionalFn,
191    /// Builds complete concrete leaf metadata with the registered capabilities.
192    pub value_type_fn: fn() -> cel_runtime::value_type::ValueType,
193}
194
195/// Maps DSL type names to Rust types for adam-lang cell declarations.
196///
197/// Every registered concrete type remains reverse-reachable while at least one of its
198/// names survives. Registering a new alias makes it the preferred reverse name;
199/// replacing that alias selects a surviving name if one exists.
200///
201/// # Example
202///
203/// ```rust
204/// use adam_lang::TypeRegistry;
205///
206/// let mut reg = TypeRegistry::new();
207/// assert!(reg.get("i32").is_some());
208/// assert!(reg.get("unknown").is_none());
209/// ```
210#[derive(Clone)]
211pub struct TypeRegistry {
212    by_name: HashMap<String, TypeEntry>,
213    by_type_id: HashMap<TypeId, String>,
214    range_inclusive: HashMap<TypeId, RangeEntry>,
215}
216
217/// Appends a typed argument read using this registration's concrete leaf type.
218///
219/// - Complexity: follows [`DynSegment::push_arg`], including segment storage growth.
220fn push_arg_impl<T: 'static + Clone>(segment: &mut DynSegment, index: usize) {
221    segment.push_arg::<T>(index);
222}
223
224/// Moves a leaf whose exact identity was validated when preparing the adapter.
225///
226/// # Safety
227/// `value` must have the complete representation `Leaf<T>`.
228unsafe fn box_owned_impl<T: 'static>(value: cel_runtime::OwnedValue) -> Box<dyn Any> {
229    Box::new(unsafe { value.into_typed_bound::<cel_runtime::Leaf<T>>() })
230}
231
232/// Calls `Sheet::add_conditional::<T>` from type-erased branch data.
233///
234/// - Precondition: each `Box<dyn Any>` in `branches` holds a value of type `T`.
235fn add_conditional_impl<T: Any + PartialEq + 'static>(
236    sheet: &mut Sheet,
237    source: MatchExpr,
238    branches: Vec<(Box<dyn Any>, Vec<RelationshipId>)>,
239    default: Vec<RelationshipId>,
240) -> Result<ConditionalId, adam_rs::Error> {
241    debug_assert!(branches.iter().all(|(value, _)| value.is::<T>()));
242    let typed_branches: Vec<(Vec<T>, Vec<RelationshipId>)> = branches
243        .into_iter()
244        .map(|(val, rel_ids)| {
245            let v = *val
246                .downcast::<T>()
247                .expect("add_conditional_impl: type matches registration");
248            (vec![v], rel_ids)
249        })
250        .collect();
251    sheet.add_conditional::<T>(source, typed_branches, default)
252}
253
254/// Adds one cell whose boxed value has the registration's exact concrete type.
255///
256/// - Precondition: `value` contains `T`.
257fn add_cell_impl<T: Any + PartialEq + 'static>(sheet: &mut Sheet, value: Box<dyn Any>) -> CellId {
258    debug_assert!(value.is::<T>());
259    let v = value
260        .downcast::<T>()
261        .expect("add_cell_impl: type matches registration");
262    sheet.add_cell(*v)
263}
264
265/// Adds `value` to `sheet` as a source-kind cell (via [`Sheet::add_source`]), for
266/// `TypeEntry::add_source_fn`.
267///
268/// - Precondition: `value` holds a `T` matching the registration that produced this fn pointer.
269fn add_source_impl<T: Any + PartialEq + 'static>(sheet: &mut Sheet, value: Box<dyn Any>) -> CellId {
270    debug_assert!(value.is::<T>());
271    let v = value
272        .downcast::<T>()
273        .expect("add_source_impl: type matches registration");
274    sheet.add_source(*v)
275}
276
277/// Compares concrete `T` values and returns false for either mismatched operand.
278///
279/// - Complexity: the concrete type's equality cost; no additional allocation.
280fn eq_dyn_impl<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
281    match (a.downcast_ref::<T>(), b.downcast_ref::<T>()) {
282        (Some(a), Some(b)) => a == b,
283        _ => false,
284    }
285}
286
287/// Executes the checked host-call boundary and boxes its concrete result.
288///
289/// # Errors
290///
291/// Reports rejected argument types, result types, or evaluation failures.
292///
293/// - Complexity: the segment's execution cost plus its result's clone cost.
294fn call_dyn_impl<T: 'static + Clone>(
295    seg: &mut DynSegment,
296    inputs: &[&dyn Any],
297) -> anyhow::Result<Box<dyn Any>> {
298    Ok(Box::new(seg.call_dyn::<T>(inputs)?))
299}
300
301/// Builds complete leaf ownership and the capabilities required by Adam registration.
302fn value_type_impl<T: Clone + PartialEq + std::fmt::Debug + 'static>()
303-> cel_runtime::value_type::ValueType {
304    use cel_runtime::value_type::{Leaf, ValueType};
305    ValueType::cloneable_leaf::<T>()
306        .with_equality::<Leaf<T>>()
307        .expect("same registered leaf")
308        .with_debug::<Leaf<T>>()
309        .expect("same registered leaf")
310}
311
312/// Consumes a bound numeric range and clones the candidate or its nearest endpoint.
313///
314/// - Precondition: `result` is bound to `RangeInclusive<T>` and `value` contains `T`.
315/// - Complexity: one value clone and at most two value comparisons.
316fn range_clamp_impl<T: Clone + PartialOrd + 'static>(
317    result: cel_runtime::OwnedValue,
318    value: &dyn Any,
319) -> anyhow::Result<Box<dyn Any>> {
320    debug_assert!(value.is::<T>());
321    // SAFETY: The registered range and cell descriptors are bound during preparation.
322    let range =
323        unsafe { result.into_typed_bound::<cel_runtime::Leaf<std::ops::RangeInclusive<T>>>() };
324    let v = unsafe { &*std::ptr::from_ref(value).cast::<T>() };
325    let clamped = if v < range.start() {
326        range.start().clone()
327    } else if v > range.end() {
328        range.end().clone()
329    } else {
330        v.clone()
331    };
332    Ok(Box::new(clamped) as Box<dyn Any>)
333}
334
335/// Consumes a bound numeric range and clones both inclusive endpoints.
336///
337/// - Precondition: `result` is bound to `RangeInclusive<T>`.
338/// - Complexity: two endpoint clones.
339fn range_bounds_impl<T: Clone + 'static>(
340    result: cel_runtime::OwnedValue,
341) -> anyhow::Result<(Box<dyn Any>, Box<dyn Any>)> {
342    // SAFETY: This callback is selected only after binding the exact range descriptor.
343    let range =
344        unsafe { result.into_typed_bound::<cel_runtime::Leaf<std::ops::RangeInclusive<T>>>() };
345    Ok((
346        Box::new(range.start().clone()) as Box<dyn Any>,
347        Box::new(range.end().clone()) as Box<dyn Any>,
348    ))
349}
350
351impl TypeRegistry {
352    /// Returns a registered leaf's source-facing name and complete CEL element descriptor.
353    ///
354    /// - Complexity: O(l) to clone the registered name of length l.
355    pub(crate) fn resolved_leaf_type(&self, type_id: TypeId) -> Option<ResolvedLeafType> {
356        let name = self.by_type_id.get(&type_id)?;
357        let entry = self.by_name.get(name)?;
358        Some(ResolvedLeafType::new(name.clone(), (entry.value_type_fn)()))
359    }
360
361    /// Builds a CEL-facing resolver snapshot from this registry's registered leaf types.
362    ///
363    /// The returned resolver owns only the name-to-descriptor metadata CEL needs for typed array
364    /// annotations, keeping sheet construction and other Adam-specific function pointers out of
365    /// `cel-parser`.
366    ///
367    /// - Complexity: O(n + b) time and space for n registered names totaling b bytes.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use cel_parser::TypeResolver;
373    /// let registry = adam_lang::TypeRegistry::new();
374    /// let snapshot = registry.cel_type_resolver();
375    /// drop(registry);
376    /// assert!(snapshot.resolve_named_type("i32", &[]).is_some());
377    /// ```
378    #[must_use]
379    pub fn cel_type_resolver(&self) -> RegistryTypeResolver {
380        let by_name = self
381            .by_name
382            .iter()
383            .map(|(name, entry)| {
384                (
385                    name.clone(),
386                    ResolvedLeafType::new(name.clone(), (entry.value_type_fn)()),
387                )
388            })
389            .collect();
390        RegistryTypeResolver { by_name }
391    }
392
393    /// Creates a registry pre-populated with all built-in CEL/Rust primitive types.
394    ///
395    /// Registered types: `i8`, `i16`, `i32`, `i64`, `i128`, `isize`,
396    /// `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `String`.
397    ///
398    /// # Examples
399    ///
400    /// ```
401    /// let registry = adam_lang::TypeRegistry::new();
402    /// assert_eq!(registry.get("i32").unwrap().type_id, std::any::TypeId::of::<i32>());
403    /// ```
404    #[must_use]
405    pub fn new() -> Self {
406        let mut r = TypeRegistry {
407            by_name: HashMap::new(),
408            by_type_id: HashMap::new(),
409            range_inclusive: HashMap::new(),
410        };
411        r.register::<i8>("i8");
412        r.register::<i16>("i16");
413        r.register::<i32>("i32");
414        r.register::<i64>("i64");
415        r.register::<i128>("i128");
416        r.register::<isize>("isize");
417        r.register::<u8>("u8");
418        r.register::<u16>("u16");
419        r.register::<u32>("u32");
420        r.register::<u64>("u64");
421        r.register::<u128>("u128");
422        r.register::<usize>("usize");
423        r.register::<f32>("f32");
424        r.register::<f64>("f64");
425        r.register::<bool>("bool");
426        r.register::<String>("String");
427        r.register_range_inclusive::<i8>();
428        r.register_range_inclusive::<i16>();
429        r.register_range_inclusive::<i32>();
430        r.register_range_inclusive::<i64>();
431        r.register_range_inclusive::<i128>();
432        r.register_range_inclusive::<isize>();
433        r.register_range_inclusive::<u8>();
434        r.register_range_inclusive::<u16>();
435        r.register_range_inclusive::<u32>();
436        r.register_range_inclusive::<u64>();
437        r.register_range_inclusive::<u128>();
438        r.register_range_inclusive::<usize>();
439        r.register_range_inclusive::<f32>();
440        r.register_range_inclusive::<f64>();
441        r
442    }
443
444    /// Registers `T` under `name` with default initialization support.
445    ///
446    /// - Postcondition: `self.get(name)` returns `Some(entry)` with `entry.default_fn.is_some()`.
447    ///   Surviving aliases remain reachable by name and concrete type identity.
448    /// - Complexity: O(n + k) time and O(k) new name space for n registrations
449    ///   and k copied or compared name bytes.
450    ///
451    /// # Examples
452    ///
453    /// ```rust
454    /// use adam_lang::TypeRegistry;
455    /// let mut reg = TypeRegistry::new();
456    /// reg.register::<u64>("my_u64");
457    /// assert!(reg.get("my_u64").is_some());
458    /// ```
459    pub fn register<T: Any + PartialEq + Default + Clone + std::fmt::Debug + 'static>(
460        &mut self,
461        name: &str,
462    ) {
463        let type_id = TypeId::of::<T>();
464        self.remove_registration_name(name);
465        self.by_name.insert(
466            name.to_owned(),
467            TypeEntry {
468                template: crate::composition::LeafAdapters::of::<T>(),
469                box_owned: box_owned_impl::<T>,
470                type_id,
471                type_name: std::any::type_name::<T>(),
472                push_arg_fn: push_arg_impl::<T>,
473                add_cell_fn: add_cell_impl::<T>,
474                add_source_fn: add_source_impl::<T>,
475                call_dyn_fn: call_dyn_impl::<T>,
476                eq_dyn_fn: eq_dyn_impl::<T>,
477                default_fn: Some(|| Box::new(T::default()) as Box<dyn Any>),
478                add_conditional_fn: add_conditional_impl::<T>,
479                value_type_fn: value_type_impl::<T>,
480            },
481        );
482        self.by_type_id.insert(type_id, name.to_owned());
483    }
484
485    /// Registers `T` under `name` without default initialization support.
486    ///
487    /// A cell declared as `cell x: T;` (no initializer) is a parse error for this type.
488    ///
489    /// - Postcondition: `self.get(name)` returns `Some(entry)` with `entry.default_fn.is_none()`.
490    ///   Surviving aliases remain reachable by name and concrete type identity.
491    /// - Complexity: O(n + k) time and O(k) new name space for n registrations
492    ///   and k copied or compared name bytes.
493    ///
494    /// # Examples
495    ///
496    /// ```rust
497    /// use adam_lang::TypeRegistry;
498    /// #[derive(PartialEq, Clone, Debug)]
499    /// struct MyType(i32);
500    /// let mut reg = TypeRegistry::new();
501    /// reg.register_no_default::<MyType>("MyType");
502    /// let entry = reg.get("MyType").unwrap();
503    /// assert!(entry.default_fn.is_none());
504    /// ```
505    pub fn register_no_default<T: Any + PartialEq + Clone + std::fmt::Debug + 'static>(
506        &mut self,
507        name: &str,
508    ) {
509        let type_id = TypeId::of::<T>();
510        self.remove_registration_name(name);
511        self.by_name.insert(
512            name.to_owned(),
513            TypeEntry {
514                template: crate::composition::LeafAdapters::of::<T>(),
515                box_owned: box_owned_impl::<T>,
516                type_id,
517                type_name: std::any::type_name::<T>(),
518                push_arg_fn: push_arg_impl::<T>,
519                add_cell_fn: add_cell_impl::<T>,
520                add_source_fn: add_source_impl::<T>,
521                call_dyn_fn: call_dyn_impl::<T>,
522                eq_dyn_fn: eq_dyn_impl::<T>,
523                default_fn: None,
524                add_conditional_fn: add_conditional_impl::<T>,
525                value_type_fn: value_type_impl::<T>,
526            },
527        );
528        self.by_type_id.insert(type_id, name.to_owned());
529    }
530
531    /// Removes `name`, retaining reverse lookup through a surviving alias before replacement.
532    ///
533    /// - Complexity: O(n + k) time over n registrations and k inspected or copied
534    ///   name bytes; O(k) space for a surviving name.
535    fn remove_registration_name(&mut self, name: &str) {
536        let Some(old) = self.by_name.remove(name) else {
537            return;
538        };
539        let old_id = old.type_id;
540        if self
541            .by_type_id
542            .get(&old_id)
543            .is_none_or(|canonical| canonical != name)
544        {
545            return;
546        }
547        let remaining = self
548            .by_name
549            .iter()
550            .find(|(_, entry)| entry.type_id == old_id)
551            .map(|(alias, _)| alias.clone());
552        if let Some(alias) = remaining {
553            self.by_type_id.insert(old_id, alias);
554        } else {
555            self.by_type_id.remove(&old_id);
556        }
557    }
558
559    /// Looks up a type entry by its DSL name.
560    ///
561    /// Returns `None` if `name` has not been registered.
562    ///
563    /// - Complexity: expected O(k) time in k name bytes and O(1) space.
564    ///
565    /// # Examples
566    ///
567    /// ```rust
568    /// use adam_lang::TypeRegistry;
569    /// let reg = TypeRegistry::new();
570    /// assert!(reg.get("f64").is_some());
571    /// assert!(reg.get("nonexistent").is_none());
572    /// ```
573    #[must_use]
574    pub fn get(&self, name: &str) -> Option<&TypeEntry> {
575        self.by_name.get(name)
576    }
577
578    /// Looks up a type entry by its `TypeId`.
579    ///
580    /// Returns `None` if no type with this `TypeId` has been registered.
581    ///
582    /// - Complexity: expected O(k) time in the selected registered name's k bytes and O(1) space.
583    ///
584    /// # Examples
585    ///
586    /// ```rust
587    /// use adam_lang::TypeRegistry;
588    /// use std::any::TypeId;
589    /// let reg = TypeRegistry::new();
590    /// assert!(reg.entry_by_type_id(TypeId::of::<f64>()).is_some());
591    /// assert!(reg.entry_by_type_id(TypeId::of::<Vec<u8>>()).is_none());
592    /// ```
593    #[must_use]
594    pub fn entry_by_type_id(&self, type_id: TypeId) -> Option<&TypeEntry> {
595        let name = self.by_type_id.get(&type_id)?;
596        self.by_name.get(name)
597    }
598
599    /// Registers `RangeInclusive<T>` support for `T`, keyed by `RangeInclusive<T>`'s own
600    /// `TypeId`. Private — only called from [`TypeRegistry::new`] for the fixed set of numeric
601    /// primitives `cel-parser`'s `..=` operator supports; unlike [`TypeRegistry::register`], this
602    /// is not part of the public API a host binary extends for its own custom types, since
603    /// `RangeInclusive<T>` recognition is not extensible per-type in this codebase's current
604    /// design.
605    fn register_range_inclusive<T: Clone + PartialOrd + 'static>(&mut self) {
606        self.range_inclusive.insert(
607            TypeId::of::<std::ops::RangeInclusive<T>>(),
608            RangeEntry {
609                element_type_id: TypeId::of::<T>(),
610                clamp_fn: range_clamp_impl::<T>,
611                bounds_fn: range_bounds_impl::<T>,
612            },
613        );
614    }
615
616    /// Looks up `RangeInclusive<T>` support by the range's own `TypeId`.
617    ///
618    /// Returns `None` if `range_type_id` is not `RangeInclusive<T>` for any `T` this registry
619    /// recognizes range support for (see [`TypeRegistry::new`]).
620    #[allow(dead_code)] // Used by Task 4 (parser.rs)
621    pub(crate) fn range_entry(&self, range_type_id: TypeId) -> Option<&RangeEntry> {
622        self.range_inclusive.get(&range_type_id)
623    }
624
625    /// Resolves a parsed `type_expr` against this registry, recursively.
626    ///
627    /// # Errors
628    /// Returns an unknown leaf name, malformed callable signature, or unrepresentable layout.
629    /// Registered generic leaves use their complete source name, e.g. `RangeInclusive(i32)`.
630    ///
631    /// - Complexity: O(n) in type nodes and registered-name bytes.
632    ///
633    /// # Examples
634    /// ```
635    /// let registry = adam_lang::TypeRegistry::new();
636    /// let ty = cel_parser::CELParser::new(cel_parser::OpLookup::new())
637    ///     .parse_type_expr_str("[(i32, [f64])]").unwrap();
638    /// assert_eq!(registry.display_name(&registry.resolve(&ty).unwrap()), "[(i32, [f64])]");
639    /// ```
640    pub fn resolve(
641        &self,
642        expr: &cel_parser::TypeExpr,
643    ) -> std::result::Result<TypeShape, (String, proc_macro2::Span)> {
644        let resolved = expr
645            .resolve(self)
646            .map_err(|error| (error.message().to_string(), error.span()))?;
647        self.shape_of(
648            &resolved
649                .into_value_type()
650                .map_err(|error| (error.to_string(), expr.span().start))?,
651        )
652        .map_err(|error| (error, expr.span().start))
653    }
654
655    /// Returns the DSL name `type_id` was registered under, or `None` if it is unregistered.
656    ///
657    /// This is the name every diagnostic should use for a live value's type: a `TypeEntry`'s own
658    /// `type_name` is the Rust type path (`alloc::string::String`), not the registered name.
659    ///
660    /// # Examples
661    ///
662    /// ```rust
663    /// use adam_lang::TypeRegistry;
664    /// let reg = TypeRegistry::new();
665    /// assert_eq!(reg.registered_name(std::any::TypeId::of::<String>()), Some("String"));
666    /// assert_eq!(reg.registered_name(std::any::TypeId::of::<Vec<u8>>()), None);
667    /// ```
668    #[must_use]
669    pub fn registered_name(&self, type_id: TypeId) -> Option<&str> {
670        self.by_type_id.get(&type_id).map(String::as_str)
671    }
672
673    /// Formats `shape` recursively using registered leaf names, or `?` for unknown leaves.
674    ///
675    /// - Complexity: O(n + b) time for n shape nodes and b result bytes; O(b + d)
676    ///   space including the result and recursion depth d.
677    ///
678    /// # Examples
679    ///
680    /// ```
681    /// use adam_lang::{TypeRegistry, type_registry::TypeShape};
682    /// let shape = TypeShape::Tuple(vec![TypeShape::Named(std::any::TypeId::of::<i32>())]);
683    /// assert_eq!(TypeRegistry::new().display_name(&shape), "(i32,)");
684    /// ```
685    #[must_use]
686    pub fn display_name(&self, shape: &TypeShape) -> String {
687        let mut output = String::new();
688        self.write_shape_name(&mut output, shape);
689        output
690    }
691
692    /// Appends a shape name without cloning shapes or allocating intermediate names.
693    ///
694    /// - Complexity: O(n + b) amortized time and O(b + d) space for n nodes,
695    ///   b appended bytes, and recursion depth d, including output growth.
696    fn write_shape_name(&self, output: &mut String, shape: &TypeShape) {
697        match shape {
698            TypeShape::Named(type_id) => {
699                output.push_str(self.registered_name(*type_id).unwrap_or("?"));
700            }
701            TypeShape::Tuple(elements) => self.write_tuple_name(output, elements),
702            TypeShape::Array(element) => {
703                output.push('[');
704                self.write_shape_name(output, element);
705                output.push(']');
706            }
707            TypeShape::Callable(parameters, result) => {
708                output.push_str("Fn(");
709                self.write_tuple_name(output, parameters);
710                output.push_str(", ");
711                self.write_shape_name(output, result);
712                output.push(')');
713            }
714        }
715    }
716
717    /// Appends a tuple name, including the required terminal comma for one element.
718    ///
719    /// - Complexity: O(n + b) amortized time and O(b + d) space over n reachable
720    ///   shape nodes, b appended bytes, and recursion depth d, including output growth.
721    fn write_tuple_name(&self, output: &mut String, elements: &[TypeShape]) {
722        output.push('(');
723        // Emit each borrowed element directly into the shared output.
724        for (index, element) in elements.iter().enumerate() {
725            if index != 0 {
726                output.push_str(", ");
727            }
728            self.write_shape_name(output, element);
729        }
730        if elements.len() == 1 {
731            output.push(',');
732        }
733        output.push(')');
734    }
735
736    /// Builds one `AssociatedType` entry describing `shape`: a leaf's own registered layout for
737    /// `TypeShape::Named`, or a nested tuple's own recursively-laid-out elements (via
738    /// `cel_runtime::ValueType::tuple`) for `TypeShape::Tuple`.
739    ///
740    /// - Precondition: every leaf `TypeId` reachable from `shape` is registered.
741    ///
742    /// - Postcondition: the returned entry's `offset` is always 0; the caller
743    ///   (`ValueType::tuple`) computes canonical tail-first sibling offsets.
744    /// - Complexity: O(n) descriptor nodes and space, including recursive traversal.
745    fn one_associated(&self, shape: &TypeShape) -> cel_runtime::AssociatedType {
746        let value_type = match shape {
747            TypeShape::Named(type_id) => {
748                let entry = self
749                    .entry_by_type_id(*type_id)
750                    .expect("one_associated: type registered");
751                (entry.value_type_fn)()
752            }
753            TypeShape::Tuple(elements) => cel_runtime::ValueType::tuple(
754                elements.iter().map(|e| self.one_associated(e)).collect(),
755            )
756            .expect("registered tuple layout"),
757            TypeShape::Array(element) => cel_runtime::ValueType::array(self.value_type(element)),
758            TypeShape::Callable(parameters, result) => cel_runtime::ValueType::callable(
759                parameters.iter().map(|p| self.value_type(p)).collect(),
760                self.value_type(result),
761            ),
762        };
763        cel_runtime::AssociatedType {
764            offset: 0,
765            value_type,
766        }
767    }
768
769    /// Returns the canonical descriptor of a resolved shape.
770    ///
771    /// - Precondition: Every named leaf is registered.
772    /// - Complexity: O(n) in shape nodes.
773    pub(crate) fn value_type(&self, shape: &TypeShape) -> cel_runtime::ValueType {
774        self.one_associated(shape).value_type
775    }
776
777    /// Checks every registered leaf and representable compound layout without evaluation.
778    ///
779    /// Callable signatures and empty arrays still require registered element leaves.
780    /// # Errors
781    /// Rejects unregistered leaves or unrepresentable tuple layouts.
782    /// - Complexity: O(n) descriptor nodes and space.
783    ///
784    /// # Examples
785    /// ```
786    /// use adam_lang::{TypeRegistry, type_registry::TypeShape};
787    /// let types = TypeRegistry::new();
788    /// assert!(types.validate_shape(&TypeShape::Array(Box::new(
789    ///     TypeShape::Named(std::any::TypeId::of::<i32>())
790    /// ))).is_ok());
791    /// ```
792    pub fn validate_shape(&self, shape: &TypeShape) -> anyhow::Result<()> {
793        self.checked_value_type(shape).map(|_| ())
794    }
795
796    /// Resolves a full descriptor while rejecting unknown leaves and invalid layouts.
797    ///
798    /// # Errors
799    /// Rejects unregistered leaves and unrepresentable tuple layouts.
800    /// - Complexity: O(n) descriptor nodes and space.
801    pub(crate) fn checked_value_type(
802        &self,
803        shape: &TypeShape,
804    ) -> anyhow::Result<cel_runtime::ValueType> {
805        use cel_runtime::{AssociatedType, ValueType};
806        Ok(match shape {
807            TypeShape::Named(id) => {
808                let entry = self
809                    .entry_by_type_id(*id)
810                    .ok_or_else(|| anyhow::anyhow!("unregistered shape leaf {id:?}"))?;
811                (entry.value_type_fn)()
812            }
813            TypeShape::Tuple(fields) => ValueType::tuple(
814                fields
815                    .iter()
816                    .map(|field| {
817                        Ok(AssociatedType {
818                            offset: 0,
819                            value_type: self.checked_value_type(field)?,
820                        })
821                    })
822                    .collect::<anyhow::Result<_>>()?,
823            )?,
824            TypeShape::Array(element) => ValueType::array(self.checked_value_type(element)?),
825            TypeShape::Callable(parameters, result) => ValueType::callable(
826                parameters
827                    .iter()
828                    .map(|p| self.checked_value_type(p))
829                    .collect::<anyhow::Result<_>>()?,
830                self.checked_value_type(result)?,
831            ),
832        })
833    }
834
835    /// Recovers structural identity without synthesizing aggregate Rust identities.
836    ///
837    /// # Errors
838    /// Names an unregistered leaf, including leaves nested in a signature.
839    /// - Complexity: O(n) in descriptor nodes.
840    pub(crate) fn shape_of(&self, ty: &cel_runtime::ValueType) -> Result<TypeShape, String> {
841        use cel_runtime::ValueKind;
842        Ok(match ty.kind() {
843            ValueKind::Leaf(id) => {
844                if self.entry_by_type_id(*id).is_none() {
845                    return Err(format!(
846                        "cannot infer a type: unregistered type `{}`",
847                        ty.type_name()
848                    ));
849                }
850                TypeShape::Named(*id)
851            }
852            ValueKind::Array(element) => TypeShape::Array(Box::new(self.shape_of(element)?)),
853            ValueKind::Tuple(fields) => TypeShape::Tuple(
854                fields
855                    .iter()
856                    .map(|field| self.shape_of(&field.value_type))
857                    .collect::<Result<_, _>>()?,
858            ),
859            ValueKind::Callable { parameters, result } => TypeShape::Callable(
860                parameters
861                    .iter()
862                    .map(|p| self.shape_of(p))
863                    .collect::<Result<_, _>>()?,
864                Box::new(self.shape_of(result)?),
865            ),
866        })
867    }
868
869    /// Checks default availability without constructing any values.
870    ///
871    /// Arrays need no element defaults; tuple fields are checked in source order.
872    /// # Errors
873    /// Names the first missing leaf default or callable requiring an initializer.
874    /// - Complexity: O(n) in constructed shape nodes.
875    pub(crate) fn validate_default(&self, shape: &TypeShape) -> anyhow::Result<()> {
876        match shape {
877            TypeShape::Named(id) => {
878                anyhow::ensure!(
879                    self.entry_by_type_id(*id)
880                        .expect("resolved leaf")
881                        .default_fn
882                        .is_some(),
883                    "type `{}` has no default; provide `= ...`",
884                    self.display_name(shape)
885                );
886            }
887            TypeShape::Tuple(fields) => {
888                for field in fields {
889                    self.validate_default(field)?;
890                }
891            }
892            TypeShape::Array(_) => {}
893            TypeShape::Callable(_, _) => {
894                anyhow::bail!("callable has no default; provide an initializer `= ...`")
895            }
896        }
897        Ok(())
898    }
899
900    /// Emits recursive defaults, leaving arrays empty without constructing their elements.
901    ///
902    /// Cell builders preflight default availability and cell capabilities for the entire
903    /// descriptor before calling this effectful emitter.
904    ///
905    /// # Errors
906    /// Names the precise missing default leaf; callables require an initializer.
907    /// - Complexity: O(n) in constructed shape nodes.
908    pub(crate) fn emit_default(
909        &self,
910        shape: &TypeShape,
911        body: &mut DynSegment,
912    ) -> anyhow::Result<()> {
913        let origin = body.current_stack_offset();
914        match shape {
915            TypeShape::Named(id) => {
916                let entry = self.entry_by_type_id(*id).expect("resolved leaf");
917                let default = entry.default_fn.ok_or_else(|| {
918                    anyhow::anyhow!(
919                        "type `{}` has no default; provide `= ...`",
920                        self.display_name(shape)
921                    )
922                })?;
923                body.just_external(self.value_type(shape), default())?;
924            }
925            TypeShape::Tuple(fields) => {
926                for field in fields {
927                    self.emit_default(field, body)?;
928                }
929                body.make_tuple(fields.len(), origin)?;
930            }
931            TypeShape::Array(element) => {
932                body.make_typed_array(0, origin, self.value_type(element))?
933            }
934            TypeShape::Callable(_, _) => {
935                anyhow::bail!("callable has no default; provide an initializer `= ...`")
936            }
937        }
938        Ok(())
939    }
940}
941
942/// A CEL-facing snapshot of one [`TypeRegistry`]'s registered leaf types.
943///
944/// The metadata remains valid independently of later registry mutation or destruction.
945///
946/// # Examples
947///
948/// See [`TypeRegistry::cel_type_resolver`] for a snapshot used after registry destruction.
949#[derive(Clone, Default)]
950pub struct RegistryTypeResolver {
951    by_name: HashMap<String, ResolvedLeafType>,
952}
953
954impl TypeResolver for RegistryTypeResolver {
955    /// Resolves registered plain or generic leaves, then CEL built-ins.
956    ///
957    /// - Complexity: O(n) in generic argument nodes and name bytes.
958    fn resolve_named_type(&self, name: &str, args: &[ResolvedType]) -> Option<ResolvedLeafType> {
959        let key = named_key(name, args);
960        if let Some(leaf) = self.by_name.get(key.as_ref()).cloned() {
961            return Some(leaf);
962        }
963        cel_parser::resolve_builtin_named_type(name, args)
964    }
965}
966
967impl TypeResolver for TypeRegistry {
968    /// Resolves a leaf without allocating a complete registry snapshot.
969    ///
970    /// - Complexity: O(n) in generic argument nodes and name bytes.
971    fn resolve_named_type(&self, name: &str, args: &[ResolvedType]) -> Option<ResolvedLeafType> {
972        let key = named_key(name, args);
973        self.get(key.as_ref())
974            .map(|entry| ResolvedLeafType::new(key.into_owned(), (entry.value_type_fn)()))
975            .or_else(|| cel_parser::resolve_builtin_named_type(name, args))
976    }
977}
978
979/// Borrows simple names and formats only explicitly parameterized leaf lookups.
980///
981/// - Complexity: O(n) in generic argument nodes and name bytes.
982fn named_key<'a>(name: &'a str, args: &[ResolvedType]) -> std::borrow::Cow<'a, str> {
983    if args.is_empty() {
984        std::borrow::Cow::Borrowed(name)
985    } else {
986        let mut key = String::from(name);
987        key.push('(');
988        for (index, argument) in args.iter().enumerate() {
989            if index != 0 {
990                key.push_str(", ");
991            }
992            key.push_str(&argument.display_name());
993        }
994        key.push(')');
995        std::borrow::Cow::Owned(key)
996    }
997}
998
999impl Default for TypeRegistry {
1000    /// Returns `TypeRegistry::new()`.
1001    fn default() -> Self {
1002        Self::new()
1003    }
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008    use super::*;
1009    use proc_macro2::Span;
1010    use std::any::TypeId;
1011
1012    /// Renders empty, singleton, array, callable, and unknown shapes with strict tuple syntax.
1013    #[test]
1014    fn display_name_covers_recursive_shape_variants() {
1015        let integer = TypeShape::Named(TypeId::of::<i32>());
1016        let cases = [
1017            (TypeShape::Tuple(vec![]), "()"),
1018            (TypeShape::Tuple(vec![integer.clone()]), "(i32,)"),
1019            (
1020                TypeShape::Array(Box::new(TypeShape::Tuple(vec![integer.clone()]))),
1021                "[(i32,)]",
1022            ),
1023            (
1024                TypeShape::Callable(vec![integer], Box::new(TypeShape::Tuple(vec![]))),
1025                "Fn((i32,), ())",
1026            ),
1027            (TypeShape::Named(TypeId::of::<Vec<i32>>()), "?"),
1028        ];
1029        let registry = TypeRegistry::new();
1030        for (shape, expected) in cases {
1031            assert_eq!(registry.display_name(&shape), expected);
1032        }
1033    }
1034
1035    /// Constructs a zero-width expression range at the supplied span.
1036    fn point(span: Span) -> crate::ast::ExprSpan {
1037        crate::ast::ExprSpan {
1038            start: span,
1039            end: span,
1040        }
1041    }
1042
1043    /// Registers the built-in integer under its concrete runtime type.
1044    #[test]
1045    fn new_registers_builtin_i32() {
1046        let reg = TypeRegistry::new();
1047        let entry = reg.get("i32").expect("i32 registered");
1048        assert_eq!(entry.type_id, TypeId::of::<i32>());
1049    }
1050
1051    /// Installs complete factories while foreign concrete types are still known.
1052    #[test]
1053    fn complete_factory_registered_foreign_leaf_retains_capabilities() {
1054        use cel_runtime::{owned_value::OwnedValue, value_type::Leaf};
1055        let mut registry = TypeRegistry::new();
1056        registry.register::<std::path::PathBuf>("Path");
1057        registry.register_no_default::<std::ops::RangeInclusive<i32>>("Bounds");
1058        let entry = registry.get("Path").unwrap();
1059        let descriptor = (entry.value_type_fn)();
1060        let owner = OwnedValue::new::<Leaf<std::path::PathBuf>>("example".into())
1061            .with_value_type(descriptor)
1062            .unwrap();
1063        assert!(owner.try_eq(&owner.try_clone().unwrap()).unwrap());
1064        assert_eq!(owner.try_debug().unwrap(), "\"example\"");
1065        for name in ["Path", "Bounds"] {
1066            let resolved = registry
1067                .cel_type_resolver()
1068                .resolve_named_type(name, &[])
1069                .unwrap();
1070            assert_eq!(resolved.type_name(), name);
1071            assert!(
1072                resolved
1073                    .complete_value_type()
1074                    .unwrap()
1075                    .same_shape(&(registry.get(name).unwrap().value_type_fn)())
1076            );
1077        }
1078    }
1079
1080    /// Registers floating-point values with default construction.
1081    #[test]
1082    fn new_registers_builtin_f64_with_default() {
1083        let reg = TypeRegistry::new();
1084        let entry = reg.get("f64").expect("f64 registered");
1085        assert_eq!(entry.type_id, TypeId::of::<f64>());
1086        assert!(entry.default_fn.is_some(), "f64 must have a default");
1087    }
1088
1089    /// Registers the built-in owned string type.
1090    #[test]
1091    fn new_registers_builtin_string() {
1092        let reg = TypeRegistry::new();
1093        let entry = reg.get("String").expect("String registered");
1094        assert_eq!(entry.type_id, TypeId::of::<String>());
1095    }
1096
1097    /// Registers a custom name with its concrete type and default factory.
1098    #[test]
1099    fn register_custom_type_with_default() {
1100        let mut reg = TypeRegistry::new();
1101        reg.register::<u64>("my_u64");
1102        let entry = reg.get("my_u64").expect("custom type registered");
1103        assert_eq!(entry.type_id, TypeId::of::<u64>());
1104        assert!(entry.default_fn.is_some());
1105    }
1106
1107    /// Omits default construction when registering a non-defaultable leaf.
1108    #[test]
1109    fn register_no_default_has_no_default_fn() {
1110        /// Supplies a test leaf without a `Default` implementation.
1111        #[derive(PartialEq, Clone, Debug)]
1112        struct NoDefault(i32);
1113
1114        let mut reg = TypeRegistry::new();
1115        reg.register_no_default::<NoDefault>("no_default");
1116        let entry = reg.get("no_default").expect("registered");
1117        assert!(entry.default_fn.is_none());
1118    }
1119
1120    /// Preserves a registered custom leaf's requested CEL name and type.
1121    #[test]
1122    fn cel_type_resolver_uses_registered_leaf_names() {
1123        /// Supplies a non-built-in leaf for name resolution.
1124        #[derive(PartialEq, Clone, Debug)]
1125        struct Custom(i32);
1126
1127        let mut reg = TypeRegistry::new();
1128        reg.register_no_default::<Custom>("Custom");
1129
1130        let resolver = reg.cel_type_resolver();
1131        let leaf = resolver
1132            .resolve_named_type("Custom", &[])
1133            .expect("custom leaf registered");
1134
1135        assert_eq!(leaf.type_id(), TypeId::of::<Custom>());
1136        assert_eq!(leaf.type_name(), "Custom");
1137    }
1138
1139    /// Forwards a registered argument through dynamic segment invocation.
1140    #[test]
1141    fn push_arg_fn_drives_call_dyn() {
1142        use cel_runtime::DynSegment;
1143        use std::any::Any;
1144
1145        let reg = TypeRegistry::new();
1146        let entry = reg.get("i32").unwrap();
1147        let mut seg = DynSegment::new::<()>();
1148        (entry.push_arg_fn)(&mut seg, 0);
1149        let x: i32 = 7;
1150        let result: i32 = seg.call_dyn(&[&x as &dyn Any]).unwrap();
1151        assert_eq!(result, 7);
1152    }
1153
1154    /// Adds exactly one cell storing the supplied concrete value.
1155    #[test]
1156    fn add_cell_fn_creates_cell() {
1157        use adam_rs::Sheet;
1158        use std::any::Any;
1159
1160        let reg = TypeRegistry::new();
1161        let entry = reg.get("f64").unwrap();
1162        let mut sheet = Sheet::new();
1163        let val: Box<dyn Any> = Box::new(42.14_f64);
1164        let cell_id = (entry.add_cell_fn)(&mut sheet, val);
1165        assert_eq!(sheet.cells().count(), 1);
1166        assert_eq!(*sheet.read::<f64>(cell_id).unwrap(), 42.14);
1167    }
1168
1169    /// Boxes a dynamic call's concrete result without changing its value.
1170    #[test]
1171    fn call_dyn_fn_returns_boxed_result() {
1172        use cel_runtime::DynSegment;
1173        use std::any::Any;
1174
1175        let reg = TypeRegistry::new();
1176        let entry = reg.get("i32").unwrap();
1177        let mut seg = DynSegment::new::<()>();
1178        (entry.push_arg_fn)(&mut seg, 0);
1179        let x: i32 = 99;
1180        let boxed = (entry.call_dyn_fn)(&mut seg, &[&x as &dyn Any]).unwrap();
1181        let result = boxed.downcast::<i32>().expect("i32");
1182        assert_eq!(*result, 99);
1183    }
1184
1185    /// Resolves a registered leaf through its concrete `TypeId`.
1186    #[test]
1187    fn entry_by_type_id_roundtrip() {
1188        let reg = TypeRegistry::new();
1189        let entry = reg
1190            .entry_by_type_id(std::any::TypeId::of::<f64>())
1191            .expect("f64 registered");
1192        assert_eq!(entry.type_id, std::any::TypeId::of::<f64>());
1193    }
1194
1195    /// Returns no entry for an unknown registered name.
1196    #[test]
1197    fn get_nonexistent_returns_none() {
1198        let reg = TypeRegistry::new();
1199        assert!(reg.get("nonexistent").is_none());
1200    }
1201
1202    /// Returns no entry for an unregistered concrete type.
1203    #[test]
1204    fn entry_by_type_id_nonexistent_returns_none() {
1205        let reg = TypeRegistry::new();
1206        // Vec<u8> is not a registered built-in type.
1207        assert!(
1208            reg.entry_by_type_id(std::any::TypeId::of::<Vec<u8>>())
1209                .is_none()
1210        );
1211    }
1212
1213    /// Removes reverse lookup when the replaced leaf has no surviving name.
1214    #[test]
1215    fn register_overwrite_removes_stale_type_id() {
1216        /// Identifies the defaultable leaf whose last name is replaced.
1217        #[derive(PartialEq, Clone, Default, Debug)]
1218        struct TypeA;
1219        /// Identifies the replacement defaultable leaf.
1220        #[derive(PartialEq, Clone, Default, Debug)]
1221        struct TypeB;
1222
1223        let mut reg = TypeRegistry::new();
1224        reg.register::<TypeA>("alias");
1225        reg.register::<TypeB>("alias");
1226
1227        // After overwriting, TypeA's TypeId must no longer be found.
1228        assert!(
1229            reg.entry_by_type_id(TypeId::of::<TypeA>()).is_none(),
1230            "stale TypeA mapping should have been removed"
1231        );
1232        // TypeB must be reachable by both name and TypeId.
1233        assert!(reg.entry_by_type_id(TypeId::of::<TypeB>()).is_some());
1234        assert_eq!(reg.get("alias").unwrap().type_id, TypeId::of::<TypeB>());
1235    }
1236
1237    /// Removes obsolete reverse lookup when replacing a non-defaultable leaf.
1238    #[test]
1239    fn register_no_default_overwrite_removes_stale_type_id() {
1240        /// Identifies the leaf whose last name is replaced.
1241        #[derive(PartialEq, Clone, Debug)]
1242        struct TypeA;
1243        /// Identifies the replacement leaf.
1244        #[derive(PartialEq, Clone, Debug)]
1245        struct TypeB;
1246
1247        let mut reg = TypeRegistry::new();
1248        reg.register_no_default::<TypeA>("alias");
1249        reg.register_no_default::<TypeB>("alias");
1250
1251        assert!(
1252            reg.entry_by_type_id(TypeId::of::<TypeA>()).is_none(),
1253            "stale TypeA mapping should have been removed"
1254        );
1255        assert!(reg.entry_by_type_id(TypeId::of::<TypeB>()).is_some());
1256    }
1257
1258    /// Registered leaves retain independent canonical clone, equality, and debug plans.
1259    #[test]
1260    fn registered_leaf_capabilities_operate_on_canonical_owners() {
1261        let reg = TypeRegistry::new();
1262        let entry = reg.get("i32").unwrap();
1263        let value = cel_runtime::OwnedValue::new::<cel_runtime::Leaf<i32>>(7)
1264            .with_value_type((entry.value_type_fn)())
1265            .unwrap();
1266        let cloned = value.try_clone().unwrap();
1267        assert!(value.try_eq(&cloned).unwrap());
1268        assert_eq!(cloned.try_debug().unwrap(), "7");
1269    }
1270
1271    /// Resolves a named annotation to its registered concrete shape.
1272    #[test]
1273    fn resolve_named_type_expr_returns_the_matching_type_shape() {
1274        let reg = TypeRegistry::new();
1275        let expr = cel_parser::TypeExpr::Named {
1276            name: "i32".to_string(),
1277            args: Vec::new(),
1278            span: point(Span::call_site()),
1279        };
1280        let shape = reg.resolve(&expr).unwrap();
1281        assert_eq!(shape, TypeShape::Named(TypeId::of::<i32>()));
1282    }
1283
1284    /// Reports an unknown named annotation.
1285    #[test]
1286    fn resolve_unknown_named_type_expr_is_an_error() {
1287        let reg = TypeRegistry::new();
1288        let expr = cel_parser::TypeExpr::Named {
1289            name: "bogus".to_string(),
1290            args: Vec::new(),
1291            span: point(Span::call_site()),
1292        };
1293        assert!(reg.resolve(&expr).is_err());
1294    }
1295
1296    /// Resolves recursively nested tuple annotations.
1297    #[test]
1298    fn resolve_tuple_type_expr_returns_a_nested_type_shape() {
1299        let reg = TypeRegistry::new();
1300        let span = point(Span::call_site());
1301        let expr = cel_parser::TypeExpr::Tuple {
1302            elements: vec![
1303                cel_parser::TypeExpr::Named {
1304                    name: "i32".to_string(),
1305                    args: Vec::new(),
1306                    span,
1307                },
1308                cel_parser::TypeExpr::Tuple {
1309                    elements: vec![
1310                        cel_parser::TypeExpr::Named {
1311                            name: "f64".to_string(),
1312                            args: Vec::new(),
1313                            span,
1314                        },
1315                        cel_parser::TypeExpr::Named {
1316                            name: "String".to_string(),
1317                            args: Vec::new(),
1318                            span,
1319                        },
1320                    ],
1321                    span,
1322                },
1323            ],
1324            span,
1325        };
1326        let shape = reg.resolve(&expr).unwrap();
1327        assert_eq!(
1328            shape,
1329            TypeShape::Tuple(vec![
1330                TypeShape::Named(TypeId::of::<i32>()),
1331                TypeShape::Tuple(vec![
1332                    TypeShape::Named(TypeId::of::<f64>()),
1333                    TypeShape::Named(TypeId::of::<String>()),
1334                ]),
1335            ])
1336        );
1337    }
1338
1339    /// Resolves structural arrays without registering their carrier.
1340    #[test]
1341    fn resolve_accepts_structural_array_cell_type() {
1342        let registry = TypeRegistry::new();
1343        let span = point(Span::call_site());
1344        let expr = cel_parser::TypeExpr::Array {
1345            element: Box::new(cel_parser::TypeExpr::Named {
1346                name: "i32".to_string(),
1347                args: Vec::new(),
1348                span,
1349            }),
1350            span,
1351        };
1352        assert_eq!(
1353            registry.resolve(&expr).unwrap(),
1354            TypeShape::Array(Box::new(TypeShape::Named(TypeId::of::<i32>())))
1355        );
1356    }
1357
1358    /// Resolves a registered generic leaf annotation.
1359    #[test]
1360    fn resolve_accepts_registered_generic_leaf() {
1361        let mut registry = TypeRegistry::new();
1362        registry.register_no_default::<std::ops::RangeInclusive<f64>>("RangeInclusive(f64)");
1363        let span = point(Span::call_site());
1364        let expr = cel_parser::TypeExpr::Named {
1365            name: "RangeInclusive".to_string(),
1366            args: vec![cel_parser::TypeExpr::Named {
1367                name: "f64".to_string(),
1368                args: Vec::new(),
1369                span,
1370            }],
1371            span,
1372        };
1373        assert_eq!(
1374            registry.resolve(&expr).unwrap(),
1375            TypeShape::Named(TypeId::of::<std::ops::RangeInclusive<f64>>())
1376        );
1377    }
1378
1379    /// Both resolvers retain requested names and concrete identities across alias orders.
1380    #[test]
1381    fn registered_aliases_preserve_requested_leaf_names() {
1382        for names in [["Integer", "i32"], ["i32", "Integer"]] {
1383            let mut registry = TypeRegistry::new();
1384            for name in names {
1385                registry.register::<i32>(name);
1386            }
1387            let snapshot = registry.cel_type_resolver();
1388            for name in names {
1389                let direct = registry.resolve_named_type(name, &[]).unwrap();
1390                let copied = snapshot.resolve_named_type(name, &[]).unwrap();
1391                assert_eq!(direct.type_name(), name);
1392                assert_eq!(copied.type_name(), name);
1393                assert!(
1394                    direct
1395                        .complete_value_type()
1396                        .unwrap()
1397                        .same_shape(&cel_runtime::ValueType::leaf::<i32>())
1398                );
1399                assert!(
1400                    direct
1401                        .complete_value_type()
1402                        .unwrap()
1403                        .same_shape(copied.complete_value_type().unwrap())
1404                );
1405            }
1406        }
1407    }
1408
1409    /// Aliases never rewrite registered keys inside recursive generic arguments.
1410    #[test]
1411    fn registered_aliases_preserve_recursive_generic_keys() {
1412        use std::ops::RangeInclusive;
1413
1414        for alias_first in [false, true] {
1415            let mut registry = TypeRegistry::new();
1416            if alias_first {
1417                registry.register::<i32>("Integer");
1418            }
1419            let names = [
1420                "Bounds(i32)",
1421                "Bounds(Integer)",
1422                "Bounds(Bounds(i32))",
1423                "Bounds([i32])",
1424                "Bounds((i32, [Integer]))",
1425                "Bounds((i32,))",
1426                "Bounds(Fn((), Integer))",
1427                "Bounds(Fn((Integer,), [i32]))",
1428                "Bounds(Fn((Integer, [i32]), ()))",
1429            ];
1430            for name in names {
1431                registry.register_no_default::<RangeInclusive<i32>>(name);
1432            }
1433            if !alias_first {
1434                registry.register::<i32>("Integer");
1435            }
1436            let snapshot = registry.cel_type_resolver();
1437            for name in names {
1438                for source in [name.to_owned(), format!("[({name}, [Integer])]")] {
1439                    let expr = cel_parser::CELParser::new(cel_parser::OpLookup::new())
1440                        .parse_type_expr_str(&source)
1441                        .unwrap();
1442                    let copied = expr.resolve(&snapshot).unwrap().into_value_type().unwrap();
1443                    let direct = expr.resolve(&registry).unwrap();
1444                    assert_eq!(direct.display_name(), source);
1445                    let direct = direct.into_value_type().unwrap();
1446                    assert!(direct.same_shape(&copied), "{source}");
1447                    assert_eq!(
1448                        registry.resolve(&expr).unwrap(),
1449                        registry.shape_of(&copied).unwrap(),
1450                        "{source}"
1451                    );
1452                }
1453            }
1454            let missing = cel_parser::CELParser::new(cel_parser::OpLookup::new())
1455                .parse_type_expr_str("Bounds([Integer])")
1456                .unwrap();
1457            let direct = missing.resolve(&registry).unwrap_err();
1458            let copied = missing.resolve(&snapshot).unwrap_err();
1459            assert_eq!(direct.message(), copied.message());
1460            assert!(direct.message().contains("unknown type `Bounds`"));
1461        }
1462    }
1463
1464    /// Resolves built-in generic types through the CEL fallback.
1465    #[test]
1466    fn cel_type_resolver_falls_back_to_builtin_generics() {
1467        let registry = TypeRegistry::new();
1468        let resolver = registry.cel_type_resolver();
1469        let f64_leaf = resolver.resolve_named_type("f64", &[]).expect("f64");
1470        let resolved = resolver
1471            .resolve_named_type("RangeInclusive", &[ResolvedType::Scalar(f64_leaf)])
1472            .expect("RangeInclusive(f64) should resolve via the builtin fallback");
1473        assert_eq!(resolved.type_name(), "RangeInclusive(f64)");
1474    }
1475
1476    /// Renders a recursive tuple shape with registered leaf names.
1477    #[test]
1478    fn display_name_formats_a_nested_tuple_shape() {
1479        let reg = TypeRegistry::new();
1480        let shape = TypeShape::Tuple(vec![
1481            TypeShape::Named(TypeId::of::<i32>()),
1482            TypeShape::Tuple(vec![
1483                TypeShape::Named(TypeId::of::<f64>()),
1484                TypeShape::Named(TypeId::of::<String>()),
1485            ]),
1486        ]);
1487        assert_eq!(reg.display_name(&shape), "(i32, (f64, String))");
1488    }
1489
1490    /// Describes a flat tuple using its canonical runtime layout.
1491    #[test]
1492    fn canonical_descriptor_describes_a_flat_tuple() {
1493        let reg = TypeRegistry::new();
1494        let shape = TypeShape::Tuple(vec![
1495            TypeShape::Named(TypeId::of::<i32>()),
1496            TypeShape::Named(TypeId::of::<f64>()),
1497        ]);
1498        let descriptor = reg.value_type(&shape);
1499        let prototype = descriptor.tuple_elements().unwrap();
1500        assert_eq!(prototype.len(), 2);
1501        assert_eq!(prototype[0].value_type.type_id(), TypeId::of::<i32>());
1502        assert_eq!(prototype[1].value_type.type_id(), TypeId::of::<f64>());
1503        assert_eq!(prototype[0].offset, 8);
1504        assert_eq!(prototype[1].offset, 0);
1505    }
1506
1507    /// Retains recursive element descriptors for a nested tuple.
1508    #[test]
1509    fn canonical_descriptor_describes_a_nested_tuple() {
1510        let reg = TypeRegistry::new();
1511        let shape = TypeShape::Tuple(vec![
1512            TypeShape::Named(TypeId::of::<i32>()),
1513            TypeShape::Tuple(vec![
1514                TypeShape::Named(TypeId::of::<i32>()),
1515                TypeShape::Named(TypeId::of::<i32>()),
1516            ]),
1517        ]);
1518        let descriptor = reg.value_type(&shape);
1519        let prototype = descriptor.tuple_elements().unwrap();
1520        assert_eq!(prototype.len(), 2);
1521        assert_eq!(
1522            prototype[1].value_type.type_id(),
1523            TypeId::of::<cel_runtime::CNil<()>>()
1524        );
1525        assert_eq!(
1526            prototype[1]
1527                .value_type
1528                .tuple_elements()
1529                .expect("nested tuple")
1530                .len(),
1531            2
1532        );
1533    }
1534
1535    /// Compound defaults compose arrays and tuples, and reject leaves lacking defaults.
1536    #[test]
1537    fn canonical_defaults_compose_and_report_missing_leaf_defaults() {
1538        use cel_runtime::{ArrayOf, Cons, IntoRustTuple, Leaf, Nil};
1539        let parsed = crate::AdamParser::new(TypeRegistry::new(), cel_parser::OpLookup::new())
1540            .parse_str("sheet s { cell x: (i32, ([f64],)); }")
1541            .unwrap();
1542        /// Represents the recursive tuple and array default requested by the fixture.
1543        type Shape = Cons<Leaf<i32>, Cons<Cons<ArrayOf<Leaf<f64>>, Nil>, Nil>>;
1544        let (number, nested) = parsed
1545            .read_compound::<Shape>(parsed.cell_names["x"].0)
1546            .unwrap()
1547            .into_rust_tuple();
1548        assert_eq!(number, 0);
1549        assert!(nested.into_rust_tuple().0.is_empty());
1550        /// Supplies a leaf whose missing default must be diagnosed.
1551        #[derive(Clone, PartialEq, Debug)]
1552        struct NoDefault;
1553        let mut registry = TypeRegistry::new();
1554        registry.register_no_default::<NoDefault>("NoDefault");
1555        let error = crate::AdamParser::new(registry, cel_parser::OpLookup::new())
1556            .parse_str("sheet s { cell x: (NoDefault,); }")
1557            .err()
1558            .unwrap();
1559        assert!(error.to_string().contains("NoDefault"));
1560    }
1561
1562    /// Compares equal concrete integers as equal through the erased adapter.
1563    #[test]
1564    fn eq_dyn_fn_compares_equal_i32_values_as_equal() {
1565        let reg = TypeRegistry::new();
1566        let entry = reg.get("i32").unwrap();
1567        let a: i32 = 7;
1568        let b: i32 = 7;
1569        assert!((entry.eq_dyn_fn)(&a, &b));
1570    }
1571
1572    /// Compares unequal concrete integers as unequal through the erased adapter.
1573    #[test]
1574    fn eq_dyn_fn_compares_unequal_i32_values_as_unequal() {
1575        let reg = TypeRegistry::new();
1576        let entry = reg.get("i32").unwrap();
1577        let a: i32 = 7;
1578        let b: i32 = 8;
1579        assert!(!(entry.eq_dyn_fn)(&a, &b));
1580    }
1581
1582    /// Supplies value equality for non-defaultable registered leaves.
1583    #[test]
1584    fn register_no_default_also_populates_eq_dyn_fn() {
1585        /// Supplies a non-defaultable leaf with value equality.
1586        #[derive(PartialEq, Clone, Debug)]
1587        struct NoDefault(i32);
1588
1589        let mut reg = TypeRegistry::new();
1590        reg.register_no_default::<NoDefault>("NoDefault");
1591        let entry = reg.get("NoDefault").unwrap();
1592        let a = NoDefault(1);
1593        let b = NoDefault(1);
1594        let c = NoDefault(2);
1595        assert!((entry.eq_dyn_fn)(&a, &b));
1596        assert!(!(entry.eq_dyn_fn)(&a, &c));
1597    }
1598
1599    /// Associates a registered numeric range with its element type.
1600    #[test]
1601    fn range_entry_recognizes_a_registered_numeric_range_inclusive_type() {
1602        let reg = TypeRegistry::new();
1603        let entry = reg
1604            .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1605            .expect("i32 range recognized");
1606        assert_eq!(entry.element_type_id, TypeId::of::<i32>());
1607    }
1608
1609    /// Returns no range adapter for an ordinary scalar type.
1610    #[test]
1611    fn range_entry_returns_none_for_a_non_range_type() {
1612        let reg = TypeRegistry::new();
1613        assert!(reg.range_entry(TypeId::of::<i32>()).is_none());
1614    }
1615
1616    /// Clamps an erased candidate to evaluated inclusive bounds.
1617    #[test]
1618    fn range_entry_clamp_fn_clamps_a_value_into_the_evaluated_bounds() {
1619        let reg = TypeRegistry::new();
1620        let entry = reg
1621            .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1622            .unwrap();
1623        let mut segment = DynSegment::new::<()>();
1624        segment.push_arg::<i32>(1);
1625        segment.push_arg::<i32>(2);
1626        segment.op2(|a: i32, b: i32| a..=b).unwrap();
1627        let value = 500i32;
1628        let lo = 0i32;
1629        let hi = 100i32;
1630        let evaluated = segment.call_dyn_value(&[&value, &lo, &hi]).unwrap();
1631        let result = (entry.clamp_fn)(evaluated, &value).unwrap();
1632        assert_eq!(*result.downcast_ref::<i32>().unwrap(), 100);
1633    }
1634
1635    /// Returns both evaluated inclusive bounds with their concrete values.
1636    #[test]
1637    fn range_entry_bounds_fn_returns_the_evaluated_bounds() {
1638        let reg = TypeRegistry::new();
1639        let entry = reg
1640            .range_entry(TypeId::of::<std::ops::RangeInclusive<i32>>())
1641            .unwrap();
1642        let mut segment = DynSegment::new::<()>();
1643        segment.push_arg::<i32>(1);
1644        segment.push_arg::<i32>(2);
1645        segment.op2(|a: i32, b: i32| a..=b).unwrap();
1646        let placeholder = 0i32;
1647        let lo = 0i32;
1648        let hi = 100i32;
1649        let evaluated = segment.call_dyn_value(&[&placeholder, &lo, &hi]).unwrap();
1650        let (lo_out, hi_out) = (entry.bounds_fn)(evaluated).unwrap();
1651        assert_eq!(*lo_out.downcast_ref::<i32>().unwrap(), 0);
1652        assert_eq!(*hi_out.downcast_ref::<i32>().unwrap(), 100);
1653    }
1654}