Skip to main content

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;
35
36#[cfg(doc)]
37use crate::error::{HostStage, LeanDiagnosticCode};
38use crate::error::{LeanError, LeanResult};
39use crate::runtime::LeanRuntime;
40use crate::runtime::obj::Obj;
41
42/// Move a Rust value into a freshly owned Lean object.
43///
44/// The returned [`Obj`] carries exactly one Lean reference count and is
45/// anchored to the `&'lean LeanRuntime` borrow that witnessed the call.
46pub trait IntoLean<'lean>: Sized {
47    /// Allocate (or scalar-box) a Lean representation of `self` and return
48    /// the owned handle.
49    fn into_lean(self, runtime: &'lean LeanRuntime) -> Obj<'lean>;
50}
51
52/// Decode an owned Lean object into a Rust value.
53///
54/// Consumes the [`Obj`] — even on failure, the refcount is released by
55/// `obj`'s `Drop`. The function signature returns the error type without
56/// the Obj because the cases where the caller wants to recover the
57/// original `Obj` are rare; if they arise, we will add a `try_from_lean_ref`
58/// variant against an `ObjRef` rather than complicating this trait.
59pub trait TryFromLean<'lean>: Sized {
60    /// Decode `obj` into `Self`, returning a
61    /// [`LeanError::Host`](LeanError) with stage
62    /// [`HostStage::Conversion`] if the object's kind or payload is
63    /// outside the type's representable range.
64    ///
65    /// # Errors
66    ///
67    /// Per-impl behaviours are documented at the impl site. Helpers in
68    /// the per-type modules use the [`conversion_error`] free
69    /// function to build the bounded diagnostic.
70    fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self>;
71}
72
73/// Build a `LeanError::Host { stage: Conversion, .. }` carrying a uniform
74/// diagnostic.
75///
76/// Centralised so per-type ABI impls share the wording and so a future
77/// log/sink can hook one site instead of N.
78pub fn conversion_error(message: impl Into<String>) -> LeanError {
79    LeanError::abi_conversion(message)
80}
81
82// -- Sealing for LeanAbi -----------------------------------------------
83
84/// Supertrait that seals [`LeanAbi`] against external implementations.
85///
86/// The module is `pub` (not `pub(crate)`) because Cargo has no "friend
87/// crate" visibility and the sibling [`lean-rs-host`](https://docs.rs/lean-rs-host)
88/// crate genuinely needs to implement `LeanAbi` for its own
89/// host-defined types (`LeanEvidence` etc.) — that's exactly what the
90/// L1 → L2 seam is for. The pattern that holds is:
91///
92/// - **External crates** (anyone other than `lean-rs-host`) cannot
93///   implement `LeanAbi` for their own types: the orphan rule blocks
94///   `impl LeanAbi for MyType` directly, and writing
95///   `impl SealedAbi for MyType` is a transparent intent-to-bypass
96///   that any honest reviewer should reject — combined with the
97///   `#[doc(hidden)]` module marker on the parent module's seam
98///   re-exports, the signal is unambiguous.
99/// - **The sibling `lean-rs-host` crate** reaches `SealedAbi` directly
100///   and implements both `SealedAbi` and `LeanAbi` for its host types.
101///   This is intentional; the sealing is against accidental external
102///   impls, not the same-team L2 stack.
103#[doc(hidden)]
104pub mod sealed {
105    /// Sealed supertrait for [`super::LeanAbi`]. See module-level docs
106    /// for the L1 → L2 sibling-crate seam discipline.
107    pub trait SealedAbi {}
108}
109
110/// Per-type C-ABI representation used by [`crate::module::LeanExported`].
111///
112/// Lake emits unboxed C primitives for `UIntN`/`IntN`/`USize`/`ISize`/
113/// `Bool`/`Char`/`Float` exports; boxed `lean_object*` for everything else
114/// (`String`, `ByteArray`, `Nat`, `Int`, structures, IO results, …). The
115/// per-type [`CRepr`](LeanAbi::CRepr) records which convention applies.
116///
117/// `into_c` / `from_c` are paired: a type's `CRepr` is invariant between
118/// the encode and decode directions, so they live on one trait (Ousterhout
119/// ch 9 — combining concerns that share information).
120///
121/// Sealed via [`sealed::SealedAbi`]. External crates cannot implement
122/// this trait for their own types (orphan rule + sealed supertrait
123/// rejection). The sibling `lean-rs-host` crate reaches the seam
124/// intentionally and implements `LeanAbi` for its host-defined
125/// types — that's the documented L1 → L2 extension point, not a
126/// stability violation.
127pub trait LeanAbi<'lean>: Sized + sealed::SealedAbi {
128    /// The C-ABI type Lake emits for this Lean type at function
129    /// signatures.
130    type CRepr: Copy;
131
132    /// Encode `self` into the C-ABI representation. The returned value
133    /// is suitable for passing as a function argument; ownership of any
134    /// allocated Lean object is transferred to the receiver.
135    #[doc(hidden)]
136    fn into_c(self, runtime: &'lean LeanRuntime) -> Self::CRepr;
137
138    /// Decode an owned C-ABI value into [`Self`].
139    ///
140    /// For boxed `CRepr = *mut lean_object`, the pointer carries one
141    /// owned reference count (per Lake's `lean_obj_res` ownership
142    /// contract); `from_c` consumes it.
143    ///
144    /// `clippy::not_unsafe_ptr_arg_deref` is allowed: the function is
145    /// only invoked through the sealed [`crate::module::DecodeCallResult`]
146    /// dispatch, which receives `c` directly from the
147    /// `unsafe extern "C"` call inside [`crate::module::LeanExported`].
148    /// Marking this method `unsafe fn` would cascade through every per-type
149    /// impl without adding safety beyond what sealing already enforces.
150    ///
151    /// # Errors
152    ///
153    /// Returns [`LeanError::Host`] with stage [`HostStage::Conversion`]
154    /// if the value cannot be decoded into `Self` (kind mismatch,
155    /// out-of-range bignum, malformed UTF-8, non-Unicode `char` payload).
156    #[doc(hidden)]
157    #[allow(
158        clippy::not_unsafe_ptr_arg_deref,
159        reason = "sealed trait — called only by LeanExported"
160    )]
161    fn from_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self>;
162}
163
164// -- LeanAbi for Obj<'lean> -------------------------------------------
165//
166// The identity impl: `Obj<'lean>` already IS the boxed C ABI shape.
167// Lets `LeanExported<(Obj,), Obj>` work for tests that pass Lean values
168// constructed via per-type helpers (`nat::from_u64`, `string::from_str`,
169// …) directly without re-typing.
170
171impl sealed::SealedAbi for Obj<'_> {}
172
173impl<'lean> LeanAbi<'lean> for Obj<'lean> {
174    type CRepr = *mut lean_object;
175    fn into_c(self, _runtime: &'lean LeanRuntime) -> *mut lean_object {
176        self.into_raw()
177    }
178    #[allow(
179        clippy::not_unsafe_ptr_arg_deref,
180        reason = "sealed trait — called only by LeanExported"
181    )]
182    fn from_c(c: *mut lean_object, runtime: &'lean LeanRuntime) -> LeanResult<Self> {
183        // SAFETY: `c` carries one owned reference count returned from
184        // an extern Lean function (per Lake's `lean_obj_res` contract).
185        // `runtime` is the witness for `'lean`.
186        #[allow(unsafe_code)]
187        Ok(unsafe { Obj::from_owned_raw(runtime, c) })
188    }
189}
190
191// `Obj<'lean>: TryFromLean<'lean>` is the identity decoder. It lets a
192// caller write `LeanIo<Obj<'lean>>` as the typed handle's return type to
193// get the raw IO payload back as an `Obj`, then decode through a
194// per-type helper (`nat::try_to_u64`, `ctor_tag`, …) when the value
195// shape doesn't fit a polymorphic-boxing `TryFromLean` impl.
196//
197// `Obj<'lean>` deliberately does NOT implement `IntoLean<'lean>` —
198// passing an `Obj` as an argument goes through `LeanAbi::into_c`
199// (identity), not through the polymorphic-boxing path.
200
201impl<'lean> TryFromLean<'lean> for Obj<'lean> {
202    fn try_from_lean(obj: Self) -> LeanResult<Self> {
203        Ok(obj)
204    }
205}