use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, MaybeUninit};
use core::ops::Deref;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use std::os::raw::c_ulong;
use objc2::encode::{EncodeArguments, EncodeReturn, Encoding, RefEncode};
use crate::abi::{
BlockDescriptor, BlockDescriptorCopyDispose, BlockDescriptorPtr, BlockFlags, BlockHeader,
};
use crate::debug::debug_block_header;
use crate::{ffi, Block, IntoBlock};
#[repr(C)]
pub struct StackBlock<'f, A, R, Closure> {
p: PhantomData<dyn Fn(A) -> R + Send + Sync + RefUnwindSafe + UnwindSafe + Unpin + 'f>,
header: BlockHeader,
pub(crate) closure: Closure,
}
unsafe impl<'f, A, R, Closure> RefEncode for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
{
const ENCODING_REF: Encoding = Encoding::Block;
}
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
const SIZE: c_ulong = mem::size_of::<Self>() as _;
unsafe extern "C" fn drop_closure(block: *mut c_void) {
let block: *mut Self = block.cast();
let closure = unsafe { ptr::addr_of_mut!((*block).closure) };
unsafe { ptr::drop_in_place(closure) };
}
const DESCRIPTOR_BASIC: BlockDescriptor = BlockDescriptor {
reserved: 0,
size: Self::SIZE,
};
}
impl<'f, A, R, Closure: Clone> StackBlock<'f, A, R, Closure> {
unsafe extern "C" fn clone_closure(dst: *mut c_void, src: *const c_void) {
let dst: *mut Self = dst.cast();
let src: *const Self = src.cast();
let dst_closure = unsafe { ptr::addr_of_mut!((*dst).closure) };
let src_closure = unsafe { &*ptr::addr_of!((*src).closure) };
unsafe { ptr::write(dst_closure, src_closure.clone()) };
}
const DESCRIPTOR_WITH_CLONE: BlockDescriptorCopyDispose = BlockDescriptorCopyDispose {
reserved: 0,
size: Self::SIZE,
copy: Some(Self::clone_closure),
dispose: Some(Self::drop_closure),
};
}
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
#[inline]
pub fn new(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R> + Clone,
{
let header = BlockHeader {
isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) },
flags: BlockFlags::BLOCK_HAS_COPY_DISPOSE,
reserved: MaybeUninit::new(0),
invoke: Some(Closure::__get_invoke_stack_block()),
descriptor: BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_CLONE,
},
};
Self {
p: PhantomData,
header,
closure,
}
}
}
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
unsafe extern "C" fn empty_clone_closure(_dst: *mut c_void, _src: *const c_void) {
}
const DESCRIPTOR_WITH_DROP: BlockDescriptorCopyDispose = BlockDescriptorCopyDispose {
reserved: 0,
size: Self::SIZE,
copy: Some(Self::empty_clone_closure),
dispose: Some(Self::drop_closure),
};
#[inline]
pub(crate) unsafe fn new_no_clone(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
{
let flags = if mem::needs_drop::<Self>() {
BlockFlags::BLOCK_HAS_COPY_DISPOSE
} else {
BlockFlags::EMPTY
};
let descriptor = if mem::needs_drop::<Self>() {
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_DROP,
}
} else {
BlockDescriptorPtr {
basic: &Self::DESCRIPTOR_BASIC,
}
};
let header = BlockHeader {
isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) },
flags,
reserved: MaybeUninit::new(0),
invoke: Some(Closure::__get_invoke_stack_block()),
descriptor,
};
Self {
p: PhantomData,
header,
closure,
}
}
}
impl<'f, A, R, Closure: Clone> Clone for StackBlock<'f, A, R, Closure> {
#[inline]
fn clone(&self) -> Self {
Self {
p: PhantomData,
header: self.header,
closure: self.closure.clone(),
}
}
}
impl<'f, A, R, Closure: Copy> Copy for StackBlock<'f, A, R, Closure> {}
impl<'f, A, R, Closure> Deref for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
{
type Target = Block<Closure::Dyn>;
#[inline]
fn deref(&self) -> &Self::Target {
let ptr: NonNull<Self> = NonNull::from(self);
let ptr: NonNull<Block<Closure::Dyn>> = ptr.cast();
unsafe { ptr.as_ref() }
}
}
impl<'f, A, R, Closure> fmt::Debug for StackBlock<'f, A, R, Closure> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("StackBlock");
debug_block_header(&self.header, &mut f);
f.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size() {
assert_eq!(
mem::size_of::<BlockHeader>(),
<StackBlock<'_, (), (), ()>>::SIZE as _,
);
assert_eq!(
mem::size_of::<BlockHeader>() + mem::size_of::<fn()>(),
<StackBlock<'_, (), (), fn()>>::SIZE as _,
);
}
#[allow(dead_code)]
fn covariant<'b, 'f>(
b: StackBlock<'static, (), (), impl Fn() + 'static>,
) -> StackBlock<'b, (), (), impl Fn() + 'f> {
b
}
}