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, PartialEq, Eq)]
21pub struct BuiltinManifestEntry {
22 pub name: &'static str,
23 /// The builtin's primary name. Equal to [`Self::name`] except on an alias
24 /// entry, which repeats the primary's signature and contract under a second
25 /// name. Projections keyed by something other than the name — a capability
26 /// method, say — must use only the entry where these two agree, or they
27 /// will see one builtin as several.
28 pub canonical_name: &'static str,
29 pub signature: &'static BuiltinSignature,
30 pub contract: BuiltinContract,
31}
32
33impl BuiltinManifestEntry {
34 /// Whether this entry is the builtin's primary name rather than an alias.
35 pub fn is_canonical(&self) -> bool {
36 self.name == self.canonical_name
37 }
38}
39
40/// A complete description of one builtin: its signature, its aliases, the
41/// runtime handler (typed by the consumer via `H`), and optional metadata.
42///
43/// `H` is parametric so this crate stays free of any handler-type
44/// dependency. `harn-vm` instantiates it as
45/// `BuiltinDef<VmBuiltinHandler>`; parser-only consumers ignore the handler
46/// and read just the [`Self::sig`] field.
47#[derive(Debug, Clone, Copy)]
48pub struct BuiltinDef<H: 'static> {
49 /// Static signature consumed by the parser/typechecker.
50 pub sig: BuiltinSignature,
51 /// Typed source exposure and effect contract. This is the semantic owner
52 /// consumed by every parser/runtime/policy projection.
53 pub contract: BuiltinContract,
54 /// Additional names that share this impl + signature. Each alias gets
55 /// its own [`BuiltinSignature`] entry at install time (with the same
56 /// param/return types) so the typechecker accepts both.
57 pub aliases: &'static [&'static str],
58 /// Runtime handler (sync fn, async fn, or `None` for parser-only
59 /// builtins). Type is opaque to this crate.
60 pub handler: H,
61 /// Free-form category label used for metadata/observability.
62 pub category: Option<&'static str>,
63 /// Human-readable doc, typically the leading `///` block from the impl
64 /// function. Surfaced to LSP hover and `harn explain`.
65 pub doc: Option<&'static str>,
66 /// Free-form Harn-style signature text (e.g. `"foo(a: dict) -> dict"`).
67 /// Populated by `#[harn_builtin]` from the `sig = "..."` literal so the
68 /// runtime metadata layer can surface the original source spelling
69 /// without re-rendering [`Self::sig`]. The DSL builder shape used to
70 /// store this via `.signature(...)`; the macro shape replaces it.
71 pub signature_text: Option<&'static str>,
72 /// Set to `true` for builtins that exist in the parser registry but
73 /// have no runtime entry (`len`, `split`, … — see
74 /// `PARSER_ONLY_EXCEPTIONS` in the alignment test). The registry
75 /// skips runtime registration for these.
76 pub parser_only: bool,
77 /// Set to `true` for compiler-synthesized runtime helpers (sigil
78 /// prefix `__`, opcode keywords, enum constructors) that exist as VM
79 /// builtins but should NOT show up in the parser signature table.
80 /// The registry skips signature publishing for these.
81 pub runtime_only: bool,
82}
83
84impl<H: 'static> BuiltinDef<H> {
85 /// Compact constructor for the common case: one signature, no aliases,
86 /// no metadata flags.
87 pub const fn new(sig: BuiltinSignature, handler: H) -> Self {
88 Self {
89 sig,
90 contract: BuiltinContract::UNDECLARED,
91 aliases: &[],
92 handler,
93 category: None,
94 doc: None,
95 signature_text: None,
96 parser_only: false,
97 runtime_only: false,
98 }
99 }
100}
101
102static INSTALLED: OnceLock<RwLock<BTreeMap<&'static str, &'static BuiltinManifestEntry>>> =
103 OnceLock::new();
104
105fn installed() -> &'static RwLock<BTreeMap<&'static str, &'static BuiltinManifestEntry>> {
106 INSTALLED.get_or_init(|| RwLock::new(BTreeMap::new()))
107}
108
109/// Install the process-global builtin manifest.
110///
111/// # Panics
112/// Multiple owners may contribute disjoint manifest fragments (for example
113/// the portable kernel and a native host). Installing structurally identical
114/// projections is idempotent; redefining an existing name panics.
115pub fn install_builtin_manifest(entries: &'static [&'static BuiltinManifestEntry]) {
116 let mut manifest = installed().write().expect("builtin manifest lock poisoned");
117 for entry in entries {
118 if let Some(previous) = manifest.get(entry.name) {
119 assert!(
120 *previous == *entry,
121 "builtin manifest name `{}` registered by multiple contracts",
122 entry.name
123 );
124 } else {
125 manifest.insert(entry.name, entry);
126 }
127 }
128}
129
130/// Test-only one-shot manifest install.
131#[doc(hidden)]
132pub fn _test_only_reinstall(entries: &'static [&'static BuiltinManifestEntry]) {
133 install_builtin_manifest(entries);
134}
135
136/// Read the installed manifest.
137pub fn installed_manifest() -> Vec<&'static BuiltinManifestEntry> {
138 installed()
139 .read()
140 .expect("builtin manifest lock poisoned")
141 .values()
142 .copied()
143 .collect()
144}
145
146/// Resolve one installed manifest entry.
147pub fn builtin_entry(name: &str) -> Option<&'static BuiltinManifestEntry> {
148 installed()
149 .read()
150 .expect("builtin manifest lock poisoned")
151 .get(name)
152 .copied()
153}
154
155/// Resolve the typed contract for one installed source name.
156pub fn builtin_contract(name: &str) -> Option<&'static BuiltinContract> {
157 builtin_entry(name).map(|entry| &entry.contract)
158}
159
160/// True when the registry has been populated. Useful for guards in parser
161/// code that wants to assert it's running in a configured driver context.
162pub fn is_installed() -> bool {
163 !installed()
164 .read()
165 .expect("builtin manifest lock poisoned")
166 .is_empty()
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use harn_builtin_meta::{Param, Ty};
173
174 const SIGNATURE_A: BuiltinSignature = BuiltinSignature::simple(
175 "__registry_structural_idempotence",
176 &[Param::new("value", Ty::Named("string"))],
177 Ty::Named("string"),
178 );
179 const SIGNATURE_B: BuiltinSignature = SIGNATURE_A;
180 static ENTRY_A: BuiltinManifestEntry = BuiltinManifestEntry {
181 name: "__registry_structural_idempotence",
182 canonical_name: "__registry_structural_idempotence",
183 signature: &SIGNATURE_A,
184 contract: BuiltinContract::PURE,
185 };
186 static ENTRY_B: BuiltinManifestEntry = BuiltinManifestEntry {
187 name: "__registry_structural_idempotence",
188 canonical_name: "__registry_structural_idempotence",
189 signature: &SIGNATURE_B,
190 contract: BuiltinContract::PURE,
191 };
192 static MANIFEST_A: &[&BuiltinManifestEntry] = &[&ENTRY_A];
193 static MANIFEST_B: &[&BuiltinManifestEntry] = &[&ENTRY_B];
194
195 #[test]
196 fn structurally_identical_projections_are_idempotent() {
197 install_builtin_manifest(MANIFEST_A);
198 install_builtin_manifest(MANIFEST_B);
199 assert!(std::ptr::eq(
200 builtin_entry(ENTRY_A.name).unwrap(),
201 &raw const ENTRY_A
202 ));
203 }
204}