use std::ffi::c_void;
use crate::{arc, define_obj_type, ns, objc};
use super::objc_runtime::ExceptionName;
impl ExceptionName {
#[doc(alias = "NSGenericException")]
pub fn generic() -> &'static Self {
unsafe { NSGenericException }
}
#[doc(alias = "NSRangeException")]
pub fn range() -> &'static Self {
unsafe { NSRangeException }
}
#[doc(alias = "NSInvalidArgumentException")]
pub fn invalid_arg() -> &'static Self {
unsafe { NSInvalidArgumentException }
}
#[doc(alias = "NSInternalInconsistencyException")]
pub fn internal_inconsistency() -> &'static Self {
unsafe { NSInternalInconsistencyException }
}
#[doc(alias = "NSMallocException")]
pub fn malloc() -> &'static Self {
unsafe { NSMallocException }
}
#[doc(alias = "NSObjectInaccessibleException")]
pub fn object_inaccessible() -> &'static Self {
unsafe { NSObjectInaccessibleException }
}
#[doc(alias = "NSObjectNotAvailableException")]
pub fn object_not_available() -> &'static Self {
unsafe { NSObjectNotAvailableException }
}
#[doc(alias = "NSDestinationInvalidException")]
pub fn destination_invalid() -> &'static Self {
unsafe { NSDestinationInvalidException }
}
#[doc(alias = "NSPortTimeoutException")]
pub fn port_timeout() -> &'static Self {
unsafe { NSPortTimeoutException }
}
#[doc(alias = "NSInvalidSendPortException")]
pub fn invalid_send_port() -> &'static Self {
unsafe { NSInvalidSendPortException }
}
#[doc(alias = "NSInvalidReceivePortException")]
pub fn invalid_receive_port() -> &'static Self {
unsafe { NSInvalidReceivePortException }
}
#[doc(alias = "NSPortSendException")]
pub fn port_send() -> &'static Self {
unsafe { NSPortSendException }
}
#[doc(alias = "NSPortReceiveException")]
pub fn port_receive() -> &'static Self {
unsafe { NSPortReceiveException }
}
#[doc(alias = "NSOldStyleException")]
pub fn old_style() -> &'static Self {
unsafe { NSOldStyleException }
}
#[doc(alias = "NSInconsistentArchiveException")]
pub fn inconsisten_archive() -> &'static Self {
unsafe { NSInconsistentArchiveException }
}
}
unsafe extern "C" {
static NSGenericException: &'static ExceptionName;
static NSRangeException: &'static ExceptionName;
static NSInvalidArgumentException: &'static ExceptionName;
static NSInternalInconsistencyException: &'static ExceptionName;
static NSMallocException: &'static ExceptionName;
static NSObjectInaccessibleException: &'static ExceptionName;
static NSObjectNotAvailableException: &'static ExceptionName;
static NSDestinationInvalidException: &'static ExceptionName;
static NSPortTimeoutException: &'static ExceptionName;
static NSInvalidSendPortException: &'static ExceptionName;
static NSInvalidReceivePortException: &'static ExceptionName;
static NSPortSendException: &'static ExceptionName;
static NSPortReceiveException: &'static ExceptionName;
static NSOldStyleException: &'static ExceptionName;
static NSInconsistentArchiveException: &'static ExceptionName;
}
define_obj_type!(
#[doc(alias = "NSException")]
pub Exception(ns::Id)
);
impl Exception {
pub fn raise(message: &ns::String) -> ! {
unsafe { cidre_raise_exception(message) }
}
#[objc::msg_send(name)]
pub fn name(&self) -> arc::R<ns::ExceptionName>;
#[objc::msg_send(reason)]
pub fn reason(&self) -> Option<arc::R<ns::String>>;
#[objc::msg_send(userInfo)]
pub fn user_info(&self) -> Option<arc::R<ns::Dictionary<ns::Id, ns::Id>>>;
}
impl std::fmt::Display for Exception {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(reason) = self.reason() {
std::fmt::Display::fmt(&reason, f)
} else {
write!(f, "unknown exception")
}
}
}
pub type UncaughtExceptionHandler = extern "C" fn(exception: &Exception);
pub fn uncaught_exception_handler() -> *const UncaughtExceptionHandler {
unsafe { NSGetUncaughtExceptionHandler() }
}
pub unsafe fn set_uncaught_exception_handler(handler: *const UncaughtExceptionHandler) {
unsafe { NSSetUncaughtExceptionHandler(handler) }
}
unsafe extern "C" {
fn NSGetUncaughtExceptionHandler() -> *const UncaughtExceptionHandler;
fn NSSetUncaughtExceptionHandler(handler: *const UncaughtExceptionHandler);
}
#[link(name = "ns", kind = "static")]
unsafe extern "C-unwind" {
fn cidre_raise_exception(message: &ns::String) -> !;
fn cidre_try_catch<'ar>(
during: extern "C" fn(ctx: *mut c_void),
ctx: *mut c_void,
) -> Option<&'ar ns::Id>;
}
#[inline]
fn type_helper<F>(_t: &Option<F>) -> extern "C-unwind" fn(t: &mut Option<F>)
where
F: FnOnce(),
{
extern "C-unwind" fn during<F>(f: &mut Option<F>)
where
F: FnOnce(),
{
unsafe { f.take().unwrap_unchecked()() };
}
during
}
pub fn try_catch<'ar, F, R>(f: F) -> ns::ExResult<'ar, R>
where
F: FnOnce() -> R,
{
let mut result = None;
let mut wrapper = Some(|| result = Some(f()));
let f = type_helper(&wrapper);
let ctx = &mut wrapper as *mut _ as *mut c_void;
unsafe {
match cidre_try_catch(std::mem::transmute(f), ctx) {
None => Ok(result.unwrap_unchecked()),
Some(e) => Err(std::mem::transmute(e)),
}
}
}
impl<'ear> From<&'ear ns::Exception> for ns::ExErr<'ear> {
fn from(value: &'ear ns::Exception) -> Self {
Self::Ex(value)
}
}
pub fn try_catch_err<'ar, F, R>(f: F) -> Result<R, ns::ExErr<'ar>>
where
F: FnOnce() -> Result<R, &'ar ns::Error>,
{
Ok(try_catch(f)??)
}
#[cfg(test)]
mod tests {
use crate::{cf, ns, objc};
#[test]
fn catch() {
let x = ns::try_catch(|| 0).expect("result");
assert_eq!(0, x);
}
#[test]
fn ns_exception_catch() {
let reason = ns::str!(c"test");
let ex = ns::try_catch(|| ns::Exception::raise(reason)).expect_err("result");
assert!(ex.user_info().is_none());
assert!(ex.name().eq(ns::ExceptionName::generic()));
assert!(ex.reason().unwrap().eq(reason));
assert_ne!(cf::String::type_id(), ex.as_type_ref().get_type_id());
println!("{:?} {:?}", ex, ex.as_type_ref().retain_count());
}
#[test]
fn objc_throw_catch() {
let msg = ns::str!(c"this is longer string so it is not tagged ptr");
let exc = objc::try_catch(|| objc::throw(msg)).expect_err("result");
assert_eq!(cf::String::type_id(), exc.as_type_ref().get_type_id());
assert!(msg.is_equal(&exc));
println!("{:?} {:?}", exc, exc.as_type_ref().retain_count());
}
}