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}