lean_rs/module/exported.rs
1//! Typed handles for exported Lean functions.
2//!
3//! The public dispatch surface has two lookup paths:
4//!
5//! - [`LeanCapability::exported<Args, R>(name)`](super::capability::LeanCapability::exported)
6//! —safe checked lookup for capabilities opened from trusted manifest
7//! signature metadata.
8//! - [`LeanModule::exported_unchecked<Args, R>(name)`](super::loaded::LeanModule::exported_unchecked)
9//! —unsafe arbitrary lookup for callers that can prove the symbol's
10//! compiled C ABI matches the requested Rust `Args`/`R` shape.
11//!
12//! The typed handle surface is two types and three sealed traits:
13//!
14//! - [`LeanExported<'lean, 'lib, Args, R>`]—a typed handle for an
15//! exported Lean symbol. `Args` is a tuple of Rust argument types
16//! (`()`, `(A,)`, `(A, B)`, … up to arity 12); each element must
17//! implement [`crate::abi::traits::LeanAbi`]. `R` is the return type, bounded
18//! by [`DecodeCallResult`]—a sealed trait satisfied by every
19//! [`crate::abi::traits::LeanAbi`] type (pure call) and by [`LeanIo<T>`] for
20//! `T: crate::abi::traits::TryFromLean` (IO-returning Lean export).
21//! - [`LeanIo<T>`]—return-type marker for Lean exports declared
22//! `IO α`. Writing `exported_unchecked::<Args, LeanIo<T>>(name)` tells the
23//! handle to compose `decode_io` before
24//! [`crate::abi::traits::TryFromLean::try_from_lean`]. The `.call(...)` method
25//! returns `LeanResult<T>` (not `LeanResult<LeanIo<T>>`)—the marker
26//! only lives in the type signature.
27//!
28//! `LeanModule::exported_unchecked` distinguishes function-symbol
29//! resolution from Lean nullary-constant global reads transparently,
30//! using the `globals` set computed at
31//! [`super::library::LeanLibrary::open`].
32//!
33//! ## Call shape
34//!
35//! ```ignore
36//! let runtime = lean_rs::LeanRuntime::init()?;
37//! let library = lean_rs::module::LeanLibrary::open(runtime, path)?;
38//! let module = library.initialize_module("foo_pkg", "Foo.Bar")?;
39//!
40//! // Pure, arity 1:
41//! // SAFETY: the Lean export's C ABI is known to be `String -> String`.
42//! let f = unsafe { module.exported_unchecked::<(String,), String>("foo_string_identity") }?;
43//! let s = f.call("abc".to_owned())?;
44//!
45//! // IO, arity 0; return type carries the marker, .call returns T directly:
46//! // SAFETY: the Lean export's C ABI is known to be `IO UInt64`.
47//! let g = unsafe { module.exported_unchecked::<(), lean_rs::module::LeanIo<u64>>("foo_io_seven") }?;
48//! let n: u64 = g.call()?;
49//! ```
50//!
51//! ## Design rationale
52//!
53//! A single tuple-`Args` handle replaces an arity-stamped
54//! `LeanExported0..LeanExported12` family. Arity lives in the tuple
55//! type, not in the method name. IO-ness lives in the return type rather
56//! than in a `.call_io()` method. Per-type C-ABI representation (unboxed
57//! scalar vs boxed `lean_object*`) is hidden behind [`crate::abi::traits::LeanAbi`]
58//!—Lake emits both shapes depending on the Lean type, and the typed
59//! handle's function-pointer cast is generic over each arg's `CRepr`.
60//!
61//! Lake's compiled `IO α` exports take only the user-visible arguments at
62//! the C ABI; the "world" is a Lean-level abstraction the compiler
63//! optimises away for top-level IO exports, so `.call` synthesises no
64//! world token.
65//!
66//! ## Checked lookup boundary
67//!
68//! `LeanCapability::exported` is safe because the capability manifest
69//! supplies the trusted ABI fact before the typed handle is built.
70//! `LeanModule::exported_unchecked` remains unsafe because a raw symbol
71//! name plus caller-chosen generic types cannot establish that fact.
72//! Raw dynamic-loader addresses are intentionally not part of the
73//! public API.
74
75// SAFETY DOC: every `unsafe { ... }` block in this file carries its own
76// `// SAFETY:` comment. The blanket allow exists because this is the
77// single unchecked-front-door site that resolves and dispatches
78// user-typed Lean exports, per `docs/architecture/01-safety-model.md`.
79#![allow(unsafe_code)]
80// The public surface intentionally references `pub(crate)` items
81// (`Obj`) inside trait method signatures. Soundness is enforced by
82// sealing (`sealed::SealedArgs` / `sealed::SealedResult`); downstream
83// cannot add `LeanArgs` or `DecodeCallResult` impls, so the
84// visibility-of-bounds lints are a documentation concern that does not
85// apply here.
86#![allow(private_bounds, private_interfaces)]
87
88use core::ffi::c_void;
89use core::marker::PhantomData;
90
91use lean_rs_sys::lean_object;
92use lean_rs_sys::refcount::lean_inc;
93use lean_toolchain::{
94 LeanExportAbiRepr, LeanExportArgAbi, LeanExportOwnership, LeanExportResultConvention, LeanExportReturnAbi,
95};
96
97use super::library::LeanLibrary;
98use super::loaded::LeanModule;
99use crate::abi::traits::{LeanAbi, LeanCReprAbi, TryFromLean};
100#[cfg(doc)]
101use crate::error::HostStage;
102use crate::error::io::decode_io;
103use crate::error::{LeanError, LeanResult};
104use crate::runtime::LeanRuntime;
105use crate::runtime::obj::Obj;
106
107/// A typed handle for an exported Lean symbol.
108///
109/// Holds a resolved symbol address (function or persistent global) and
110/// the runtime borrow that anchors any `Obj` it produces. Construction
111/// goes exclusively through
112/// [`LeanModule::exported_unchecked`](super::loaded::LeanModule::exported_unchecked); the
113/// handle borrows from its source [`LeanModule`] via `'lib` so neither
114/// the library nor the runtime can be dropped while a handle is live.
115///
116/// `Args` is a tuple of Rust argument types whose elements implement
117/// [`LeanAbi`]; the [`LeanArgs`] impl for that tuple supplies the
118/// arity. `R` is the return type, satisfying [`DecodeCallResult`].
119///
120/// Neither [`Send`] nor [`Sync`]: the contained runtime reference and
121/// the `*mut` symbol address both propagate the per-thread restriction.
122pub struct LeanExported<'lean, 'lib, Args, R> {
123 target: CallableTarget,
124 runtime: &'lean LeanRuntime,
125 _life: PhantomData<&'lib LeanLibrary<'lean>>,
126 _args: PhantomData<fn(Args) -> R>,
127}
128
129/// Return-type marker for Lean exports declared `IO α`.
130///
131/// Writing `exported_unchecked::<Args, LeanIo<T>>(name)` makes [`LeanExported`]'s
132/// `.call` method compose `decode_io` before
133/// `TryFromLean::try_from_lean`, so the handle returns `LeanResult<T>`.
134/// The marker has no value—it is a pure type-level switch.
135///
136/// `LeanIo<T>` cannot be constructed from outside the crate (its single
137/// field is private); it appears only in `R` positions on
138/// [`LeanModule::exported_unchecked`](super::loaded::LeanModule::exported_unchecked).
139pub struct LeanIo<T>(PhantomData<fn() -> T>);
140
141/// Internal: which symbol shape the handle dispatches.
142///
143/// Lean compiles `def x : T := constant` to a persistent
144/// `lean_object*` data-section symbol (`lean_mark_persistent` at module
145/// init); the `Global` variant carries the address of that storage.
146/// Every other `@[export]` is a callable function whose entry point is
147/// the symbol's address.
148enum CallableTarget {
149 /// Symbol resolves to a function entry point.
150 Function(*mut c_void),
151 /// Symbol resolves to the storage of a persistent `lean_object*`.
152 Global(*mut *mut lean_object),
153}
154
155// -- Sealing: prevent downstream impls of LeanArgs / DecodeCallResult ----
156
157/// Private supertraits that seal [`LeanArgs`] and [`DecodeCallResult`]
158/// at the crate boundary.
159///
160/// Two distinct sealed traits (rather than one shared `Sealed`) because
161/// `()`, `(u64,)`, and other tuples implement [`TryFromLean`]—a single
162/// `Sealed` blanket-implemented over `T: TryFromLean` would overlap with
163/// any per-arity `Sealed` impl on tuples. Splitting the seal by which
164/// public trait it gates removes the overlap.
165mod sealed {
166 /// Sealed supertrait for [`super::LeanArgs`].
167 #[allow(unreachable_pub, reason = "sealed trait pattern—pub inside a pub(crate) module")]
168 pub trait SealedArgs {}
169 /// Sealed supertrait for [`super::DecodeCallResult`].
170 #[allow(unreachable_pub, reason = "sealed trait pattern—pub inside a pub(crate) module")]
171 pub trait SealedResult {}
172}
173
174// -- LeanArgs: arity marker on argument tuples ---------------------------
175
176/// Per-arity marker for [`LeanExported`]'s argument tuple.
177///
178/// Sealed via `SealedArgs`; downstream cannot implement it.
179/// Macro-stamped for arity-0..=12 tuples whose elements implement
180/// [`LeanAbi`]. Used at lookup time to reject `ARITY > 0` against a
181/// Lean nullary-constant global.
182pub trait LeanArgs<'lean>: Sized + sealed::SealedArgs {
183 /// Number of arguments the tuple represents.
184 const ARITY: usize;
185
186 /// ABI metadata for this argument tuple.
187 #[doc(hidden)]
188 fn export_abi_args() -> Vec<LeanExportArgAbi>;
189
190 /// Destructure `args` and dispatch through `handle`.
191 ///
192 /// The per-arity `.call(a1, a2, ...)` method on [`LeanExported`]
193 /// takes its arguments destructured (one per parameter) because
194 /// that is the natural ergonomic form for hand-written call sites.
195 /// Generic callers cannot destructure a generic `Args` tuple, so
196 /// they reach `.call(...)` through this associated function instead.
197 /// Macro-stamped per arity to forward to the existing destructured
198 /// impl.
199 #[doc(hidden)]
200 fn invoke<R>(handle: &LeanExported<'lean, '_, Self, R>, args: Self) -> LeanResult<R::Output>
201 where
202 R: DecodeCallResult<'lean>;
203}
204
205// -- DecodeCallResult: pure vs IO return decoding ------------------------
206
207/// How to decode an owned Lean call result into a Rust value.
208///
209/// Sealed via `SealedResult`; downstream cannot implement it.
210/// Two implementors:
211///
212/// - blanket `impl<T: LeanAbi<'lean>> DecodeCallResult<'lean> for T`—
213/// the *pure* path; `CRepr = T::CRepr`, `Output = T`; `decode_c` is
214/// `T::from_c(c, rt)`.
215/// - special `impl<T: TryFromLean<'lean>> DecodeCallResult<'lean> for
216/// LeanIo<T>`—the *IO* path; `CRepr = *mut lean_object` (the IO
217/// result wrapper), `Output = T`; `decode_c` wraps the pointer in
218/// `Obj`, runs `decode_io`, then `T::try_from_lean`.
219///
220/// Coherence holds because [`LeanIo<T>`] does not implement [`LeanAbi`],
221/// so the blanket impl does not match it.
222pub trait DecodeCallResult<'lean>: Sized + sealed::SealedResult {
223 /// What `.call(...)` returns inside `LeanResult`.
224 type Output;
225 /// The C-ABI return type of the Lake-emitted function. For the pure
226 /// path this is `T::CRepr` (e.g. `u8` for `Bool` exports, `*mut
227 /// lean_object` for `String`); for the IO path it is always
228 /// `*mut lean_object` (the `lean_io_result_*` wrapper).
229 type CRepr: Copy;
230 /// `true` iff this decoder expects a `lean_io_result_*` shape.
231 /// Used at lookup time to reject `LeanIo<_>` against a global symbol
232 /// (which is never IO-typed in Lean's compilation).
233 #[doc(hidden)]
234 const EXPECTS_IO_RESULT: bool;
235 /// ABI metadata for this return decoder.
236 #[doc(hidden)]
237 fn export_abi_return() -> LeanExportReturnAbi;
238 /// Decode the owned C-ABI return value into [`Output`](Self::Output).
239 ///
240 /// # Errors
241 ///
242 /// Returns whatever the underlying decoder returns—
243 /// [`LeanAbi::from_c`] for the pure path, `decode_io` chained into
244 /// `TryFromLean::try_from_lean` for the IO path.
245 #[doc(hidden)]
246 fn decode_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self::Output>;
247}
248
249impl<'lean, T> sealed::SealedResult for T where T: LeanAbi<'lean> {}
250impl<'lean, T> DecodeCallResult<'lean> for T
251where
252 T: LeanAbi<'lean>,
253{
254 type Output = T;
255 type CRepr = T::CRepr;
256 const EXPECTS_IO_RESULT: bool = false;
257 fn export_abi_return() -> LeanExportReturnAbi {
258 export_abi_return_for::<T::CRepr>(LeanExportResultConvention::Pure)
259 }
260 fn decode_c(c: T::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<T> {
261 T::from_c(c, runtime)
262 }
263}
264
265impl<T> sealed::SealedResult for LeanIo<T> {}
266impl<'lean, T> DecodeCallResult<'lean> for LeanIo<T>
267where
268 T: TryFromLean<'lean>,
269{
270 type Output = T;
271 type CRepr = *mut lean_object;
272 const EXPECTS_IO_RESULT: bool = true;
273 fn export_abi_return() -> LeanExportReturnAbi {
274 LeanExportReturnAbi::new(
275 LeanExportAbiRepr::LeanObject,
276 LeanExportOwnership::Owned,
277 LeanExportResultConvention::IoResult,
278 )
279 }
280 #[allow(
281 clippy::not_unsafe_ptr_arg_deref,
282 reason = "sealed trait—caller invariant documented on DecodeCallResult::decode_c"
283 )]
284 fn decode_c(c: *mut lean_object, runtime: &'lean LeanRuntime) -> LeanResult<T> {
285 // SAFETY: `c` is an owned `lean_io_result_*` returned by an
286 // extern Lean function; `runtime` witnesses `'lean`.
287 let result_obj = unsafe { Obj::from_owned_raw(runtime, c) };
288 let payload = decode_io(runtime, result_obj)?;
289 T::try_from_lean(payload)
290 }
291}
292
293pub(crate) fn export_abi_arg_for<T: LeanCReprAbi>() -> LeanExportArgAbi {
294 let repr = T::EXPORT_ABI_REPR;
295 LeanExportArgAbi::new(repr, ownership_for_repr(repr))
296}
297
298fn export_abi_return_for<T: LeanCReprAbi>(convention: LeanExportResultConvention) -> LeanExportReturnAbi {
299 let repr = T::EXPORT_ABI_REPR;
300 LeanExportReturnAbi::new(repr, ownership_for_repr(repr), convention)
301}
302
303fn ownership_for_repr(repr: LeanExportAbiRepr) -> LeanExportOwnership {
304 if repr == LeanExportAbiRepr::LeanObject {
305 LeanExportOwnership::Owned
306 } else {
307 LeanExportOwnership::None
308 }
309}
310
311// -- LeanExported<Args, R>: Debug impl + Global-path helper --------------
312
313impl<Args, R> core::fmt::Debug for LeanExported<'_, '_, Args, R> {
314 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
315 let kind = match self.target {
316 CallableTarget::Function(_) => "Function",
317 CallableTarget::Global(_) => "Global",
318 };
319 f.debug_struct("LeanExported").field("kind", &kind).finish()
320 }
321}
322
323/// Read a Lean nullary-constant global's persistent `*mut lean_object`,
324/// `lean_inc` it, and return the bumped pointer.
325///
326/// The same logic appears in the arity-0 stamped `.call` impl on the
327/// Global branch—extracted here so the per-arity macro stays small.
328///
329/// # Safety
330///
331/// `ptr` must point at a Lake-emitted persistent `lean_object*` slot
332/// (data-section export). The caller has verified this through the
333/// `globals` set at `LeanLibrary::open` time.
334unsafe fn read_global_pointer(ptr: *mut *mut lean_object) -> *mut lean_object {
335 // SAFETY: `ptr` is a Lake-installed persistent slot (per caller
336 // invariant). Reading the slot yields the persistent
337 // `lean_object*`; `lean_inc` bumps its refcount so the returned
338 // value owns a fresh reference that `Drop` can release.
339 unsafe {
340 let inner = *ptr;
341 lean_inc(inner);
342 inner
343 }
344}
345
346// -- LeanModule::exported_unchecked lookup -----------------------------------------
347
348// Both lifetimes flow into the returned `LeanExported<'lean, 'lib, ...>`.
349#[allow(single_use_lifetimes, reason = "'lean and 'lib both anchor the returned handle")]
350impl<'lean, 'lib> LeanModule<'lean, 'lib> {
351 /// Look up a typed handle for the named exported symbol without ABI
352 /// metadata checks.
353 ///
354 /// `Args` is a tuple of Rust argument types whose arity matches the
355 /// Lean export's parameter count (use `()` for nullary exports);
356 /// each element must implement [`LeanAbi`]. `R` is the return
357 /// decoder: either a [`LeanAbi`] type (pure path) or [`LeanIo<T>`]
358 /// for an `IO α`-returning export.
359 ///
360 /// # Safety
361 ///
362 /// The caller must prove that `name` resolves to a Lean export whose
363 /// emitted C ABI and ownership behavior exactly match the requested
364 /// Rust `Args` and `R`:
365 ///
366 /// - each argument slot must have the C representation selected by
367 /// that slot's [`LeanAbi::CRepr`], in the same order and arity;
368 /// - the return slot must have the C representation expected by
369 /// [`DecodeCallResult::CRepr`], including `LeanIo<T>` only for
370 /// exports that return Lean `IO α`;
371 /// - object arguments and results must follow Lake's ownership and
372 /// refcount transfer rules for the chosen signature.
373 ///
374 /// Passing a Rust signature that does not match the Lean export's
375 /// actual C ABI is undefined behavior.
376 ///
377 /// # Errors
378 ///
379 /// Returns [`LeanError::Host`] with stage [`HostStage::Link`] if:
380 ///
381 /// - the symbol is not exported by this module's library;
382 /// - the symbol is a Lean nullary-constant global (data-section
383 /// symbol) and `Args::ARITY > 0`—the function-vs-global mismatch
384 /// is diagnosed at lookup so the next `.call` cannot SIGBUS;
385 /// - the symbol is a Lean nullary-constant global and `R` is
386 /// [`LeanIo<_>`]—globals are never IO-returning in Lean, so the
387 /// decoder cannot apply.
388 pub unsafe fn exported_unchecked<Args, R>(&self, name: &str) -> LeanResult<LeanExported<'lean, 'lib, Args, R>>
389 where
390 Args: LeanArgs<'lean>,
391 R: DecodeCallResult<'lean>,
392 {
393 let library = self.library();
394 let target = if library.globals().contains(name) {
395 if Args::ARITY > 0 {
396 return Err(LeanError::symbol_lookup(format!(
397 "exported symbol '{}' in '{}' is a Lean nullary-constant global, not a function; \
398 look it up with `exported_unchecked::<(), R>(name)` instead (lookup arity is {})",
399 name,
400 library.path().display(),
401 Args::ARITY,
402 )));
403 }
404 if R::EXPECTS_IO_RESULT {
405 return Err(LeanError::symbol_lookup(format!(
406 "exported symbol '{}' in '{}' is a Lean nullary-constant global; \
407 `LeanIo<_>` does not apply (Lean does not compile globals as IO-returning)",
408 name,
409 library.path().display(),
410 )));
411 }
412 let ptr = library.resolve_global_symbol(name)?;
413 CallableTarget::Global(ptr)
414 } else {
415 let addr = library.resolve_function_symbol(name)?;
416 CallableTarget::Function(addr)
417 };
418 Ok(LeanExported {
419 target,
420 runtime: library.runtime(),
421 _life: PhantomData,
422 _args: PhantomData,
423 })
424 }
425}
426
427// -- impl_arity!: stamps LeanArgs + LeanExported::call for one arity -----
428
429/// Stamp the [`LeanArgs`] impl, the `SealedArgs` seal, and the
430/// per-arity [`LeanExported`] `.call` impl for one arity.
431///
432/// Invoked once per arity 0..=12. Each invocation spells out the
433/// per-slot `<$A as LeanAbi<'lean>>::CRepr` arguments in the function
434/// pointer signature—stable Rust has no token-counting expansion that
435/// would let us synthesise N copies of the type from a count.
436///
437/// The macro takes only the typed-arg list `(A1 a1, ..., AN aN)` plus
438/// the arity literal. The function-pointer type is constructed inline
439/// inside the `.call` body using the per-`A` associated types.
440macro_rules! impl_arity {
441 (
442 $arity:literal,
443 ($($A:ident $a:ident),* $(,)?)
444 ) => {
445 impl<$($A,)*> sealed::SealedArgs for ($($A,)*) {}
446
447 // The `'lean` parameter binds the per-arg `LeanAbi` bounds even
448 // when the tuple itself is arity 0 (no $A to use it explicitly).
449 #[allow(single_use_lifetimes, reason = "binds the LeanAbi<'lean> bounds")]
450 impl<'lean, $($A,)*> LeanArgs<'lean> for ($($A,)*)
451 where
452 $($A: LeanAbi<'lean>,)*
453 {
454 const ARITY: usize = $arity;
455
456 fn export_abi_args() -> Vec<LeanExportArgAbi> {
457 vec![$(export_abi_arg_for::<<$A as LeanAbi<'lean>>::CRepr>()),*]
458 }
459
460 #[allow(
461 clippy::unused_unit,
462 unused_variables,
463 reason = "arity 0 has no destructure target and no $A to bind"
464 )]
465 fn invoke<R>(
466 handle: &LeanExported<'lean, '_, Self, R>,
467 args: Self,
468 ) -> LeanResult<R::Output>
469 where
470 R: DecodeCallResult<'lean>,
471 {
472 let ($($a,)*) = args;
473 handle.call($($a,)*)
474 }
475 }
476
477 // Both lifetimes flow into LeanExported<'lean, 'lib, ...>.
478 #[allow(single_use_lifetimes, reason = "'lean and 'lib anchor LeanExported")]
479 impl<'lean, 'lib, $($A,)* R> LeanExported<'lean, 'lib, ($($A,)*), R>
480 where
481 $($A: LeanAbi<'lean>,)*
482 R: DecodeCallResult<'lean>,
483 {
484 /// Invoke the exported symbol and decode the result.
485 ///
486 /// # Errors
487 ///
488 /// Returns [`LeanError::LeanException`] when the underlying
489 /// Lean export raises through `IO` (only possible when `R`
490 /// is [`LeanIo<_>`]). Returns [`LeanError::Host`] with stage
491 /// [`HostStage::Conversion`] when the return value does not
492 /// decode into the declared `R` type.
493 #[allow(
494 clippy::unused_unit,
495 unused_variables,
496 reason = "arity 0 does not convert args or destructure them"
497 )]
498 pub fn call(&self, $($a: $A),*) -> LeanResult<R::Output> {
499 // Debug-only: catch worker threads that forgot to construct
500 // a `LeanThreadGuard` before invoking Lean code. Compiles
501 // to a no-op in release. See
502 // `docs/architecture/04-concurrency.md`.
503 crate::runtime::thread::debug_assert_attached("LeanExported::call");
504 let _span = tracing::trace_span!(
505 target: "lean_rs",
506 "lean_rs.module.exported.call",
507 arity = $arity,
508 )
509 .entered();
510 let runtime = self.runtime;
511 let raw_out: R::CRepr = match self.target {
512 CallableTarget::Function(addr) => {
513 // The function-pointer signature is per-arg
514 // `<$A as LeanAbi<'lean>>::CRepr` (an unboxed
515 // scalar or a `*mut lean_object`, depending on
516 // the type) and the return is `R::CRepr` (which
517 // is `T::CRepr` for the pure path and
518 // `*mut lean_object` for `LeanIo<T>`).
519 //
520 // SAFETY: lookup verified the symbol resolves to
521 // a function entry point in the loaded library;
522 // the tuple type carries the matching arity and
523 // per-arg CRepr so the transmute lines up with
524 // Lake's emitted C signature.
525 let f = unsafe {
526 core::mem::transmute::<
527 *mut c_void,
528 unsafe extern "C" fn(
529 $(<$A as LeanAbi<'lean>>::CRepr,)*
530 ) -> <R as DecodeCallResult<'lean>>::CRepr,
531 >(addr)
532 };
533 // Each $a: $A converts to its CRepr through
534 // LeanAbi::into_c, transferring ownership of any
535 // allocated Lean object.
536 $(let $a = $a.into_c(runtime);)*
537 // SAFETY: per-arg ownership transferred per
538 // Lake's `lean_obj_arg` contract; the return
539 // value owns one refcount (or is a scalar—no
540 // refcount obligation).
541 unsafe { f($($a,)*) }
542 }
543 CallableTarget::Global(ptr) => {
544 debug_assert_eq!(
545 <($($A,)*) as LeanArgs<'lean>>::ARITY,
546 0,
547 "arity > 0 against a global is rejected at lookup",
548 );
549 debug_assert!(
550 !<R as DecodeCallResult<'lean>>::EXPECTS_IO_RESULT,
551 "LeanIo<_> against a global is rejected at lookup",
552 );
553 // `R::CRepr` for the global path must be
554 // `*mut lean_object` because the pure-path
555 // blanket impl picks `T::CRepr`, and any boxed
556 // `T: LeanAbi` has `CRepr = *mut lean_object`.
557 // Globals always hold a `lean_object*`, so this
558 // alignment is structural. transmute_copy is
559 // sound because Rust statically guarantees both
560 // are pointer-sized when we reach this branch.
561 // SAFETY: pointer-sized scalar reinterpret;
562 // `read_global_pointer` returns a non-null
563 // `*mut lean_object` owning one refcount.
564 let raw: *mut lean_object = unsafe { read_global_pointer(ptr) };
565 // SAFETY: R::CRepr is the pointer-sized C ABI
566 // type the pure-path blanket assigned. For
567 // `Obj<'lean>`, `Option<T>`, `String`, etc.,
568 // this is `*mut lean_object` directly.
569 unsafe { core::mem::transmute_copy::<*mut lean_object, R::CRepr>(&raw) }
570 }
571 };
572 R::decode_c(raw_out, runtime)
573 }
574 }
575 };
576}
577
578impl_arity!(0, ());
579impl_arity!(1, (A1 a1));
580impl_arity!(2, (A1 a1, A2 a2));
581impl_arity!(3, (A1 a1, A2 a2, A3 a3));
582impl_arity!(4, (A1 a1, A2 a2, A3 a3, A4 a4));
583impl_arity!(5, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5));
584impl_arity!(6, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6));
585impl_arity!(7, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7));
586impl_arity!(8, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8));
587impl_arity!(9, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9));
588impl_arity!(10, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10));
589impl_arity!(11, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11));
590impl_arity!(
591 12,
592 (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12)
593);