Skip to main content

runmat_runtime/builtin/
registry.rs

1use super::RuntimeBuiltinBinding;
2use std::collections::{BTreeMap, BTreeSet};
3
4#[cfg(not(target_arch = "wasm32"))]
5inventory::collect!(RuntimeBuiltinBinding);
6
7#[cfg(not(target_arch = "wasm32"))]
8pub fn runtime_builtin_bindings() -> Vec<RuntimeBuiltinBinding> {
9    inventory::iter::<RuntimeBuiltinBinding>()
10        .copied()
11        .collect()
12}
13
14#[cfg(target_arch = "wasm32")]
15pub mod wasm_registry {
16    use super::RuntimeBuiltinBinding;
17    use std::cell::RefCell;
18
19    thread_local! {
20        static BINDINGS: RefCell<Vec<&'static RuntimeBuiltinBinding>> = const { RefCell::new(Vec::new()) };
21    }
22
23    pub fn submit(binding: RuntimeBuiltinBinding) {
24        BINDINGS.with_borrow_mut(|bindings| bindings.push(Box::leak(Box::new(binding))));
25    }
26
27    pub fn bindings() -> Vec<&'static RuntimeBuiltinBinding> {
28        BINDINGS.with_borrow(Clone::clone)
29    }
30}
31
32#[cfg(target_arch = "wasm32")]
33pub fn runtime_builtin_bindings() -> Vec<RuntimeBuiltinBinding> {
34    wasm_registry::bindings().into_iter().copied().collect()
35}
36
37pub fn runtime_builtin_bindings_by_name(name: &str) -> Vec<RuntimeBuiltinBinding> {
38    runtime_builtin_bindings()
39        .into_iter()
40        .filter(|binding| binding.identity.builtin.name.eq_ignore_ascii_case(name))
41        .collect()
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct RuntimeBuiltinRegistryError {
46    pub identity: String,
47    pub message: String,
48}
49
50pub fn validate_runtime_builtin_registry() -> Vec<RuntimeBuiltinRegistryError> {
51    let catalog = runmat_builtins::builtin_catalog_entries();
52    let actual = runtime_builtin_bindings();
53    let mut errors = Vec::new();
54    let mut actual_counts = BTreeMap::new();
55    for binding in &actual {
56        *actual_counts.entry(binding.identity).or_insert(0usize) += 1;
57    }
58    for (identity, count) in &actual_counts {
59        if *count > 1 {
60            errors.push(registry_error(
61                identity,
62                "runtime binding identity is registered more than once",
63            ));
64        }
65    }
66
67    let mut declared = BTreeSet::new();
68    for entry in catalog {
69        if runmat_builtins::builtin_function_by_name(entry.identity.name).is_some() {
70            errors.push(RuntimeBuiltinRegistryError {
71                identity: entry.identity.name.to_string(),
72                message: "catalog identity also has a legacy BuiltinFunction authority".into(),
73            });
74        }
75        for binding in entry.bindings {
76            declared.insert(binding.identity);
77            let count = actual_counts.get(&binding.identity).copied().unwrap_or(0);
78            if matches!(
79                binding.availability,
80                runmat_builtins::BuiltinBindingAvailability::Required
81            ) && count != 1
82            {
83                errors.push(registry_error(
84                    &binding.identity,
85                    "required runtime binding is not registered exactly once",
86                ));
87            }
88        }
89    }
90    for binding in actual {
91        if !declared.contains(&binding.identity) {
92            errors.push(registry_error(
93                &binding.identity,
94                "runtime binding has no canonical catalog declaration",
95            ));
96        }
97    }
98    errors
99}
100
101fn registry_error(
102    identity: &runmat_builtins::BuiltinBindingIdentity,
103    message: impl Into<String>,
104) -> RuntimeBuiltinRegistryError {
105    RuntimeBuiltinRegistryError {
106        identity: format!("{}#{}", identity.builtin.name, identity.variant),
107        message: message.into(),
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn canonical_catalog_and_runtime_bindings_join_one_to_one() {
117        let errors = validate_runtime_builtin_registry();
118        assert!(errors.is_empty(), "{errors:#?}");
119        assert_eq!(runtime_builtin_bindings_by_name("full").len(), 1);
120        assert!(runmat_builtins::builtin_function_by_name("full").is_none());
121    }
122}