use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, MaybeUninit};
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use crate::encode::Encode;
use crate::runtime::{ivar_offset, AnyObject};
pub(crate) mod private {
pub trait Sealed {}
}
pub unsafe trait InnerIvarType: private::Sealed + Encode {
type Output;
#[doc(hidden)]
unsafe fn __deref(&self) -> &Self::Output;
#[doc(hidden)]
unsafe fn __deref_mut(&mut self) -> &mut Self::Output;
}
pub unsafe trait IvarType {
type Type: InnerIvarType;
const NAME: &'static str;
#[doc(hidden)]
unsafe fn __offset(ptr: NonNull<AnyObject>) -> isize {
let obj = unsafe { ptr.as_ref() };
ivar_offset(obj.class(), Self::NAME, &Self::Type::ENCODING)
}
}
#[repr(C)]
pub struct Ivar<T: IvarType> {
inner: [u8; 0],
item: PhantomData<T::Type>,
}
impl<T: IvarType> Drop for Ivar<T> {
#[inline]
fn drop(&mut self) {
if mem::needs_drop::<T::Type>() {
unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) }
}
}
}
impl<T: IvarType> Ivar<T> {
pub fn as_ptr(this: &Self) -> *const <Self as Deref>::Target {
this.as_inner_ptr().as_ptr().cast()
}
fn as_inner_ptr(&self) -> NonNull<T::Type> {
let ptr: NonNull<AnyObject> = NonNull::from(self).cast();
let offset = unsafe { T::__offset(ptr) };
unsafe { AnyObject::ivar_at_offset::<T::Type>(ptr, offset) }
}
pub fn as_mut_ptr(this: &mut Self) -> *mut <Self as Deref>::Target {
this.as_inner_mut_ptr().as_ptr().cast()
}
fn as_inner_mut_ptr(&mut self) -> NonNull<T::Type> {
let ptr: NonNull<AnyObject> = NonNull::from(self).cast();
let offset = unsafe { T::__offset(ptr) };
unsafe { AnyObject::ivar_at_offset::<T::Type>(ptr, offset) }
}
pub fn write(this: &mut Self, val: <Self as Deref>::Target) -> &mut <Self as Deref>::Target {
let ptr: *mut <T::Type as InnerIvarType>::Output = Self::as_mut_ptr(this);
let ptr: *mut MaybeUninit<<T::Type as InnerIvarType>::Output> = ptr.cast();
let ivar = unsafe { ptr.as_mut().unwrap_unchecked() };
ivar.write(val)
}
}
impl<T: IvarType> Deref for Ivar<T> {
type Target = <T::Type as InnerIvarType>::Output;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { self.as_inner_ptr().as_ref().__deref() }
}
}
impl<T: IvarType> DerefMut for Ivar<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.as_inner_mut_ptr().as_mut().__deref_mut() }
}
}
impl<T: IvarType> fmt::Pointer for Ivar<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&Self::as_ptr(self), f)
}
}
#[cfg(test)]
mod tests {
use core::mem;
use core::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::atomic::{AtomicBool, Ordering};
use super::*;
use crate::declare::{IvarBool, IvarEncode};
use crate::mutability::Mutable;
use crate::rc::Id;
use crate::runtime::NSObject;
use crate::{declare_class, msg_send, msg_send_id, test_utils, ClassType, MessageReceiver};
struct TestIvar;
unsafe impl IvarType for TestIvar {
type Type = IvarEncode<u32>;
const NAME: &'static str = "_foo";
}
#[repr(C)]
struct IvarTestObject {
inner: NSObject,
foo: Ivar<TestIvar>,
}
#[test]
fn auto_traits() {
fn assert_auto_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized + Send + Sync>() {}
assert_auto_traits::<Ivar<TestIvar>>();
assert_eq!(mem::size_of::<Ivar<TestIvar>>(), 0);
assert_eq!(mem::align_of::<Ivar<TestIvar>>(), 1);
}
#[test]
fn access_ivar() {
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] };
let obj = unsafe {
obj.__as_raw_receiver()
.cast::<IvarTestObject>()
.as_ref()
.unwrap()
};
assert_eq!(*obj.foo, 42);
}
#[test]
fn ensure_custom_drop_is_possible() {
static HAS_RUN_DEALLOC: AtomicBool = AtomicBool::new(false);
declare_class!(
#[derive(Debug, PartialEq, Eq)]
struct CustomDrop {
ivar: IvarEncode<u8, "_ivar">,
ivar_bool: IvarBool<"_ivar_bool">,
}
mod customdrop;
unsafe impl ClassType for CustomDrop {
type Super = NSObject;
type Mutability = Mutable;
const NAME: &'static str = "CustomDrop";
}
);
impl Drop for CustomDrop {
fn drop(&mut self) {
HAS_RUN_DEALLOC.store(true, Ordering::Relaxed);
}
}
let _: Id<CustomDrop> = unsafe { msg_send_id![CustomDrop::class(), new] };
assert!(HAS_RUN_DEALLOC.load(Ordering::Relaxed));
}
}