use core::ffi::CStr;
use std::sync::OnceLock;
use crate::ffi::NSInteger;
use crate::rc::{autoreleasepool, Retained};
use crate::runtime::{AnyClass, NSObject};
use crate::{msg_send, ClassType};
#[cold]
pub(crate) unsafe fn encountered_error<E: ClassType>(err: *mut E) -> Retained<E> {
unsafe { Retained::retain(err) }.unwrap_or_else(|| {
let err = null_error();
assert!(E::IS_NSERROR_COMPATIBLE);
unsafe { Retained::cast_unchecked(err) }
})
}
const fn is_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
trait IsNSError {
const IS_NSERROR_COMPATIBLE: bool;
}
impl<T: ClassType> IsNSError for T {
const IS_NSERROR_COMPATIBLE: bool = {
if is_eq(T::NAME, "NSError") || is_eq(T::NAME, "NSObject") {
true
} else {
panic!("error parameter must be either `NSError` or `NSObject`")
}
};
}
#[cold] fn null_error() -> Retained<NSObject> {
static CACHED_NULL_ERROR: OnceLock<NSErrorWrapper> = OnceLock::new();
CACHED_NULL_ERROR.get_or_init(create_null_error).0.clone()
}
struct NSErrorWrapper(Retained<NSObject>);
unsafe impl Send for NSErrorWrapper {}
unsafe impl Sync for NSErrorWrapper {}
#[cold] fn create_null_error() -> NSErrorWrapper {
autoreleasepool(|_| {
let cls = unsafe { CStr::from_bytes_with_nul_unchecked(b"NSString\0") };
let cls = AnyClass::get(cls).unwrap_or_else(foundation_not_linked);
let domain = unsafe { CStr::from_bytes_with_nul_unchecked(b"__objc2.missingError\0") };
let domain: Retained<NSObject> =
unsafe { msg_send![cls, stringWithUTF8String: domain.as_ptr()] };
let cls = unsafe { CStr::from_bytes_with_nul_unchecked(b"NSError\0") };
let cls = AnyClass::get(cls).unwrap_or_else(foundation_not_linked);
let domain: &NSObject = &domain;
let code: NSInteger = 0;
let user_info: Option<&NSObject> = None;
let err: Retained<NSObject> =
unsafe { msg_send![cls, errorWithDomain: domain, code: code, userInfo: user_info] };
NSErrorWrapper(err)
})
}
fn foundation_not_linked() -> &'static AnyClass {
panic!("Foundation must be linked to get a proper error message on NULL errors")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_eq() {
assert!(is_eq("NSError", "NSError"));
assert!(!is_eq("nserror", "NSError"));
assert!(!is_eq("CFError", "NSError"));
assert!(!is_eq("NSErr", "NSError"));
assert!(!is_eq("NSErrorrrr", "NSError"));
}
#[test]
fn test_create() {
let _ = create_null_error().0;
}
}