#![allow(unsafe_code)]
#![allow(private_bounds, private_interfaces)]
use core::cell::Cell;
use core::ffi::c_void;
use std::time::Instant;
use crate::host::capabilities::LeanCapabilities;
use crate::host::elaboration::{LeanElabFailure, LeanElabOptions};
use crate::host::evidence::{EvidenceStatus, LeanEvidence, LeanKernelOutcome, ProofSummary};
use crate::host::meta::{LeanMetaOptions, LeanMetaResponse, LeanMetaService};
use lean_rs::Obj;
use lean_rs::abi::traits::TryFromLean;
#[cfg(doc)]
use lean_rs::error::HostStage;
use lean_rs::error::LeanResult;
use lean_rs::module::{DecodeCallResult, LeanArgs, LeanExported, LeanIo, LeanLibrary};
use lean_rs::{LeanDeclaration, LeanExpr, LeanName};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct SessionStats {
pub ffi_calls: u64,
pub batch_items: u64,
pub elapsed_ns: u64,
}
pub(crate) struct SessionSymbols {
pub(crate) session_import: *mut c_void,
pub(crate) name_from_string: *mut c_void,
pub(crate) env_query_declaration: *mut c_void,
pub(crate) env_query_declarations_bulk: *mut c_void,
pub(crate) env_list_declarations: *mut c_void,
pub(crate) env_declaration_type: *mut c_void,
pub(crate) env_declaration_kind: *mut c_void,
pub(crate) env_declaration_name: *mut c_void,
pub(crate) elaborate: *mut c_void,
pub(crate) elaborate_bulk: *mut c_void,
pub(crate) kernel_check: *mut c_void,
pub(crate) check_evidence: *mut c_void,
pub(crate) evidence_summary: *mut c_void,
pub(crate) meta_infer_type: Option<*mut c_void>,
pub(crate) meta_whnf: Option<*mut c_void>,
pub(crate) meta_heartbeat_burn: Option<*mut c_void>,
}
impl SessionSymbols {
pub(crate) fn resolve(library: &LeanLibrary<'_>) -> LeanResult<Self> {
Ok(Self {
session_import: library.resolve_function_symbol("lean_rs_host_session_import")?,
name_from_string: library.resolve_function_symbol("lean_rs_host_name_from_string")?,
env_query_declaration: library.resolve_function_symbol("lean_rs_host_env_query_declaration")?,
env_query_declarations_bulk: library.resolve_function_symbol("lean_rs_host_env_query_declarations_bulk")?,
env_list_declarations: library.resolve_function_symbol("lean_rs_host_env_list_declarations")?,
env_declaration_type: library.resolve_function_symbol("lean_rs_host_env_declaration_type")?,
env_declaration_kind: library.resolve_function_symbol("lean_rs_host_env_declaration_kind")?,
env_declaration_name: library.resolve_function_symbol("lean_rs_host_env_declaration_name")?,
elaborate: library.resolve_function_symbol("lean_rs_host_elaborate")?,
elaborate_bulk: library.resolve_function_symbol("lean_rs_host_elaborate_bulk")?,
kernel_check: library.resolve_function_symbol("lean_rs_host_kernel_check")?,
check_evidence: library.resolve_function_symbol("lean_rs_host_check_evidence")?,
evidence_summary: library.resolve_function_symbol("lean_rs_host_evidence_summary")?,
meta_infer_type: library.resolve_optional_function_symbol("lean_rs_host_meta_infer_type"),
meta_whnf: library.resolve_optional_function_symbol("lean_rs_host_meta_whnf"),
meta_heartbeat_burn: library.resolve_optional_function_symbol("lean_rs_host_meta_heartbeat_burn"),
})
}
pub(crate) fn meta_address_by_name(&self, name: &str) -> Option<*mut c_void> {
match name {
"lean_rs_host_meta_infer_type" => self.meta_infer_type,
"lean_rs_host_meta_whnf" => self.meta_whnf,
"lean_rs_host_meta_heartbeat_burn" => self.meta_heartbeat_burn,
_ => None,
}
}
}
pub struct LeanSession<'lean, 'c> {
capabilities: &'c LeanCapabilities<'lean, 'c>,
environment: Obj<'lean>,
stats: Cell<SessionStats>,
}
impl<'lean, 'c> LeanSession<'lean, 'c> {
pub(crate) fn import(capabilities: &'c LeanCapabilities<'lean, 'c>, imports: &[&str]) -> LeanResult<Self> {
let _span = tracing::info_span!(
target: "lean_rs",
"lean_rs.host.session.import",
imports_len = imports.len(),
)
.entered();
let runtime = capabilities.host().runtime();
let address = capabilities.symbols().session_import;
let import_fn: LeanExported<'lean, '_, (Vec<String>, Vec<String>), LeanIo<Obj<'lean>>> =
unsafe { LeanExported::from_function_address(runtime, address) };
let project = capabilities.host().project();
let search_paths: Vec<String> = vec![
project.olean_search_path().to_string_lossy().into_owned(),
project.shim_olean_search_path()?.to_string_lossy().into_owned(),
];
let imports_owned: Vec<String> = imports.iter().map(|&s| s.to_owned()).collect();
let environment = import_fn.call(search_paths, imports_owned)?;
Ok(Self {
capabilities,
environment,
stats: Cell::new(SessionStats::default()),
})
}
pub(crate) fn from_environment(capabilities: &'c LeanCapabilities<'lean, 'c>, environment: Obj<'lean>) -> Self {
Self {
capabilities,
environment,
stats: Cell::new(SessionStats::default()),
}
}
pub(crate) fn into_environment(self) -> Obj<'lean> {
self.environment
}
#[must_use]
pub fn stats(&self) -> SessionStats {
self.stats.get()
}
fn record_call(&self, batch: u64, elapsed: std::time::Duration) {
let mut s = self.stats.get();
s.ffi_calls = s.ffi_calls.saturating_add(1);
s.batch_items = s.batch_items.saturating_add(batch);
s.elapsed_ns = s
.elapsed_ns
.saturating_add(u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX));
self.stats.set(s);
}
pub fn query_declaration(&mut self, name: &str) -> LeanResult<LeanDeclaration<'lean>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.query_declaration",
name = name,
)
.entered();
let name_handle = self.make_name(name)?;
let address = self.capabilities.symbols().env_query_declaration;
let query: LeanExported<'lean, '_, (Obj<'lean>, LeanName<'lean>), LeanIo<Option<LeanDeclaration<'lean>>>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = query.call(self.environment.clone(), name_handle);
self.record_call(0, t.elapsed());
match result? {
Some(decl) => Ok(decl),
None => Err(lean_rs::abi::traits::conversion_error(format!(
"declaration '{name}' not found in imported environment"
))),
}
}
pub fn list_declarations(&mut self) -> LeanResult<Vec<LeanName<'lean>>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.list_declarations",
)
.entered();
let address = self.capabilities.symbols().env_list_declarations;
let list: LeanExported<'lean, '_, (Obj<'lean>,), LeanIo<Vec<Obj<'lean>>>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let raw = list.call(self.environment.clone());
self.record_call(0, t.elapsed());
raw?.into_iter().map(LeanName::try_from_lean).collect()
}
pub fn declaration_type(&mut self, name: &str) -> LeanResult<Option<LeanExpr<'lean>>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.declaration_type",
name = name,
)
.entered();
let name_handle = self.make_name(name)?;
let address = self.capabilities.symbols().env_declaration_type;
let query: LeanExported<'lean, '_, (Obj<'lean>, LeanName<'lean>), LeanIo<Option<LeanExpr<'lean>>>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = query.call(self.environment.clone(), name_handle);
self.record_call(0, t.elapsed());
result
}
pub fn declaration_kind(&mut self, name: &str) -> LeanResult<String> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.declaration_kind",
name = name,
)
.entered();
let name_handle = self.make_name(name)?;
let address = self.capabilities.symbols().env_declaration_kind;
let query: LeanExported<'lean, '_, (Obj<'lean>, LeanName<'lean>), LeanIo<String>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = query.call(self.environment.clone(), name_handle);
self.record_call(0, t.elapsed());
result
}
pub fn declaration_name(&mut self, name: &str) -> LeanResult<String> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.declaration_name",
name = name,
)
.entered();
let name_handle = self.make_name(name)?;
let address = self.capabilities.symbols().env_declaration_name;
let query: LeanExported<'lean, '_, (Obj<'lean>, LeanName<'lean>), LeanIo<String>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = query.call(self.environment.clone(), name_handle);
self.record_call(0, t.elapsed());
result
}
pub fn elaborate(
&mut self,
source: &str,
expected_type: Option<&LeanExpr<'lean>>,
options: &LeanElabOptions,
) -> LeanResult<Result<LeanExpr<'lean>, LeanElabFailure>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.elaborate",
source_len = source.len(),
heartbeats = options.heartbeats(),
diagnostic_byte_limit = options.diagnostic_byte_limit_usize(),
)
.entered();
let address = self.capabilities.symbols().elaborate;
let call: LeanExported<
'lean,
'_,
(Obj<'lean>, &str, Option<LeanExpr<'lean>>, &str, &str, u64, usize),
LeanIo<Result<LeanExpr<'lean>, LeanElabFailure>>,
> = unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(
self.environment.clone(),
source,
expected_type.cloned(),
options.namespace_context_str(),
options.file_label_str(),
options.heartbeats(),
options.diagnostic_byte_limit_usize(),
);
self.record_call(0, t.elapsed());
result
}
pub fn kernel_check(&mut self, source: &str, options: &LeanElabOptions) -> LeanResult<LeanKernelOutcome<'lean>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.kernel_check",
source_len = source.len(),
heartbeats = options.heartbeats(),
diagnostic_byte_limit = options.diagnostic_byte_limit_usize(),
)
.entered();
let address = self.capabilities.symbols().kernel_check;
let call: LeanExported<
'lean,
'_,
(Obj<'lean>, &str, &str, &str, u64, usize),
LeanIo<LeanKernelOutcome<'lean>>,
> = unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(
self.environment.clone(),
source,
options.namespace_context_str(),
options.file_label_str(),
options.heartbeats(),
options.diagnostic_byte_limit_usize(),
);
self.record_call(0, t.elapsed());
result
}
pub fn check_evidence(&mut self, handle: &LeanEvidence<'lean>) -> LeanResult<EvidenceStatus> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.check_evidence",
)
.entered();
let address = self.capabilities.symbols().check_evidence;
let call: LeanExported<'lean, '_, (Obj<'lean>, LeanEvidence<'lean>), LeanIo<EvidenceStatus>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(self.environment.clone(), handle.clone());
self.record_call(0, t.elapsed());
result
}
pub fn summarize_evidence(&mut self, handle: &LeanEvidence<'lean>) -> LeanResult<ProofSummary> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.summarize_evidence",
)
.entered();
let address = self.capabilities.symbols().evidence_summary;
let call: LeanExported<'lean, '_, (Obj<'lean>, LeanEvidence<'lean>), LeanIo<ProofSummary>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(self.environment.clone(), handle.clone());
self.record_call(0, t.elapsed());
result
}
pub fn run_meta<Req, Resp>(
&mut self,
service: &LeanMetaService<Req, Resp>,
request: Req,
options: &LeanMetaOptions,
) -> LeanResult<LeanMetaResponse<Resp>>
where
Req: lean_rs::abi::traits::LeanAbi<'lean>,
Resp: TryFromLean<'lean>,
{
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.run_meta",
service = service.name(),
heartbeats = options.heartbeats(),
diagnostic_byte_limit = options.diagnostic_byte_limit_usize(),
)
.entered();
let Some(address) = self.capabilities.symbols().meta_address_by_name(service.name()) else {
let message = format!(
"meta service '{}' is not exported by the loaded capability",
service.name()
);
return Ok(LeanMetaResponse::Unsupported(LeanElabFailure::synthetic(
message,
"<host>".to_owned(),
)));
};
let call: LeanExported<'lean, '_, (Obj<'lean>, Req, u64, usize, u8), LeanIo<LeanMetaResponse<Resp>>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(
self.environment.clone(),
request,
options.heartbeats(),
options.diagnostic_byte_limit_usize(),
options.transparency_byte(),
);
self.record_call(0, t.elapsed());
result
}
pub fn query_declarations_bulk(&mut self, names: &[&str]) -> LeanResult<Vec<LeanDeclaration<'lean>>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.query_declarations_bulk",
batch_size = names.len(),
)
.entered();
if names.is_empty() {
return Ok(Vec::new());
}
let name_handles: Vec<LeanName<'lean>> = names.iter().map(|n| self.make_name(n)).collect::<LeanResult<_>>()?;
let address = self.capabilities.symbols().env_query_declarations_bulk;
let call: LeanExported<
'lean,
'_,
(Obj<'lean>, Vec<LeanName<'lean>>),
LeanIo<Vec<Option<LeanDeclaration<'lean>>>>,
> = unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = call.call(self.environment.clone(), name_handles);
let batch_len = u64::try_from(names.len()).unwrap_or(u64::MAX);
self.record_call(batch_len, t.elapsed());
let raw = result?;
let mut out: Vec<LeanDeclaration<'lean>> = Vec::with_capacity(raw.len());
for (slot, name) in raw.into_iter().zip(names.iter()) {
match slot {
Some(decl) => out.push(decl),
None => {
return Err(lean_rs::abi::traits::conversion_error(format!(
"declaration '{name}' not found in imported environment"
)));
}
}
}
Ok(out)
}
pub fn elaborate_bulk(
&mut self,
sources: &[&str],
options: &LeanElabOptions,
) -> LeanResult<Vec<Result<LeanExpr<'lean>, LeanElabFailure>>> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.elaborate_bulk",
batch_size = sources.len(),
heartbeats = options.heartbeats(),
diagnostic_byte_limit = options.diagnostic_byte_limit_usize(),
)
.entered();
if sources.is_empty() {
return Ok(Vec::new());
}
let address = self.capabilities.symbols().elaborate_bulk;
let call: LeanExported<
'lean,
'_,
(Obj<'lean>, Vec<String>, &str, &str, u64, usize),
LeanIo<Vec<Result<LeanExpr<'lean>, LeanElabFailure>>>,
> = unsafe { LeanExported::from_function_address(self.runtime(), address) };
let sources_owned: Vec<String> = sources.iter().map(|&s| s.to_owned()).collect();
let t = Instant::now();
let result = call.call(
self.environment.clone(),
sources_owned,
options.namespace_context_str(),
options.file_label_str(),
options.heartbeats(),
options.diagnostic_byte_limit_usize(),
);
let batch_len = u64::try_from(sources.len()).unwrap_or(u64::MAX);
self.record_call(batch_len, t.elapsed());
result
}
pub fn call_capability<Args, R>(&mut self, name: &str, args: Args) -> LeanResult<R::Output>
where
Args: LeanArgs<'lean>,
R: DecodeCallResult<'lean>,
{
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.host.session.call_capability",
symbol = name,
arity = Args::ARITY,
)
.entered();
let address = self.capabilities.library().resolve_function_symbol(name)?;
let call: LeanExported<'lean, '_, Args, R> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = Args::invoke(&call, args);
self.record_call(0, t.elapsed());
result
}
fn runtime(&self) -> &'lean lean_rs::LeanRuntime {
self.capabilities.host().runtime()
}
fn make_name(&self, name: &str) -> LeanResult<LeanName<'lean>> {
let address = self.capabilities.symbols().name_from_string;
let to_name: LeanExported<'lean, '_, (&str,), LeanName<'lean>> =
unsafe { LeanExported::from_function_address(self.runtime(), address) };
let t = Instant::now();
let result = to_name.call(name);
self.record_call(0, t.elapsed());
result
}
}