use core::fmt;
use core::marker::PhantomData;
use core::ptr::NonNull;
use objc2::encode::{Encoding, RefEncode};
use crate::abi::BlockHeader;
use crate::debug::debug_block_header;
use crate::rc_block::block_copy_fail;
use crate::{BlockFn, RcBlock};
#[repr(C)]
pub struct Block<F: ?Sized> {
_inner: [u8; 0],
_header: PhantomData<BlockHeader>,
_p: PhantomData<F>,
}
unsafe impl<F: ?Sized + BlockFn> RefEncode for Block<F> {
const ENCODING_REF: Encoding = Encoding::Block;
}
impl<F: ?Sized> Block<F> {
fn header(&self) -> &BlockHeader {
let ptr: NonNull<Self> = NonNull::from(self);
let ptr: NonNull<BlockHeader> = ptr.cast();
unsafe { ptr.as_ref() }
}
#[doc(alias = "Block_copy")]
#[doc(alias = "_Block_copy")]
#[inline]
pub fn copy(&self) -> RcBlock<F> {
let ptr: *const Self = self;
let ptr: *mut Block<F> = ptr as *mut _;
unsafe { RcBlock::copy(ptr) }.unwrap_or_else(|| block_copy_fail())
}
#[doc(alias = "invoke")]
pub fn call(&self, args: F::Args) -> F::Output
where
F: BlockFn,
{
let invoke = self.header().invoke.unwrap_or_else(|| unreachable!());
let ptr: NonNull<Self> = NonNull::from(self);
let ptr: *mut Self = ptr.as_ptr();
unsafe { F::__call_block(invoke, ptr, args) }
}
}
impl<F: ?Sized> fmt::Debug for Block<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("Block");
debug_block_header(self.header(), &mut f);
f.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
#[test]
fn test_rust_dyn_lifetime_semantics() {
fn takes_static(block: &Block<dyn Fn() + 'static>) {
block.call(());
}
fn takes_elided(block: &Block<dyn Fn() + '_>) {
block.call(());
}
fn takes_unspecified(block: &Block<dyn Fn()>) {
block.call(());
}
static MY_STATIC: AtomicUsize = AtomicUsize::new(0);
MY_STATIC.store(0, Ordering::Relaxed);
let static_lifetime: RcBlock<dyn Fn() + 'static> = RcBlock::new(|| {
MY_STATIC.fetch_add(1, Ordering::Relaxed);
});
takes_static(&static_lifetime);
takes_elided(&static_lifetime);
takes_unspecified(&static_lifetime);
assert_eq!(MY_STATIC.load(Ordering::Relaxed), 3);
let captured = Cell::new(0);
let elided_lifetime: RcBlock<dyn Fn() + '_> = RcBlock::new(|| {
captured.set(captured.get() + 1);
});
takes_elided(&elided_lifetime);
assert_eq!(captured.get(), 1);
let captured = Cell::new(0);
let unspecified_lifetime: RcBlock<dyn Fn()> = RcBlock::new(|| {
captured.set(captured.get() + 1);
});
takes_elided(&unspecified_lifetime);
assert_eq!(captured.get(), 1);
}
#[allow(dead_code)]
fn unspecified_in_fn_is_static(block: &Block<dyn Fn()>) -> &Block<dyn Fn() + 'static> {
block
}
#[allow(dead_code)]
fn lending_block<'b>(block: &Block<dyn Fn() -> &'b i32 + 'b>) {
let _ = *block.call(());
}
#[allow(dead_code)]
fn takes_lifetime(_: &Block<dyn Fn(&i32) -> &i32>) {
}
#[allow(dead_code)]
fn covariant<'b, 'f>(b: &'b Block<dyn Fn() + 'static>) -> &'b Block<dyn Fn() + 'f> {
b
}
}