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    /// 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}