Skip to main content

lean_rs_host/host/
capabilities.rs

1//! `LeanCapabilities`—loaded manifest-checked host shims and optional user
2//! dylibs.
3//!
4//! [`LeanCapabilities`] owns the [`lean_rs::module::LeanLibrary`] handles and
5//! anchors the checked shim capability that [`crate::host::LeanSession`]
6//! resolves typed bindings from:
7//!
8//! - The **user's capability dylib**, when present, is the artefact the
9//!   consumer built with `lake build` and named in
10//!   [`crate::host::LeanHost::load_capabilities`]. The host stack initializes
11//!   it so imported modules can depend on it; arbitrary user export dispatch
12//!   stays in the lower-level `lean-rs` crate.
13//! - The **shim dylib** is `liblean__rs__host__shims_LeanRsHostShims.dylib`,
14//!   built from the `lean-rs-host` crate's bundled shim sources. It contains
15//!   the 28 mandatory + 9 optional `lean_rs_host_*` `@[export]` symbols that
16//!   every typed `LeanSession` method dispatches through. Lake does *not*
17//!   transitively bundle the shim's `@[export]` symbols into the user's dylib
18//!   because `LeanLib.sharedFacet` emits a per-package shared library, not a
19//!   transitive merge, so the host stack
20//!   loads the host shim manifest, its generic interop dependency, and the
21//!   user dylib explicitly. All dylibs share one Lean runtime; each per-module
22//!   `initialize_<Module>` short-circuits idempotently on its own flag.
23//!
24//! A missing or mismatched mandatory shim symbol fails session construction
25//! through checked binding resolution; a missing optional shim symbol
26//! degrades to a synthesised
27//! [`crate::host::meta::LeanMetaResponse::Unsupported`] at the
28//! [`crate::LeanSession::run_meta`] call site. Bindings are resolved once
29//! per session and then cached as typed call handles—no per-query `dlsym`.
30//!
31//! Construction goes through either [`crate::host::LeanHost::load_capabilities`]
32//! or [`crate::host::LeanHost::load_shims_only`]; [`LeanCapabilities::session`]
33//! then imports a module list and returns the long-lived
34//! [`crate::host::LeanSession`] handle.
35
36use core::fmt;
37
38use lean_rs::error::LeanResult;
39use lean_rs::module::{LeanBuiltCapability, LeanCapability, LeanLibrary};
40
41use crate::host::cancellation::LeanCancellationToken;
42use crate::host::host::LeanHost;
43use crate::host::progress::LeanProgressSink;
44use crate::host::session::LeanSession;
45use crate::host::shim_bindings::host_shim_export_signatures;
46
47/// Loaded generic interop, host shim, and optional user dylibs with a checked
48/// host-shim capability ready for session binding resolution.
49///
50/// Owns the [`LeanLibrary`] handles so callers do not have to track
51/// the dylibs' lifetimes separately. Borrows from the parent
52/// [`LeanHost`] for the runtime + project context. Neither [`Send`] nor
53/// [`Sync`]: inherited from the contained `LeanLibrary` handles.
54pub struct LeanCapabilities<'lean, 'h> {
55    host: &'h LeanHost<'lean>,
56    /// User's capability dylib—the one named in `load_capabilities`, absent
57    /// for `load_shims_only`. Kept alive so initialized user modules remain
58    /// loaded while sessions import and query their environments.
59    _user_library: Option<LeanLibrary<'lean>>,
60    /// Manifest-backed bundled host shim capability. Session construction
61    /// resolves typed bindings from this checked surface.
62    shim_capability: LeanCapability<'lean>,
63}
64
65impl fmt::Debug for LeanCapabilities<'_, '_> {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        f.debug_struct("LeanCapabilities").finish_non_exhaustive()
68    }
69}
70
71impl<'lean, 'h> LeanCapabilities<'lean, 'h> {
72    /// Build a [`LeanCapabilities`] from an opened user-capability
73    /// library, opening the generic interop and host shim dylibs alongside it.
74    ///
75    /// Initializes the root module of the user's dylib (idempotent
76    /// through Lean's `_G_initialized` short-circuit), opens and
77    /// initializes the generic interop and host shim dylibs built from the
78    /// crate-owned shim sources, and resolves the
79    /// manifest-backed shim capability. Session construction resolves 28
80    /// mandatory and 9 optional checked typed bindings from that capability.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`lean_rs::LeanError::Host`] with stage
85    /// [`lean_rs::HostStage::Load`] /
86    /// [`lean_rs::LeanDiagnosticCode::ModuleInit`] if the bundled shim dylibs
87    /// cannot be built or located. Returns [`lean_rs::HostStage::Link`] if the
88    /// initializer or any of the 28 **mandatory** symbols is missing from the
89    /// shim dylib. Missing optional meta-service symbols never fail capability
90    /// load.
91    pub(crate) fn new(
92        host: &'h LeanHost<'lean>,
93        user_library: LeanLibrary<'lean>,
94        package: &str,
95        lib_name: &str,
96    ) -> LeanResult<Self> {
97        let shim_capability = load_shim_capability(host)?;
98
99        // Now the user dylib. It does not need to depend on the host shims;
100        // ad-hoc user exports still resolve from this library.
101        let _user_module = user_library.initialize_module(package, lib_name)?;
102
103        Ok(Self {
104            host,
105            _user_library: Some(user_library),
106            shim_capability,
107        })
108    }
109
110    /// Build a [`LeanCapabilities`] backed only by the bundled interop and
111    /// host shim dylibs.
112    ///
113    /// Sessions opened from this value can use every shim-backed session
114    /// operation without loading a user capability dylib.
115    pub(crate) fn new_shims_only(host: &'h LeanHost<'lean>) -> LeanResult<Self> {
116        let shim_capability = load_shim_capability(host)?;
117        Ok(Self {
118            host,
119            _user_library: None,
120            shim_capability,
121        })
122    }
123
124    /// Import the named modules into a fresh Lean environment and
125    /// return a session over the result.
126    ///
127    /// Imports happen exactly once per `session()` call. The returned
128    /// [`LeanSession`] owns the imported environment and reuses this
129    /// capability's checked shim bindings for every query.
130    ///
131    /// # Errors
132    ///
133    /// Returns [`lean_rs::LeanError::Cancelled`] if `cancellation` is
134    /// already cancelled before import dispatch.
135    ///
136    /// Returns [`lean_rs::LeanError::LeanException`] if the Lean-side
137    /// import raises (missing `.olean`, malformed module name, …),
138    /// with the bounded message Lean surfaced.
139    pub fn session<'c>(
140        &'c self,
141        imports: &[&str],
142        cancellation: Option<&LeanCancellationToken>,
143        progress: Option<&dyn LeanProgressSink>,
144    ) -> LeanResult<LeanSession<'lean, 'c>> {
145        LeanSession::import(self, imports, cancellation, progress)
146    }
147
148    /// The capability's parent host (for runtime + project access by
149    /// the session dispatch).
150    pub(crate) fn host(&self) -> &'h LeanHost<'lean> {
151        self.host
152    }
153
154    /// Manifest-backed bundled host shim capability.
155    pub(crate) fn shim_capability(&self) -> &LeanCapability<'lean> {
156        &self.shim_capability
157    }
158}
159
160fn load_shim_capability<'lean>(host: &LeanHost<'lean>) -> LeanResult<LeanCapability<'lean>> {
161    let built = crate::host::lake::LakeProject::shim_capability(host_shim_export_signatures())?;
162    LeanCapability::from_build_manifest(
163        host.runtime(),
164        LeanBuiltCapability::manifest_path(built.manifest_path()),
165    )
166}