Skip to main content

harn_builtin_registry/
lib.rs

1//! Process-global registry of builtin contracts.
2//!
3//! `harn-vm` owns the implementations and emits one `&'static BuiltinDef<H>`
4//! per `#[harn_builtin]`-annotated function via the `harn-builtin-macros`
5//! crate. At startup the driver installs one immutable manifest containing
6//! the signature, source exposure, and effects for every name.
7//!
8//! This decouples `harn-parser` (which needs to see signatures to typecheck)
9//! from `harn-vm` (which owns the impls) without a dependency cycle —
10//! `harn-parser` depends only on this crate plus `harn-builtin-meta`, never
11//! on the vm.
12
13use std::collections::BTreeMap;
14use std::sync::{OnceLock, RwLock};
15
16use harn_builtin_meta::{BuiltinContract, BuiltinSignature};
17
18/// One name-keyed projection of a macro-emitted builtin definition. Aliases
19/// receive their own entry so signature and contract can never drift.
20#[derive(Debug, Clone, Copy)]
21pub struct BuiltinManifestEntry {
22    pub name: &'static str,
23    pub signature: &'static BuiltinSignature,
24    pub contract: BuiltinContract,
25}
26
27/// A complete description of one builtin: its signature, its aliases, the
28/// runtime handler (typed by the consumer via `H`), and optional metadata.
29///
30/// `H` is parametric so this crate stays free of any handler-type
31/// dependency. `harn-vm` instantiates it as
32/// `BuiltinDef<VmBuiltinHandler>`; parser-only consumers ignore the handler
33/// and read just the [`Self::sig`] field.
34#[derive(Debug, Clone, Copy)]
35pub struct BuiltinDef<H: 'static> {
36    /// Static signature consumed by the parser/typechecker.
37    pub sig: BuiltinSignature,
38    /// Typed source exposure and effect contract. This is the semantic owner
39    /// consumed by every parser/runtime/policy projection.
40    pub contract: BuiltinContract,
41    /// Additional names that share this impl + signature. Each alias gets
42    /// its own [`BuiltinSignature`] entry at install time (with the same
43    /// param/return types) so the typechecker accepts both.
44    pub aliases: &'static [&'static str],
45    /// Runtime handler (sync fn, async fn, or `None` for parser-only
46    /// builtins). Type is opaque to this crate.
47    pub handler: H,
48    /// Free-form category label used for metadata/observability.
49    pub category: Option<&'static str>,
50    /// Human-readable doc, typically the leading `///` block from the impl
51    /// function. Surfaced to LSP hover and `harn explain`.
52    pub doc: Option<&'static str>,
53    /// Free-form Harn-style signature text (e.g. `"foo(a: dict) -> dict"`).
54    /// Populated by `#[harn_builtin]` from the `sig = "..."` literal so the
55    /// runtime metadata layer can surface the original source spelling
56    /// without re-rendering [`Self::sig`]. The DSL builder shape used to
57    /// store this via `.signature(...)`; the macro shape replaces it.
58    pub signature_text: Option<&'static str>,
59    /// Set to `true` for builtins that exist in the parser registry but
60    /// have no runtime entry (`len`, `split`, … — see
61    /// `PARSER_ONLY_EXCEPTIONS` in the alignment test). The registry
62    /// skips runtime registration for these.
63    pub parser_only: bool,
64    /// Set to `true` for compiler-synthesized runtime helpers (sigil
65    /// prefix `__`, opcode keywords, enum constructors) that exist as VM
66    /// builtins but should NOT show up in the parser signature table.
67    /// The registry skips signature publishing for these.
68    pub runtime_only: bool,
69}
70
71impl<H: 'static> BuiltinDef<H> {
72    /// Compact constructor for the common case: one signature, no aliases,
73    /// no metadata flags.
74    pub const fn new(sig: BuiltinSignature, handler: H) -> Self {
75        Self {
76            sig,
77            contract: BuiltinContract::UNDECLARED,
78            aliases: &[],
79            handler,
80            category: None,
81            doc: None,
82            signature_text: None,
83            parser_only: false,
84            runtime_only: false,
85        }
86    }
87}
88
89static INSTALLED: OnceLock<RwLock<BTreeMap<&'static str, &'static BuiltinManifestEntry>>> =
90    OnceLock::new();
91
92fn installed() -> &'static RwLock<BTreeMap<&'static str, &'static BuiltinManifestEntry>> {
93    INSTALLED.get_or_init(|| RwLock::new(BTreeMap::new()))
94}
95
96/// Install the process-global builtin manifest.
97///
98/// # Panics
99/// Multiple owners may contribute disjoint manifest fragments (for example
100/// the core runtime and an optional host capability crate). Reinstalling the
101/// same entries is idempotent; redefining an existing name panics.
102pub fn install_builtin_manifest(entries: &'static [&'static BuiltinManifestEntry]) {
103    let mut manifest = installed().write().expect("builtin manifest lock poisoned");
104    for entry in entries {
105        if let Some(previous) = manifest.insert(entry.name, entry) {
106            assert!(
107                std::ptr::eq(previous, *entry),
108                "builtin manifest name `{}` registered by multiple contracts",
109                entry.name
110            );
111        }
112    }
113}
114
115/// Test-only one-shot manifest install.
116#[doc(hidden)]
117pub fn _test_only_reinstall(entries: &'static [&'static BuiltinManifestEntry]) {
118    install_builtin_manifest(entries);
119}
120
121/// Read the installed manifest.
122pub fn installed_manifest() -> Vec<&'static BuiltinManifestEntry> {
123    installed()
124        .read()
125        .expect("builtin manifest lock poisoned")
126        .values()
127        .copied()
128        .collect()
129}
130
131/// Resolve one installed manifest entry.
132pub fn builtin_entry(name: &str) -> Option<&'static BuiltinManifestEntry> {
133    installed()
134        .read()
135        .expect("builtin manifest lock poisoned")
136        .get(name)
137        .copied()
138}
139
140/// Resolve the typed contract for one installed source name.
141pub fn builtin_contract(name: &str) -> Option<&'static BuiltinContract> {
142    builtin_entry(name).map(|entry| &entry.contract)
143}
144
145/// True when the registry has been populated. Useful for guards in parser
146/// code that wants to assert it's running in a configured driver context.
147pub fn is_installed() -> bool {
148    !installed()
149        .read()
150        .expect("builtin manifest lock poisoned")
151        .is_empty()
152}