use core::ffi::c_void;
use core::mem;
use core::ptr;
use core::ptr::NonNull;
use std::os::raw::c_uchar;
use crate::ffi;
use crate::rc::{Id, Shared};
use crate::runtime::Object;
extern "C" {
fn rust_objc_try_catch_exception(
f: extern "C" fn(*mut c_void),
context: *mut c_void,
error: *mut *mut ffi::objc_object,
) -> c_uchar;
}
#[inline]
pub unsafe fn throw(exception: Option<&Id<Object, Shared>>) -> ! {
let exception = match exception {
Some(id) => &**id as *const Object as *mut ffi::objc_object,
None => ptr::null_mut(),
};
unsafe { ffi::objc_exception_throw(exception) }
}
unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Shared>>> {
extern "C" fn try_objc_execute_closure<F: FnOnce()>(closure: &mut Option<F>) {
let closure = closure.take().unwrap();
closure();
}
let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C" fn(*mut c_void) = unsafe { mem::transmute(f) };
let mut closure = Some(closure);
let context = &mut closure as *mut _ as *mut c_void;
let mut exception = ptr::null_mut();
let success = unsafe { rust_objc_try_catch_exception(f, context, &mut exception) };
if success == 0 {
Ok(())
} else {
Err(NonNull::new(exception as *mut Object).map(|e| unsafe { Id::new(e) }))
}
}
pub unsafe fn catch<R>(closure: impl FnOnce() -> R) -> Result<R, Option<Id<Object, Shared>>> {
let mut value = None;
let value_ref = &mut value;
let closure = move || {
*value_ref = Some(closure());
};
let result = unsafe { try_no_ret(closure) };
result.map(|_| value.unwrap())
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_catch() {
let mut s = "Hello".to_string();
let result = unsafe {
catch(move || {
s.push_str(", World!");
s
})
};
assert_eq!(result.unwrap(), "Hello, World!");
}
#[test]
#[cfg(not(all(target_os = "macos", target_arch = "x86")))]
fn test_throw_catch_none() {
let s = "Hello".to_string();
let result = unsafe {
catch(move || {
if !s.is_empty() {
throw(None);
}
s.len()
})
};
assert!(result.unwrap_err().is_none());
}
#[test]
fn test_throw_catch_object() {
let obj: Id<Object, Shared> = unsafe { Id::new(msg_send![class!(NSObject), new]) };
let result = unsafe { catch(|| throw(Some(&obj))) };
let e = result.unwrap_err().unwrap();
assert_eq!(&*e as *const Object, &*obj as *const Object);
}
}