use core::fmt;
use core::hint::unreachable_unchecked;
use core::panic::{RefUnwindSafe, UnwindSafe};
use objc2::exception::Exception;
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{msg_send, msg_send_id, sel};
use crate::{NSCopying, NSDictionary, NSObject, NSString};
extern_class! {
#[derive(PartialEq, Eq, Hash)]
unsafe pub struct NSException: NSObject;
}
unsafe impl Sync for NSException {}
unsafe impl Send for NSException {}
impl UnwindSafe for NSException {}
impl RefUnwindSafe for NSException {}
type NSExceptionName = NSString;
impl NSException {
pub fn new(
name: &NSExceptionName,
reason: Option<&NSString>,
user_info: Option<&NSDictionary<Object, Object>>,
) -> Option<Id<Self, Shared>> {
let obj = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] }
}
pub unsafe fn raise(&self) -> ! {
let _: () = unsafe { msg_send![self, raise] };
unsafe { unreachable_unchecked() }
}
pub fn name(&self) -> Id<NSExceptionName, Shared> {
unsafe { msg_send_id![self, name].unwrap() }
}
pub fn reason(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, reason] }
}
pub fn user_info(&self) -> Option<Id<NSDictionary<Object, Object>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}
pub fn into_exception(this: Id<Self, Shared>) -> Id<Exception, Shared> {
unsafe { Id::cast(this) }
}
pub fn from_exception(
obj: Id<Exception, Shared>,
) -> Result<Id<Self, Shared>, Id<Exception, Shared>> {
if obj.class().responds_to(sel!(isKindOfClass:)) {
let obj = unsafe { Id::cast::<NSObject>(obj) };
if obj.is_kind_of(Self::class()) {
Ok(unsafe { Id::cast::<Self>(obj) })
} else {
Err(unsafe { Id::cast(obj) })
}
} else {
Err(obj)
}
}
}
unsafe impl NSCopying for NSException {
type Ownership = Shared;
type Output = NSException;
}
impl alloc::borrow::ToOwned for NSException {
type Owned = Id<NSException, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Debug for NSException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let obj: &Object = self.as_ref();
write!(f, "{:?} '{}'", obj, self.name())?;
if let Some(reason) = self.reason() {
write!(f, " reason:{}", reason)?;
} else {
write!(f, " reason:(NULL)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn create_and_query() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
assert_eq!(exc.name(), NSString::from_str("abc"));
assert_eq!(exc.reason().unwrap(), NSString::from_str("def"));
assert!(exc.user_info().is_none());
let description = if cfg!(feature = "gnustep-1-7") {
format!("<NSException: {:p}> NAME:abc REASON:def", exc)
} else {
"def".into()
};
assert_eq!(exc.description(), NSString::from_str(&description));
let debug = format!("<NSException: {:p}> 'abc' reason:def", exc);
assert_eq!(format!("{:?}", exc), debug);
}
#[test]
#[should_panic = "'abc' reason:def"]
fn unwrap() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
let _: () = Err(exc).unwrap();
}
}