use std::any::Any;
use std::fmt;
pub(crate) mod capture;
pub(crate) mod io;
pub(crate) mod panic;
pub(crate) mod redact;
#[cfg(test)]
mod tests;
pub use self::capture::{CapturedEvent, DIAGNOSTIC_CAPTURE_DEFAULT_CAPACITY, DiagnosticCapture};
pub const LEAN_ERROR_MESSAGE_LIMIT: usize = 4096;
pub type LeanResult<T> = Result<T, LeanError>;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum LeanError {
LeanException(LeanException),
Host(HostFailure),
}
impl LeanError {
#[must_use]
pub fn code(&self) -> LeanDiagnosticCode {
match self {
Self::LeanException(_) => LeanDiagnosticCode::LeanException,
Self::Host(failure) => failure.code,
}
}
pub(crate) fn runtime_init(message: impl Into<String>) -> Self {
Self::host(HostStage::RuntimeInit, LeanDiagnosticCode::RuntimeInit, message)
}
pub(crate) fn runtime_init_panic(payload: &(dyn Any + Send)) -> Self {
Self::runtime_init(render_panic_payload(payload))
}
pub(crate) fn runtime_init_unsupported_toolchain(active_version: &str) -> Self {
use std::fmt::Write as _;
let mut window = String::new();
for (i, t) in lean_rs_sys::SUPPORTED_TOOLCHAINS.iter().enumerate() {
if i > 0 {
window.push_str(", ");
}
let _ = write!(window, "{:?}", t.versions);
}
Self::runtime_init(format!(
"active Lean toolchain {active_version} is not in the supported window: {window}",
))
}
pub(crate) fn linking(message: impl Into<String>) -> Self {
Self::host(HostStage::Link, LeanDiagnosticCode::Linking, message)
}
pub(crate) fn module_init(message: impl Into<String>) -> Self {
Self::host(HostStage::Load, LeanDiagnosticCode::ModuleInit, message)
}
pub(crate) fn module_init_panic(payload: &(dyn Any + Send)) -> Self {
Self::module_init(render_panic_payload(payload))
}
pub(crate) fn symbol_lookup(message: impl Into<String>) -> Self {
Self::host(HostStage::Link, LeanDiagnosticCode::SymbolLookup, message)
}
pub(crate) fn abi_conversion(message: impl Into<String>) -> Self {
Self::host(HostStage::Conversion, LeanDiagnosticCode::AbiConversion, message)
}
pub(crate) fn lean_exception(kind: LeanExceptionKind, message: impl Into<String>) -> Self {
Self::LeanException(LeanException {
kind,
message: bound_message(message.into()),
})
}
pub(crate) fn callback_panic(payload: &(dyn Any + Send)) -> Self {
Self::host(
HostStage::CallbackPanic,
LeanDiagnosticCode::Internal,
render_panic_payload(payload),
)
}
fn host(stage: HostStage, code: LeanDiagnosticCode, message: impl Into<String>) -> Self {
Self::Host(HostFailure {
stage,
code,
message: bound_message(message.into()),
})
}
}
impl fmt::Display for LeanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LeanException(e) => write!(f, "lean-rs: {e}"),
Self::Host(e) => write!(f, "lean-rs: {e}"),
}
}
}
impl std::error::Error for LeanError {}
#[derive(Clone, Debug)]
pub struct LeanException {
kind: LeanExceptionKind,
message: String,
}
impl LeanException {
#[must_use]
pub fn kind(&self) -> LeanExceptionKind {
self.kind
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for LeanException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Lean threw {:?}: {}", self.kind, self.message)
}
}
#[derive(Clone, Debug)]
pub struct HostFailure {
stage: HostStage,
code: LeanDiagnosticCode,
message: String,
}
impl HostFailure {
#[must_use]
pub fn stage(&self) -> HostStage {
self.stage
}
#[must_use]
pub fn code(&self) -> LeanDiagnosticCode {
self.code
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for HostFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"host stage {:?} [{}]: {}",
self.stage,
self.code.as_str(),
self.message
)
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum HostStage {
RuntimeInit,
Conversion,
CallbackPanic,
Link,
Load,
Internal,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum LeanDiagnosticCode {
RuntimeInit,
Linking,
ModuleInit,
SymbolLookup,
AbiConversion,
LeanException,
Elaboration,
Unsupported,
Internal,
}
impl LeanDiagnosticCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::RuntimeInit => "lean_rs.runtime_init",
Self::Linking => "lean_rs.linking",
Self::ModuleInit => "lean_rs.module_init",
Self::SymbolLookup => "lean_rs.symbol_lookup",
Self::AbiConversion => "lean_rs.abi_conversion",
Self::LeanException => "lean_rs.lean_exception",
Self::Elaboration => "lean_rs.elaboration",
Self::Unsupported => "lean_rs.unsupported",
Self::Internal => "lean_rs.internal",
}
}
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::RuntimeInit => "Lean runtime initialization failed",
Self::Linking => "a linkable artefact was missing or mismatched",
Self::ModuleInit => "a capability dylib could not be opened or initialized",
Self::SymbolLookup => "a symbol was not present in the loaded dylib",
Self::AbiConversion => "an ABI conversion failed",
Self::LeanException => "Lean raised through its IO error channel",
Self::Elaboration => "term parsing or elaboration produced diagnostics",
Self::Unsupported => "the loaded capability does not expose the requested service",
Self::Internal => "a pub(crate) invariant tripped — likely a bug in lean-rs",
}
}
}
impl fmt::Display for LeanDiagnosticCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum LeanExceptionKind {
AlreadyExists,
OtherError,
ResourceBusy,
ResourceVanished,
UnsupportedOperation,
HardwareFault,
UnsatisfiedConstraints,
IllegalOperation,
ProtocolError,
TimeExpired,
Interrupted,
NoFileOrDirectory,
InvalidArgument,
PermissionDenied,
ResourceExhausted,
InappropriateType,
NoSuchThing,
UnexpectedEof,
UserError,
Other,
}
#[doc(hidden)]
pub fn bound_message(mut s: String) -> String {
if s.len() <= LEAN_ERROR_MESSAGE_LIMIT {
return s;
}
let mut cut = LEAN_ERROR_MESSAGE_LIMIT;
while cut > 0 && !s.is_char_boundary(cut) {
cut = cut.saturating_sub(1);
}
s.truncate(cut);
s
}
#[doc(hidden)]
pub fn host_module_init(message: impl Into<String>) -> LeanError {
LeanError::module_init(message)
}
fn render_panic_payload(payload: &(dyn Any + Send)) -> String {
if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_owned()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"panic payload was not a string".to_owned()
}
}