use core::mem::ManuallyDrop;
use core::ptr::NonNull;
use crate::encode::__unstable::EncodeConvertArgument;
use crate::rc::Id;
use crate::Message;
impl<T: Message + 'static> EncodeConvertArgument for &mut Id<T> {
type __Inner = NonNull<*mut T>;
type __StoredBeforeMessage = (
Self::__Inner,
NonNull<T>,
);
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`&mut Id<_>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
let ptr: NonNull<Id<T>> = NonNull::from(self);
let ptr: NonNull<NonNull<T>> = ptr.cast();
let old: NonNull<T> = unsafe { *ptr.as_ptr() };
let ptr: NonNull<*mut T> = ptr.cast();
(ptr, (ptr, old))
}
#[inline]
unsafe fn __process_after_message_send((ptr, old): Self::__StoredBeforeMessage) {
let new: Option<Id<T>> = unsafe { Id::retain(*ptr.as_ptr()) };
let _new = ManuallyDrop::new(new);
#[cfg(debug_assertions)]
if _new.is_none() {
panic!("found that NULL was written to `&mut Id<_>`, which is UB! You should handle this with `&mut Option<Id<_>>` instead");
}
let _: Id<T> = unsafe { Id::new_nonnull(old) };
}
}
impl<T: Message + 'static> EncodeConvertArgument for &mut Option<Id<T>> {
type __Inner = NonNull<*mut T>;
type __StoredBeforeMessage = (Self::__Inner, *mut T);
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`&mut Option<Id<_>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
let ptr: NonNull<Option<Id<T>>> = NonNull::from(self);
let ptr: NonNull<*mut T> = ptr.cast();
let old: *mut T = unsafe { *ptr.as_ptr() };
(ptr, (ptr, old))
}
#[inline]
unsafe fn __process_after_message_send((ptr, old): Self::__StoredBeforeMessage) {
let new: Option<Id<T>> = unsafe { Id::retain(*ptr.as_ptr()) };
let _ = ManuallyDrop::new(new);
let _: Option<Id<T>> = unsafe { Id::new(old) };
}
}
impl<T: Message + 'static> EncodeConvertArgument for Option<&mut Id<T>> {
type __Inner = Option<NonNull<*mut T>>;
type __StoredBeforeMessage = Option<(NonNull<*mut T>, NonNull<T>)>;
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`Option<&mut Id<_>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
if let Some(this) = self {
let (ptr, stored) = this.__into_argument();
(Some(ptr), Some(stored))
} else {
(None, None)
}
}
#[inline]
unsafe fn __process_after_message_send(stored: Self::__StoredBeforeMessage) {
if let Some(stored) = stored {
unsafe { <&mut Id<T>>::__process_after_message_send(stored) };
}
}
}
impl<T: Message + 'static> EncodeConvertArgument for Option<&mut Option<Id<T>>> {
type __Inner = Option<NonNull<*mut T>>;
type __StoredBeforeMessage = Option<(NonNull<*mut T>, *mut T)>;
#[inline]
fn __from_declared_param(_inner: Self::__Inner) -> Self {
todo!("`Option<&mut Option<Id<_>>>` is not supported in `declare_class!` yet")
}
#[inline]
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
if let Some(this) = self {
let (ptr, stored) = this.__into_argument();
(Some(ptr), Some(stored))
} else {
(None, None)
}
}
#[inline]
unsafe fn __process_after_message_send(stored: Self::__StoredBeforeMessage) {
if let Some(stored) = stored {
unsafe { <&mut Option<Id<T>>>::__process_after_message_send(stored) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{__RcTestObject, __ThreadTestData, autoreleasepool};
use crate::{msg_send, msg_send_id, ClassType};
#[test]
fn test_bool_error() {
let mut expected = __ThreadTestData::current();
fn bool_error(should_error: bool, error: Option<&mut Option<Id<__RcTestObject>>>) {
let cls = __RcTestObject::class();
let did_succeed: bool =
unsafe { msg_send![cls, boolAndShouldError: should_error, error: error] };
assert_ne!(should_error, did_succeed);
}
bool_error(false, None);
bool_error(true, None);
expected.assert_current();
fn helper(
expected: &mut __ThreadTestData,
should_error: bool,
mut error: Option<Id<__RcTestObject>>,
) {
std::dbg!(should_error, &error);
autoreleasepool(|_| {
bool_error(should_error, Some(&mut error));
if should_error {
expected.alloc += 1;
expected.init += 1;
expected.autorelease += 1;
}
expected.assert_current();
});
if should_error {
expected.release += 1;
}
expected.assert_current();
if error.is_some() {
expected.release += 1;
expected.dealloc += 1;
}
drop(error);
expected.assert_current();
}
helper(&mut expected, false, None);
expected.retain += 1;
helper(&mut expected, true, None);
expected.alloc += 1;
expected.init += 1;
expected.retain += 1;
expected.release += 1;
helper(&mut expected, false, Some(__RcTestObject::new()));
expected.alloc += 1;
expected.init += 1;
expected.retain += 1;
expected.release += 1;
expected.dealloc += 1;
helper(&mut expected, true, Some(__RcTestObject::new()));
}
#[test]
#[cfg_attr(
any(
not(debug_assertions),
all(not(target_pointer_width = "64"), feature = "catch-all")
),
ignore = "invokes UB which is only caught with debug_assertions"
)]
#[should_panic = "found that NULL was written to `&mut Id<_>`, which is UB! You should handle this with `&mut Option<Id<_>>` instead"]
fn test_debug_check_ub() {
let cls = __RcTestObject::class();
let mut param: Id<_> = __RcTestObject::new();
let _: () = unsafe { msg_send![cls, outParamNull: &mut param] };
}
const AUTORELEASE_SKIPPED: bool = cfg!(feature = "gnustep-1-7");
#[test]
fn test_id_interaction() {
let mut expected = __ThreadTestData::current();
let cls = __RcTestObject::class();
let mut err: Id<__RcTestObject> = __RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
autoreleasepool(|_| {
let obj: Option<Id<__RcTestObject>> =
unsafe { msg_send_id![cls, idAndShouldError: false, error: &mut err] };
expected.alloc += 1;
expected.init += 1;
if !AUTORELEASE_SKIPPED {
expected.autorelease += 1;
expected.retain += 1;
}
expected.retain += 1;
expected.release += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
if AUTORELEASE_SKIPPED {
expected.dealloc += 1;
}
expected.assert_current();
});
if !AUTORELEASE_SKIPPED {
expected.release += 1;
expected.dealloc += 1;
}
expected.assert_current();
drop(err);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
}