lean_rs/abi/traits.rs
1//! Conversion traits for first-order Lean values.
2//!
3//! Three sealed traits with distinct roles:
4//!
5//! - [`IntoLean`] / [`TryFromLean`] (`pub(crate)`)—convert between Rust
6//! values and polymorphic-boxed [`Obj`]. Used for container elements,
7//! structure fields, and any Lean position where the value lives behind
8//! a `lean_object*`. The classic encoding/decoding direction.
9//! - [`LeanAbi`] (`pub`, sealed)—convert between Rust values and the
10//! *C-ABI representation* Lake emits for a top-level Lean export
11//! parameter or return. The C representation varies: `uint8_t` for
12//! `Bool`, `uint32_t` for `Char`, `double` for `Float`, scalar primitive
13//! for `UIntN`/`UIntN`, and `lean_object*` for everything boxed. This
14//! trait drives [`crate::module::LeanExported`]'s typed function-pointer
15//! cast.
16//!
17//! `LeanAbi` is the third (and final) conversion trait. It coexists
18//! with `IntoLean`/`TryFromLean` because they encode different
19//! conventions for the same Rust type: `u8 as IntoLean` produces a
20//! polymorphic-boxed `lean_box(u8 as usize)`, while `u8 as LeanAbi`
21//! produces an unboxed `uint8_t` matching Lake's emitted signature.
22//!
23//! Borrowed conversions do not introduce a new trait. Where a Rust
24//! borrowed type appears in a Lean export's argument tuple, the per-type
25//! module adds an `impl LeanAbi for &T` rather than a new
26//! `BorrowedLeanAbi` trait. The `&str` impl in `super::string` is the
27//! earned case: `LeanSession::elaborate`, `kernel_check`, `elaborate_bulk`,
28//! and `make_name` each accepted `&str` from callers and previously paid
29//! a `String::to_owned()` only to reach `LeanAbi<'lean> for String`.
30//! Borrowed-only reads (`borrow_str`) stay as free functions because they
31//! are zero-copy *return*-direction helpers and never need to satisfy the
32//! `LeanAbi` arg-tuple bound.
33
34use lean_rs_sys::lean_object;
35use lean_toolchain::LeanExportAbiRepr;
36
37#[cfg(doc)]
38use crate::error::{HostStage, LeanDiagnosticCode};
39use crate::error::{LeanError, LeanResult};
40use crate::runtime::LeanRuntime;
41use crate::runtime::obj::Obj;
42
43/// C representation types supported by manifest-backed export signatures.
44///
45/// This is deliberately narrower than Rust FFI in general: Lean exports use
46/// either `lean_object*` or the scalar shapes Lake emits for first-order Lean
47/// values.
48pub trait LeanCReprAbi: Copy + 'static {
49 /// Manifest ABI representation for this C slot.
50 const EXPORT_ABI_REPR: LeanExportAbiRepr;
51}
52
53impl LeanCReprAbi for *mut lean_object {
54 const EXPORT_ABI_REPR: LeanExportAbiRepr = LeanExportAbiRepr::LeanObject;
55}
56
57macro_rules! impl_c_repr_abi {
58 ($($ty:ty => $repr:ident),* $(,)?) => {
59 $(
60 impl LeanCReprAbi for $ty {
61 const EXPORT_ABI_REPR: LeanExportAbiRepr = LeanExportAbiRepr::$repr;
62 }
63 )*
64 };
65}
66
67impl_c_repr_abi! {
68 u8 => U8,
69 u16 => U16,
70 u32 => U32,
71 u64 => U64,
72 usize => USize,
73 i8 => I8,
74 i16 => I16,
75 i32 => I32,
76 i64 => I64,
77 isize => ISize,
78 f64 => F64,
79}
80
81/// Move a Rust value into a freshly owned Lean object.
82///
83/// The returned [`Obj`] carries exactly one Lean reference count and is
84/// anchored to the `&'lean LeanRuntime` borrow that witnessed the call.
85pub trait IntoLean<'lean>: Sized {
86 /// Allocate (or scalar-box) a Lean representation of `self` and return
87 /// the owned handle.
88 fn into_lean(self, runtime: &'lean LeanRuntime) -> Obj<'lean>;
89}
90
91/// Decode an owned Lean object into a Rust value.
92///
93/// Consumes the [`Obj`]—even on failure, the refcount is released by
94/// `obj`'s `Drop`. The function signature returns the error type without
95/// the Obj because the cases where the caller wants to recover the
96/// original `Obj` are rare; if they arise, we will add a `try_from_lean_ref`
97/// variant against an `ObjRef` rather than complicating this trait.
98pub trait TryFromLean<'lean>: Sized {
99 /// Decode `obj` into `Self`, returning a
100 /// [`LeanError::Host`](LeanError) with stage
101 /// [`HostStage::Conversion`] if the object's kind or payload is
102 /// outside the type's representable range.
103 ///
104 /// # Errors
105 ///
106 /// Per-impl behaviours are documented at the impl site. Helpers in
107 /// the per-type modules use the [`conversion_error`] free
108 /// function to build the bounded diagnostic.
109 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self>;
110}
111
112/// Build a `LeanError::Host { stage: Conversion, .. }` carrying a uniform
113/// diagnostic.
114///
115/// Centralised so per-type ABI impls share the wording and so a future
116/// log/sink can hook one site instead of N.
117pub fn conversion_error(message: impl Into<String>) -> LeanError {
118 LeanError::abi_conversion(message)
119}
120
121// -- Sealing for LeanAbi -----------------------------------------------
122
123/// Supertrait that seals [`LeanAbi`] against external implementations.
124///
125/// The module is `pub` (not `pub(crate)`) because Cargo has no "friend
126/// crate" visibility and the sibling [`lean-rs-host`](https://docs.rs/lean-rs-host)
127/// crate genuinely needs to implement `LeanAbi` for its own
128/// host-defined types (`LeanEvidence` etc.). The pattern that holds is:
129///
130/// - **External crates** (anyone other than `lean-rs-host`) cannot
131/// implement `LeanAbi` for their own types: the orphan rule blocks
132/// `impl LeanAbi for MyType` directly, and writing
133/// `impl SealedAbi for MyType` is a transparent intent-to-bypass
134/// that bypasses the intended API boundary. Combined with the
135/// `#[doc(hidden)]` module marker on the parent module's internal
136/// re-exports, the signal is unambiguous.
137/// - **The sibling `lean-rs-host` crate** reaches `SealedAbi` directly
138/// and implements both `SealedAbi` and `LeanAbi` for its host types.
139/// This is intentional; the sealing is against accidental external
140/// impls, not the sibling service crate.
141#[doc(hidden)]
142pub mod sealed {
143 /// Sealed supertrait for [`super::LeanAbi`]. See module-level docs
144 /// for the sibling-crate implementation boundary.
145 pub trait SealedAbi {}
146}
147
148/// Per-type C-ABI representation used by [`crate::module::LeanExported`].
149///
150/// Lake emits unboxed C primitives for `UIntN`/`IntN`/`USize`/`ISize`/
151/// `Bool`/`Char`/`Float` exports; boxed `lean_object*` for everything else
152/// (`String`, `ByteArray`, `Nat`, `Int`, structures, IO results, …). The
153/// per-type [`CRepr`](LeanAbi::CRepr) records which convention applies.
154///
155/// `into_c` / `from_c` are paired: a type's `CRepr` is invariant between
156/// the encode and decode directions, so they live on one trait (Ousterhout
157/// ch 9—combining concerns that share information).
158///
159/// Sealed via [`sealed::SealedAbi`]. External crates cannot implement
160/// this trait for their own types (orphan rule + sealed supertrait
161/// rejection). The sibling `lean-rs-host` crate reaches the internal trait
162/// intentionally and implements `LeanAbi` for its host-defined
163/// types—that is the documented service-layer extension point, not a
164/// stability violation.
165pub trait LeanAbi<'lean>: Sized + sealed::SealedAbi {
166 /// The C-ABI type Lake emits for this Lean type at function
167 /// signatures.
168 type CRepr: LeanCReprAbi;
169
170 /// Encode `self` into the C-ABI representation. The returned value
171 /// is suitable for passing as a function argument; ownership of any
172 /// allocated Lean object is transferred to the receiver.
173 #[doc(hidden)]
174 fn into_c(self, runtime: &'lean LeanRuntime) -> Self::CRepr;
175
176 /// Decode an owned C-ABI value into [`Self`].
177 ///
178 /// For boxed `CRepr = *mut lean_object`, the pointer carries one
179 /// owned reference count (per Lake's `lean_obj_res` ownership
180 /// contract); `from_c` consumes it.
181 ///
182 /// `clippy::not_unsafe_ptr_arg_deref` is allowed: the function is
183 /// only invoked through the sealed [`crate::module::DecodeCallResult`]
184 /// dispatch, which receives `c` directly from the
185 /// `unsafe extern "C"` call inside [`crate::module::LeanExported`].
186 /// Marking this method `unsafe fn` would cascade through every per-type
187 /// impl without adding safety beyond what sealing already enforces.
188 ///
189 /// # Errors
190 ///
191 /// Returns [`LeanError::Host`] with stage [`HostStage::Conversion`]
192 /// if the value cannot be decoded into `Self` (kind mismatch,
193 /// out-of-range bignum, malformed UTF-8, non-Unicode `char` payload).
194 #[doc(hidden)]
195 #[allow(
196 clippy::not_unsafe_ptr_arg_deref,
197 reason = "sealed trait—called only by LeanExported"
198 )]
199 fn from_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self>;
200}
201
202// -- LeanAbi for Obj<'lean> -------------------------------------------
203//
204// The identity impl: `Obj<'lean>` already IS the boxed C ABI shape.
205// Lets `LeanExported<(Obj,), Obj>` work for tests that pass Lean values
206// constructed via per-type helpers (`nat::from_u64`, `string::from_str`,
207// …) directly without re-typing.
208
209impl sealed::SealedAbi for Obj<'_> {}
210
211impl<'lean> LeanAbi<'lean> for Obj<'lean> {
212 type CRepr = *mut lean_object;
213 fn into_c(self, _runtime: &'lean LeanRuntime) -> *mut lean_object {
214 self.into_raw()
215 }
216 #[allow(
217 clippy::not_unsafe_ptr_arg_deref,
218 reason = "sealed trait—called only by LeanExported"
219 )]
220 fn from_c(c: *mut lean_object, runtime: &'lean LeanRuntime) -> LeanResult<Self> {
221 // SAFETY: `c` carries one owned reference count returned from
222 // an extern Lean function (per Lake's `lean_obj_res` contract).
223 // `runtime` is the witness for `'lean`.
224 #[allow(unsafe_code)]
225 Ok(unsafe { Obj::from_owned_raw(runtime, c) })
226 }
227}
228
229// `Obj<'lean>: TryFromLean<'lean>` is the identity decoder. It lets a
230// caller write `LeanIo<Obj<'lean>>` as the typed handle's return type to
231// get the raw IO payload back as an `Obj`, then decode through a
232// per-type helper (`nat::try_to_u64`, `ctor_tag`, …) when the value
233// shape doesn't fit a polymorphic-boxing `TryFromLean` impl.
234//
235// `Obj<'lean>` deliberately does NOT implement `IntoLean<'lean>`—
236// passing an `Obj` as an argument goes through `LeanAbi::into_c`
237// (identity), not through the polymorphic-boxing path.
238
239impl<'lean> TryFromLean<'lean> for Obj<'lean> {
240 fn try_from_lean(obj: Self) -> LeanResult<Self> {
241 Ok(obj)
242 }
243}