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 /// 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 core runtime and an optional host capability crate). Reinstalling the
114/// same entries 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.insert(entry.name, entry) {
119 assert!(
120 std::ptr::eq(previous, *entry),
121 "builtin manifest name `{}` registered by multiple contracts",
122 entry.name
123 );
124 }
125 }
126}
127
128/// Test-only one-shot manifest install.
129#[doc(hidden)]
130pub fn _test_only_reinstall(entries: &'static [&'static BuiltinManifestEntry]) {
131 install_builtin_manifest(entries);
132}
133
134/// Read the installed manifest.
135pub fn installed_manifest() -> Vec<&'static BuiltinManifestEntry> {
136 installed()
137 .read()
138 .expect("builtin manifest lock poisoned")
139 .values()
140 .copied()
141 .collect()
142}
143
144/// Resolve one installed manifest entry.
145pub fn builtin_entry(name: &str) -> Option<&'static BuiltinManifestEntry> {
146 installed()
147 .read()
148 .expect("builtin manifest lock poisoned")
149 .get(name)
150 .copied()
151}
152
153/// Resolve the typed contract for one installed source name.
154pub fn builtin_contract(name: &str) -> Option<&'static BuiltinContract> {
155 builtin_entry(name).map(|entry| &entry.contract)
156}
157
158/// True when the registry has been populated. Useful for guards in parser
159/// code that wants to assert it's running in a configured driver context.
160pub fn is_installed() -> bool {
161 !installed()
162 .read()
163 .expect("builtin manifest lock poisoned")
164 .is_empty()
165}