use core::fmt;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use crate::encode::{EncodeConvert, Encoding};
use crate::runtime::{ivar_offset, Object};
pub(crate) mod private {
pub trait Sealed {}
}
pub unsafe trait InnerIvarType: private::Sealed {
#[doc(hidden)]
const __ENCODING: Encoding;
#[doc(hidden)]
type __Inner;
type Output;
#[doc(hidden)]
const __MAY_DROP: bool;
#[doc(hidden)]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output;
#[doc(hidden)]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output;
#[doc(hidden)]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output>;
}
impl<T: EncodeConvert> private::Sealed for T {}
unsafe impl<T: EncodeConvert> InnerIvarType for T {
const __ENCODING: Encoding = <Self as EncodeConvert>::__ENCODING;
type __Inner = Self;
type Output = Self;
const __MAY_DROP: bool = false;
#[inline]
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output {
inner
}
#[inline]
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output {
inner
}
#[inline]
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
inner
}
}
pub unsafe trait IvarType {
type Type: InnerIvarType;
const NAME: &'static str;
#[doc(hidden)]
unsafe fn __offset(ptr: NonNull<Object>) -> 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 as InnerIvarType>::Output>,
}
impl<T: IvarType> Drop for Ivar<T> {
#[inline]
fn drop(&mut self) {
if <T::Type as InnerIvarType>::__MAY_DROP {
unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) }
}
}
}
impl<T: IvarType> Ivar<T> {
pub fn as_ptr(this: &Self) -> *const <T::Type as InnerIvarType>::Output {
T::Type::__to_ptr(this.as_inner_ptr()).as_ptr()
}
fn as_inner_ptr(&self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
let ptr: NonNull<Object> = NonNull::from(self).cast();
let offset = unsafe { T::__offset(ptr) };
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
}
pub fn as_mut_ptr(this: &mut Self) -> *mut <T::Type as InnerIvarType>::Output {
T::Type::__to_ptr(this.as_inner_mut_ptr()).as_ptr()
}
fn as_inner_mut_ptr(&mut self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
let ptr: NonNull<Object> = NonNull::from(self).cast();
let offset = unsafe { T::__offset(ptr) };
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
}
pub fn write(
this: &mut Self,
val: <T::Type as InnerIvarType>::Output,
) -> &mut <T::Type as InnerIvarType>::Output {
let ptr: *mut MaybeUninit<<T::Type as InnerIvarType>::Output> =
Self::as_mut_ptr(this).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 { T::Type::__to_ref(self.as_inner_ptr().as_ref()) }
}
}
impl<T: IvarType> DerefMut for Ivar<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { T::Type::__to_mut(self.as_inner_mut_ptr().as_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::rc::{Id, Owned};
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 = u32;
const NAME: &'static str = "_foo";
}
#[repr(C)]
struct IvarTestObject {
inner: Object,
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: u8,
}
unsafe impl ClassType for CustomDrop {
type Super = NSObject;
}
unsafe impl CustomDrop {
#[method(dealloc)]
fn dealloc(&mut self) {
HAS_RUN_DEALLOC.store(true, Ordering::SeqCst);
unsafe { msg_send![super(self), dealloc] }
}
}
);
let _: Id<CustomDrop, Owned> = unsafe { msg_send_id![CustomDrop::class(), new] };
assert!(HAS_RUN_DEALLOC.load(Ordering::SeqCst));
}
}