#![allow(unsafe_code)]
#![allow(private_bounds, private_interfaces)]
use core::ffi::c_void;
use core::marker::PhantomData;
use lean_rs_sys::lean_object;
use lean_rs_sys::refcount::lean_inc;
use super::library::LeanLibrary;
use super::loaded::LeanModule;
use crate::abi::traits::{LeanAbi, TryFromLean};
#[cfg(doc)]
use crate::error::HostStage;
use crate::error::io::decode_io;
use crate::error::{LeanError, LeanResult};
use crate::runtime::LeanRuntime;
use crate::runtime::obj::Obj;
pub struct LeanExported<'lean, 'lib, Args, R> {
target: CallableTarget,
runtime: &'lean LeanRuntime,
_life: PhantomData<&'lib LeanLibrary<'lean>>,
_args: PhantomData<fn(Args) -> R>,
}
pub struct LeanIo<T>(PhantomData<fn() -> T>);
enum CallableTarget {
Function(*mut c_void),
Global(*mut *mut lean_object),
}
mod sealed {
#[allow(unreachable_pub, reason = "sealed trait pattern — pub inside a pub(crate) module")]
pub trait SealedArgs {}
#[allow(unreachable_pub, reason = "sealed trait pattern — pub inside a pub(crate) module")]
pub trait SealedResult {}
}
pub trait LeanArgs<'lean>: Sized + sealed::SealedArgs {
const ARITY: usize;
#[doc(hidden)]
fn invoke<R>(handle: &LeanExported<'lean, '_, Self, R>, args: Self) -> LeanResult<R::Output>
where
R: DecodeCallResult<'lean>;
}
pub trait DecodeCallResult<'lean>: Sized + sealed::SealedResult {
type Output;
type CRepr: Copy;
#[doc(hidden)]
const EXPECTS_IO_RESULT: bool;
#[doc(hidden)]
fn decode_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self::Output>;
}
impl<'lean, T> sealed::SealedResult for T where T: LeanAbi<'lean> {}
impl<'lean, T> DecodeCallResult<'lean> for T
where
T: LeanAbi<'lean>,
{
type Output = T;
type CRepr = T::CRepr;
const EXPECTS_IO_RESULT: bool = false;
fn decode_c(c: T::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<T> {
T::from_c(c, runtime)
}
}
impl<T> sealed::SealedResult for LeanIo<T> {}
impl<'lean, T> DecodeCallResult<'lean> for LeanIo<T>
where
T: TryFromLean<'lean>,
{
type Output = T;
type CRepr = *mut lean_object;
const EXPECTS_IO_RESULT: bool = true;
#[allow(
clippy::not_unsafe_ptr_arg_deref,
reason = "sealed trait — caller invariant documented on DecodeCallResult::decode_c"
)]
fn decode_c(c: *mut lean_object, runtime: &'lean LeanRuntime) -> LeanResult<T> {
let result_obj = unsafe { Obj::from_owned_raw(runtime, c) };
let payload = decode_io(runtime, result_obj)?;
T::try_from_lean(payload)
}
}
impl<Args, R> core::fmt::Debug for LeanExported<'_, '_, Args, R> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let kind = match self.target {
CallableTarget::Function(_) => "Function",
CallableTarget::Global(_) => "Global",
};
f.debug_struct("LeanExported").field("kind", &kind).finish()
}
}
impl<'lean, Args, R> LeanExported<'lean, '_, Args, R> {
pub unsafe fn from_function_address(runtime: &'lean LeanRuntime, address: *mut c_void) -> Self {
Self {
target: CallableTarget::Function(address),
runtime,
_life: PhantomData,
_args: PhantomData,
}
}
}
unsafe fn read_global_pointer(ptr: *mut *mut lean_object) -> *mut lean_object {
unsafe {
let inner = *ptr;
lean_inc(inner);
inner
}
}
#[allow(single_use_lifetimes, reason = "'lean and 'lib both anchor the returned handle")]
impl<'lean, 'lib> LeanModule<'lean, 'lib> {
pub fn exported<Args, R>(&self, name: &str) -> LeanResult<LeanExported<'lean, 'lib, Args, R>>
where
Args: LeanArgs<'lean>,
R: DecodeCallResult<'lean>,
{
let library = self.library();
let target = if library.globals().contains(name) {
if Args::ARITY > 0 {
return Err(LeanError::symbol_lookup(format!(
"exported symbol '{}' in '{}' is a Lean nullary-constant global, not a function; \
look it up with `exported::<(), R>(name)` instead (lookup arity is {})",
name,
library.path().display(),
Args::ARITY,
)));
}
if R::EXPECTS_IO_RESULT {
return Err(LeanError::symbol_lookup(format!(
"exported symbol '{}' in '{}' is a Lean nullary-constant global; \
`LeanIo<_>` does not apply (Lean does not compile globals as IO-returning)",
name,
library.path().display(),
)));
}
let ptr = library.resolve_global_symbol(name)?;
CallableTarget::Global(ptr)
} else {
let addr = library.resolve_function_symbol(name)?;
CallableTarget::Function(addr)
};
Ok(LeanExported {
target,
runtime: library.runtime(),
_life: PhantomData,
_args: PhantomData,
})
}
}
macro_rules! impl_arity {
(
$arity:literal,
($($A:ident $a:ident),* $(,)?)
) => {
impl<$($A,)*> sealed::SealedArgs for ($($A,)*) {}
#[allow(single_use_lifetimes, reason = "binds the LeanAbi<'lean> bounds")]
impl<'lean, $($A,)*> LeanArgs<'lean> for ($($A,)*)
where
$($A: LeanAbi<'lean>,)*
{
const ARITY: usize = $arity;
#[allow(
clippy::unused_unit,
unused_variables,
reason = "arity 0 has no destructure target and no $A to bind"
)]
fn invoke<R>(
handle: &LeanExported<'lean, '_, Self, R>,
args: Self,
) -> LeanResult<R::Output>
where
R: DecodeCallResult<'lean>,
{
let ($($a,)*) = args;
handle.call($($a,)*)
}
}
#[allow(single_use_lifetimes, reason = "'lean and 'lib anchor LeanExported")]
impl<'lean, 'lib, $($A,)* R> LeanExported<'lean, 'lib, ($($A,)*), R>
where
$($A: LeanAbi<'lean>,)*
R: DecodeCallResult<'lean>,
{
#[allow(
clippy::unused_unit,
unused_variables,
reason = "arity 0 does not convert args or destructure them"
)]
pub fn call(&self, $($a: $A),*) -> LeanResult<R::Output> {
crate::runtime::thread::debug_assert_attached("LeanExported::call");
let _span = tracing::trace_span!(
target: "lean_rs",
"lean_rs.module.exported.call",
arity = $arity,
)
.entered();
let runtime = self.runtime;
let raw_out: R::CRepr = match self.target {
CallableTarget::Function(addr) => {
let f = unsafe {
core::mem::transmute::<
*mut c_void,
unsafe extern "C" fn(
$(<$A as LeanAbi<'lean>>::CRepr,)*
) -> <R as DecodeCallResult<'lean>>::CRepr,
>(addr)
};
$(let $a = $a.into_c(runtime);)*
unsafe { f($($a,)*) }
}
CallableTarget::Global(ptr) => {
debug_assert_eq!(
<($($A,)*) as LeanArgs<'lean>>::ARITY,
0,
"arity > 0 against a global is rejected at lookup",
);
debug_assert!(
!<R as DecodeCallResult<'lean>>::EXPECTS_IO_RESULT,
"LeanIo<_> against a global is rejected at lookup",
);
let raw: *mut lean_object = unsafe { read_global_pointer(ptr) };
unsafe { core::mem::transmute_copy::<*mut lean_object, R::CRepr>(&raw) }
}
};
R::decode_c(raw_out, runtime)
}
}
};
}
impl_arity!(0, ());
impl_arity!(1, (A1 a1));
impl_arity!(2, (A1 a1, A2 a2));
impl_arity!(3, (A1 a1, A2 a2, A3 a3));
impl_arity!(4, (A1 a1, A2 a2, A3 a3, A4 a4));
impl_arity!(5, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5));
impl_arity!(6, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6));
impl_arity!(7, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7));
impl_arity!(8, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8));
impl_arity!(9, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9));
impl_arity!(10, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10));
impl_arity!(11, (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11));
impl_arity!(
12,
(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12)
);