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