use core::ptr::{self, NonNull};
use crate::encode::{Encode, RefEncode};
use crate::rc::{Allocated, PartialInit, Retained};
use crate::runtime::{AnyClass, AnyObject, Sel};
use crate::{sel, ClassType, DeclaredClass, Message};
use super::declared_ivars::set_finalized;
use super::{Alloc, ConvertArguments, CopyOrMutCopy, Init, MsgSend, New, Other, TupleExtender};
pub trait MsgSendId<T, U> {
#[track_caller]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = U>>(
obj: T,
sel: Sel,
args: A,
) -> R;
#[inline]
#[track_caller]
unsafe fn send_message_id_error<A, E, R>(obj: T, sel: Sel, args: A) -> Result<R, Retained<E>>
where
*mut *mut E: Encode,
A: TupleExtender<*mut *mut E>,
<A as TupleExtender<*mut *mut E>>::PlusOneArgument: ConvertArguments,
E: Message,
Option<R>: MaybeUnwrap<Input = U>,
{
let mut err: *mut E = ptr::null_mut();
let args = args.add_argument(&mut err);
let res: Option<R> = unsafe { Self::send_message_id(obj, sel, args) };
if let Some(res) = res {
Ok(res)
} else {
Err(unsafe { encountered_error(err) })
}
}
}
#[doc(hidden)]
pub trait MsgSendSuperId<T, U> {
type Inner: ?Sized + RefEncode;
unsafe fn send_super_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = U>>(
obj: T,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R;
#[inline]
#[track_caller]
unsafe fn send_super_message_id_static<A: ConvertArguments, R: MaybeUnwrap<Input = U>>(
obj: T,
sel: Sel,
args: A,
) -> R
where
Self::Inner: ClassType,
<Self::Inner as ClassType>::Super: ClassType,
{
unsafe {
Self::send_super_message_id(obj, <Self::Inner as ClassType>::Super::class(), sel, args)
}
}
#[inline]
#[track_caller]
unsafe fn send_super_message_id_error<A, E, R>(
obj: T,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> Result<R, Retained<E>>
where
*mut *mut E: Encode,
A: TupleExtender<*mut *mut E>,
<A as TupleExtender<*mut *mut E>>::PlusOneArgument: ConvertArguments,
E: Message,
Option<R>: MaybeUnwrap<Input = U>,
{
let mut err: *mut E = ptr::null_mut();
let args = args.add_argument(&mut err);
let res: Option<R> = unsafe { Self::send_super_message_id(obj, superclass, sel, args) };
if let Some(res) = res {
Ok(res)
} else {
Err(unsafe { encountered_error(err) })
}
}
#[inline]
#[track_caller]
unsafe fn send_super_message_id_static_error<A, E, R>(
obj: T,
sel: Sel,
args: A,
) -> Result<R, Retained<E>>
where
Self::Inner: ClassType,
<Self::Inner as ClassType>::Super: ClassType,
*mut *mut E: Encode,
A: TupleExtender<*mut *mut E>,
<A as TupleExtender<*mut *mut E>>::PlusOneArgument: ConvertArguments,
E: Message,
Option<R>: MaybeUnwrap<Input = U>,
{
let mut err: *mut E = ptr::null_mut();
let args = args.add_argument(&mut err);
let res: Option<R> = unsafe { Self::send_super_message_id_static(obj, sel, args) };
if let Some(res) = res {
Ok(res)
} else {
Err(unsafe { encountered_error(err) })
}
}
}
#[cold]
#[track_caller]
unsafe fn encountered_error<E: Message>(err: *mut E) -> Retained<E> {
unsafe { Retained::retain(err) }
.expect("error parameter should be set if the method returns NULL")
}
impl<T: MsgSend, U: ?Sized + Message> MsgSendId<T, Option<Retained<U>>> for New {
#[inline]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Option<Retained<U>>>>(
obj: T,
sel: Sel,
args: A,
) -> R {
let ptr = obj.into_raw_receiver();
let obj = unsafe { MsgSend::send_message(ptr, sel, args) };
let obj = unsafe { Retained::from_raw(obj) };
R::maybe_unwrap::<Self>(obj, (unsafe { ptr.as_ref() }, sel))
}
}
impl<T: MsgSend, U: ?Sized + Message> MsgSendSuperId<T, Option<Retained<U>>> for New {
type Inner = T::Inner;
#[inline]
unsafe fn send_super_message_id<
A: ConvertArguments,
R: MaybeUnwrap<Input = Option<Retained<U>>>,
>(
obj: T,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R {
let ptr = obj.into_raw_receiver();
let obj = unsafe { MsgSend::send_super_message(ptr, superclass, sel, args) };
let obj = unsafe { Retained::from_raw(obj) };
R::maybe_unwrap::<Self>(obj, (unsafe { ptr.as_ref() }, sel))
}
}
impl<T: Message> MsgSendId<&'_ AnyClass, Allocated<T>> for Alloc {
#[inline]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Allocated<T>>>(
cls: &AnyClass,
sel: Sel,
args: A,
) -> R {
let obj = unsafe { MsgSend::send_message(cls, sel, args) };
let obj = unsafe { Allocated::new(obj) };
R::maybe_unwrap::<Self>(obj, ())
}
}
impl<T: ?Sized + Message> MsgSendSuperId<&'_ AnyClass, Allocated<T>> for Alloc {
type Inner = AnyClass;
#[inline]
unsafe fn send_super_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Allocated<T>>>(
cls: &AnyClass,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R {
let obj = unsafe { MsgSend::send_super_message(cls, superclass, sel, args) };
let obj = unsafe { Allocated::new(obj) };
R::maybe_unwrap::<Self>(obj, ())
}
}
impl Alloc {
#[inline]
pub unsafe fn send_message_id_alloc<T: Message, R: MaybeUnwrap<Input = Allocated<T>>>(
cls: &AnyClass,
) -> R {
#[cfg(all(
target_vendor = "apple",
not(all(target_os = "macos", target_arch = "x86"))
))]
{
let obj: *mut T = unsafe { crate::ffi::objc_alloc(cls.as_ptr()).cast() };
let obj = unsafe { Allocated::new(obj) };
R::maybe_unwrap::<Alloc>(obj, ())
}
#[cfg(not(all(
target_vendor = "apple",
not(all(target_os = "macos", target_arch = "x86"))
)))]
{
unsafe { Alloc::send_message_id(cls, sel!(alloc), ()) }
}
}
}
impl<T: ?Sized + Message> MsgSendId<Allocated<T>, Option<Retained<T>>> for Init {
#[inline]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Option<Retained<T>>>>(
obj: Allocated<T>,
sel: Sel,
args: A,
) -> R {
let ptr = Allocated::into_ptr(obj);
let obj = unsafe { MsgSend::send_message(ptr, sel, args) };
let obj = unsafe { Retained::from_raw(obj) };
R::maybe_unwrap::<Self>(obj, (ptr.cast(), sel))
}
}
impl<T: DeclaredClass> MsgSendSuperId<PartialInit<T>, Option<Retained<T>>> for Init {
type Inner = T;
#[inline]
unsafe fn send_super_message_id<
A: ConvertArguments,
R: MaybeUnwrap<Input = Option<Retained<T>>>,
>(
obj: PartialInit<T>,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R {
let ptr = PartialInit::into_ptr(obj);
let ptr = unsafe { MsgSend::send_super_message(ptr, superclass, sel, args) };
if let Some(ptr) = NonNull::new(ptr) {
unsafe { set_finalized(ptr) };
}
let obj = unsafe { Retained::from_raw(ptr) };
R::maybe_unwrap::<Self>(obj, (ptr.cast(), sel))
}
}
impl<T: MsgSend, U: ?Sized + Message> MsgSendId<T, Option<Retained<U>>> for CopyOrMutCopy {
#[inline]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Option<Retained<U>>>>(
obj: T,
sel: Sel,
args: A,
) -> R {
let obj = unsafe { MsgSend::send_message(obj, sel, args) };
let obj = unsafe { Retained::from_raw(obj) };
R::maybe_unwrap::<Self>(obj, ())
}
}
impl<T: MsgSend, U: ?Sized + Message> MsgSendSuperId<T, Option<Retained<U>>> for CopyOrMutCopy {
type Inner = T::Inner;
#[inline]
unsafe fn send_super_message_id<
A: ConvertArguments,
R: MaybeUnwrap<Input = Option<Retained<U>>>,
>(
obj: T,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R {
let obj = unsafe { MsgSend::send_super_message(obj, superclass, sel, args) };
let obj = unsafe { Retained::from_raw(obj) };
R::maybe_unwrap::<Self>(obj, ())
}
}
impl<T: MsgSend, U: Message> MsgSendId<T, Option<Retained<U>>> for Other {
#[inline]
unsafe fn send_message_id<A: ConvertArguments, R: MaybeUnwrap<Input = Option<Retained<U>>>>(
obj: T,
sel: Sel,
args: A,
) -> R {
let ptr = obj.into_raw_receiver();
let obj = unsafe { MsgSend::send_message(ptr, sel, args) };
let obj = unsafe { Retained::retain_autoreleased(obj) };
R::maybe_unwrap::<Self>(obj, (unsafe { ptr.as_ref() }, sel))
}
}
impl<T: MsgSend, U: Message> MsgSendSuperId<T, Option<Retained<U>>> for Other {
type Inner = T::Inner;
#[inline]
unsafe fn send_super_message_id<
A: ConvertArguments,
R: MaybeUnwrap<Input = Option<Retained<U>>>,
>(
obj: T,
superclass: &AnyClass,
sel: Sel,
args: A,
) -> R {
let ptr = obj.into_raw_receiver();
let obj = unsafe { MsgSend::send_super_message(ptr, superclass, sel, args) };
let obj = unsafe { Retained::retain_autoreleased(obj) };
R::maybe_unwrap::<Self>(obj, (unsafe { ptr.as_ref() }, sel))
}
}
pub trait MaybeUnwrap {
type Input;
#[track_caller]
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Self::Input, args: F::Args) -> Self;
}
impl<T: ?Sized> MaybeUnwrap for Option<Retained<T>> {
type Input = Option<Retained<T>>;
#[inline]
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Retained<T>>, _args: F::Args) -> Self {
obj
}
}
impl<T: ?Sized> MaybeUnwrap for Retained<T> {
type Input = Option<Retained<T>>;
#[inline]
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Retained<T>>, args: F::Args) -> Self {
match obj {
Some(obj) => obj,
None => F::failed(args),
}
}
}
impl<T: ?Sized> MaybeUnwrap for Allocated<T> {
type Input = Allocated<T>;
#[inline]
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Allocated<T>, _args: F::Args) -> Self {
obj
}
}
pub trait MsgSendIdFailed<'a> {
type Args;
#[track_caller]
fn failed(args: Self::Args) -> !;
}
impl<'a> MsgSendIdFailed<'a> for New {
type Args = (Option<&'a AnyObject>, Sel);
#[cold]
fn failed((obj, sel): Self::Args) -> ! {
if let Some(obj) = obj {
let cls = obj.class();
if cls.is_metaclass() {
if sel == sel!(new) {
panic!("failed creating new instance of {cls}")
} else {
panic!("failed creating new instance using +[{cls} {sel}]")
}
} else {
panic!("unexpected NULL returned from -[{cls} {sel}]")
}
} else {
panic!("unexpected NULL {sel}; receiver was NULL");
}
}
}
impl<'a> MsgSendIdFailed<'a> for Alloc {
type Args = ();
#[cold]
fn failed(_: Self::Args) -> ! {
unreachable!()
}
}
impl MsgSendIdFailed<'_> for Init {
type Args = (*mut AnyObject, Sel);
#[cold]
fn failed((ptr, sel): Self::Args) -> ! {
if ptr.is_null() {
panic!("failed allocating object")
} else {
if sel == sel!(init) {
panic!("failed initializing object")
} else {
panic!("failed initializing object with -{sel}")
}
}
}
}
impl MsgSendIdFailed<'_> for CopyOrMutCopy {
type Args = ();
#[cold]
fn failed(_: Self::Args) -> ! {
panic!("failed copying object")
}
}
impl<'a> MsgSendIdFailed<'a> for Other {
type Args = (Option<&'a AnyObject>, Sel);
#[cold]
fn failed((obj, sel): Self::Args) -> ! {
if let Some(obj) = obj {
let cls = obj.class();
panic!(
"unexpected NULL returned from {}[{cls} {sel}]",
if cls.is_metaclass() { "+" } else { "-" },
)
} else {
panic!("unexpected NULL {sel}; receiver was NULL");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
use crate::runtime::{NSObject, NSZone};
use crate::{class, msg_send_id};
mod test_trait_disambugated {
use super::*;
#[allow(dead_code)]
trait Abc {
fn send_message_id(&self) {}
}
impl<T> Abc for T {}
#[test]
fn test_macro_still_works() {
let _: Retained<NSObject> = unsafe { msg_send_id![NSObject::class(), new] };
}
}
#[test]
fn test_new() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![cls, new] };
let _obj: Option<Retained<AnyObject>> = unsafe { msg_send_id![cls, new] };
let _obj: Retained<AnyObject> = unsafe { msg_send_id![super(cls, cls.metaclass()), new] };
let _obj: Option<Retained<AnyObject>> =
unsafe { msg_send_id![super(cls, cls.metaclass()), new] };
let _obj: Retained<RcTestObject> =
unsafe { msg_send_id![super(cls, NSObject::class().metaclass()), new] };
expected.alloc += 5;
expected.init += 5;
expected.assert_current();
}
#[test]
fn test_new_not_on_class() {
let mut expected = ThreadTestData::current();
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![&obj, newMethodOnInstance] };
let _obj: Option<Retained<AnyObject>> = unsafe { msg_send_id![&obj, newMethodOnInstance] };
let _obj: Retained<AnyObject> =
unsafe { msg_send_id![super(&obj, RcTestObject::class()), newMethodOnInstance] };
let _obj: Option<Retained<AnyObject>> =
unsafe { msg_send_id![super(&obj, RcTestObject::class()), newMethodOnInstance] };
expected.alloc += 4;
expected.init += 4;
expected.assert_current();
}
#[test]
#[cfg_attr(not(all(target_vendor = "apple", target_os = "macos")), ignore)]
fn test_new_with_args() {
let mut expected = ThreadTestData::current();
let object_class = RcTestObject::class();
let key: Retained<AnyObject> = unsafe { msg_send_id![class!(NSString), new] };
let contents_value: *const AnyObject = ptr::null();
let properties: Retained<AnyObject> = unsafe { msg_send_id![class!(NSDictionary), new] };
let _obj: Option<Retained<AnyObject>> = unsafe {
msg_send_id![
NSObject::class(),
newScriptingObjectOfClass: object_class,
forValueForKey: &*key,
withContentsValue: contents_value,
properties: &*properties,
]
};
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
}
#[test]
#[should_panic = "failed creating new instance of NSValue"]
#[cfg_attr(feature = "gnustep-1-7", ignore)]
fn new_nsvalue_fails() {
let _val: Retained<AnyObject> = unsafe { msg_send_id![class!(NSValue), new] };
}
#[test]
#[should_panic = "failed creating new instance using +[__RcTestObject newReturningNull]"]
fn test_new_with_null() {
let _obj: Retained<RcTestObject> =
unsafe { msg_send_id![RcTestObject::class(), newReturningNull] };
}
#[test]
#[should_panic = "failed creating new instance using +[__RcTestObject newReturningNull]"]
fn test_super_new_with_null() {
let _: Retained<RcTestObject> = unsafe {
msg_send_id![
super(RcTestObject::class(), RcTestObject::class().metaclass()),
newReturningNull
]
};
}
#[test]
#[should_panic = "unexpected NULL returned from -[__RcTestObject newMethodOnInstanceNull]"]
fn test_new_any_with_null() {
let obj = RcTestObject::new();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![&obj, newMethodOnInstanceNull] };
}
#[test]
#[should_panic = "unexpected NULL returned from -[__RcTestObject newMethodOnInstanceNull]"]
fn test_super_new_any_with_null() {
let obj = RcTestObject::new();
let _obj: Retained<AnyObject> =
unsafe { msg_send_id![super(&obj, RcTestObject::class()), newMethodOnInstanceNull] };
}
#[test]
#[cfg_attr(
debug_assertions,
should_panic = "messsaging newMethodOnInstance to nil"
)]
#[cfg_attr(
not(debug_assertions),
ignore = "unexpected NULL newMethodOnInstance; receiver was NULL"
)]
fn test_new_any_with_null_receiver() {
let obj: *const NSObject = ptr::null();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![obj, newMethodOnInstance] };
}
#[test]
#[cfg_attr(
debug_assertions,
should_panic = "messsaging newMethodOnInstance to nil"
)]
#[cfg_attr(
not(debug_assertions),
ignore = "unexpected NULL newMethodOnInstance; receiver was NULL"
)]
fn test_super_new_any_with_null_receiver() {
let obj: *const RcTestObject = ptr::null();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![super(obj), newMethodOnInstance] };
}
#[test]
fn test_alloc() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let obj: Allocated<RcTestObject> = unsafe { msg_send_id![cls, alloc] };
expected.alloc += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.assert_current();
let _: Allocated<NSObject> =
unsafe { msg_send_id![super(cls, NSObject::class().metaclass()), alloc] };
expected.alloc += 1;
expected.release += 1;
expected.assert_current();
}
#[test]
fn test_alloc_with_zone() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
let zone: *const NSZone = ptr::null();
let _obj: Allocated<RcTestObject> = unsafe { msg_send_id![cls, allocWithZone: zone] };
expected.alloc += 1;
expected.assert_current();
let _obj: Allocated<RcTestObject> =
unsafe { msg_send_id![super(cls, cls.metaclass()), allocWithZone: zone] };
expected.alloc += 1;
expected.assert_current();
let _obj: Allocated<NSObject> =
unsafe { msg_send_id![super(cls, NSObject::class().metaclass()), allocWithZone: zone] };
expected.assert_current();
}
#[test]
fn test_alloc_with_null() {
let obj: Allocated<RcTestObject> =
unsafe { msg_send_id![RcTestObject::class(), allocReturningNull] };
assert!(Allocated::as_ptr(&obj).is_null());
}
#[test]
fn test_init() {
let mut expected = ThreadTestData::current();
let _: Retained<RcTestObject> = unsafe { msg_send_id![RcTestObject::alloc(), init] };
expected.alloc += 1;
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
let obj = RcTestObject::alloc().set_ivars(());
let _: Retained<RcTestObject> = unsafe { msg_send_id![super(obj), init] };
expected.alloc += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
let obj = RcTestObject::alloc();
expected.alloc += 1;
assert!(!Allocated::as_ptr(&obj).is_null());
let _: Retained<RcTestObject> = unsafe { msg_send_id![obj, init] };
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
#[should_panic = "failed initializing object with -initReturningNull"]
fn test_init_with_null() {
let obj: Allocated<RcTestObject> = unsafe { msg_send_id![RcTestObject::class(), alloc] };
let _obj: Retained<RcTestObject> = unsafe { msg_send_id![obj, initReturningNull] };
}
#[test]
#[cfg_attr(debug_assertions, should_panic = "messsaging init to nil")]
#[cfg_attr(not(debug_assertions), ignore = "failed allocating object")]
fn test_init_with_null_receiver() {
let obj: Allocated<RcTestObject> =
unsafe { msg_send_id![RcTestObject::class(), allocReturningNull] };
let _obj: Retained<RcTestObject> = unsafe { msg_send_id![obj, init] };
}
#[test]
#[should_panic = "tried to initialize ivars after they were already initialized"]
#[cfg_attr(not(debug_assertions), ignore = "only checked with debug assertions")]
#[cfg_attr(
all(
debug_assertions,
any(feature = "unstable-c-unwind", target_arch = "x86")
),
ignore = "panicking in `init` requires that we emit the function as `C-unwind`"
)]
fn test_super_init_not_initialized() {
let obj = RcTestObject::alloc().set_ivars(());
let _: Retained<RcTestObject> =
unsafe { msg_send_id![super(obj, RcTestObject::class()), init] };
}
#[test]
#[should_panic = "tried to finalize an already finalized object"]
#[cfg_attr(not(debug_assertions), ignore = "only checked with debug assertions")]
fn test_super_init_not_finalized() {
let obj = unsafe { PartialInit::new(Allocated::into_ptr(RcTestObject::alloc())) };
let _: Retained<RcTestObject> =
unsafe { msg_send_id![super(obj, RcTestObject::class()), init] };
}
#[test]
fn test_copy() {
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
let _: Retained<RcTestObject> = unsafe { msg_send_id![&obj, copy] };
expected.copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
let _: Retained<NSObject> = unsafe { msg_send_id![super(&obj), copy] };
expected.copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
#[should_panic = "failed copying object"]
fn test_copy_with_null() {
let obj = RcTestObject::new();
let _obj: Retained<RcTestObject> = unsafe { msg_send_id![&obj, copyReturningNull] };
}
#[test]
#[should_panic = "failed copying object"]
fn test_super_copy_with_null() {
let obj = RcTestObject::new();
let _obj: Retained<RcTestObject> =
unsafe { msg_send_id![super(&obj, RcTestObject::class()), copyReturningNull] };
}
#[test]
fn test_mutable_copy() {
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
let _: Retained<RcTestObject> = unsafe { msg_send_id![&obj, mutableCopy] };
expected.mutable_copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
let _: Retained<NSObject> = unsafe { msg_send_id![super(&obj), mutableCopy] };
expected.mutable_copy += 1;
expected.alloc += 1;
expected.init += 1;
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
fn test_normal() {
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
let _: Retained<RcTestObject> = unsafe { msg_send_id![&obj, self] };
expected.retain += 1;
expected.release += 1;
expected.assert_current();
let _: Retained<RcTestObject> = unsafe { msg_send_id![super(&obj), self] };
expected.retain += 1;
expected.release += 1;
expected.assert_current();
let _: Option<Retained<RcTestObject>> = unsafe { msg_send_id![&obj, description] };
expected.assert_current();
let _: Option<Retained<RcTestObject>> = unsafe { msg_send_id![super(&obj), description] };
expected.assert_current();
}
#[test]
#[should_panic = "unexpected NULL returned from -[__RcTestObject methodReturningNull]"]
fn test_normal_with_null() {
let obj = RcTestObject::new();
let _obj: Retained<RcTestObject> = unsafe { msg_send_id![&obj, methodReturningNull] };
}
#[test]
#[should_panic = "unexpected NULL returned from -[__RcTestObject aMethod:]"]
fn test_normal_with_param_and_null() {
let obj = RcTestObject::new();
let _obj: Retained<RcTestObject> = unsafe { msg_send_id![&obj, aMethod: false] };
}
#[test]
#[cfg_attr(debug_assertions, should_panic = "messsaging description to nil")]
#[cfg_attr(
not(debug_assertions),
ignore = "unexpected NULL description; receiver was NULL"
)]
fn test_normal_with_null_receiver() {
let obj: *const NSObject = ptr::null();
let _obj: Retained<AnyObject> = unsafe { msg_send_id![obj, description] };
}
#[allow(clippy::if_same_then_else)]
const IF_AUTORELEASE_NOT_SKIPPED: usize = if cfg!(feature = "gnustep-1-7") {
1
} else if cfg!(target_arch = "x86") {
2
} else if cfg!(target_arch = "aarch64") {
2
} else if cfg!(any(debug_assertions, feature = "catch-all")) {
2
} else {
1
} - 1;
const IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK: usize = {
if cfg!(all(target_arch = "arm", panic = "unwind")) {
1
} else {
IF_AUTORELEASE_NOT_SKIPPED
}
};
macro_rules! test_error_id {
($expected:expr, $if_autorelease_not_skipped:expr, $sel:ident, $($obj:tt)*) => {
let res = autoreleasepool(|_pool| {
let res: Result<Retained<RcTestObject>, Retained<RcTestObject>> = unsafe {
msg_send_id![$($obj)*, $sel: false, error: _]
};
let res = res.expect("not ok");
$expected.alloc += 1;
$expected.init += 1;
$expected.autorelease += $if_autorelease_not_skipped;
$expected.retain += $if_autorelease_not_skipped;
$expected.assert_current();
res
});
$expected.release += $if_autorelease_not_skipped;
$expected.assert_current();
drop(res);
$expected.release += 1;
$expected.drop += 1;
$expected.assert_current();
let res = autoreleasepool(|_pool| {
let res: Result<Retained<RcTestObject>, Retained<RcTestObject>> = unsafe {
msg_send_id![$($obj)*, $sel: true, error: _]
};
$expected.alloc += 1;
$expected.init += 1;
$expected.autorelease += 1;
$expected.retain += 1;
$expected.assert_current();
res.expect_err("not err")
});
$expected.release += 1;
$expected.assert_current();
drop(res);
$expected.release += 1;
$expected.drop += 1;
$expected.assert_current();
}
}
#[test]
fn test_error_id() {
let mut expected = ThreadTestData::current();
let cls = RcTestObject::class();
test_error_id!(
expected,
IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK,
idAndShouldError,
cls
);
test_error_id!(expected, 0, newAndShouldError, cls);
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
test_error_id!(
expected,
IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK,
idAndShouldError,
&obj
);
expected.alloc -= 1;
expected.release -= 1;
test_error_id!(expected, 0, initAndShouldError, {
expected.alloc += 1;
expected.release += 1;
RcTestObject::alloc()
});
}
#[test]
fn test_method_id_with_param() {
let mut expected = ThreadTestData::current();
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
let res: Option<Retained<RcTestObject>> = unsafe { msg_send_id![&obj, aMethod: false] };
assert!(res.is_none());
expected.assert_current();
let _res = autoreleasepool(|_pool| {
let res: Option<Retained<RcTestObject>> = unsafe { msg_send_id![&obj, aMethod: true] };
assert!(res.is_some());
expected.alloc += 1;
expected.init += 1;
expected.autorelease += IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK;
expected.retain += IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK;
expected.assert_current();
res
});
expected.release += IF_AUTORELEASE_NOT_SKIPPED_ARM_HACK;
expected.assert_current();
}
}