use core::ffi::CStr;
use core::marker::PhantomData;
use core::mem;
use core::ptr;
use objc2::encode::EncodeArguments;
use objc2::encode::{EncodeArgument, EncodeReturn};
use crate::{Block, StackBlock};
mod private {
pub trait Sealed<A, R> {}
}
pub unsafe trait BlockFn: private::Sealed<Self::Args, Self::Output> {
type Args: EncodeArguments;
type Output: EncodeReturn;
#[doc(hidden)]
unsafe fn __call_block(
invoke: unsafe extern "C-unwind" fn(),
block: *mut Block<Self>,
args: Self::Args,
) -> Self::Output;
}
pub unsafe trait IntoBlock<'f, A, R>: private::Sealed<A, R>
where
A: EncodeArguments,
R: EncodeReturn,
{
type Dyn: ?Sized + BlockFn<Args = A, Output = R>;
#[doc(hidden)]
fn __get_invoke_stack_block() -> unsafe extern "C-unwind" fn();
}
macro_rules! impl_traits {
($($a:ident: $t:ident),*) => (
impl<$($t: EncodeArgument,)* R: EncodeReturn, Closure> private::Sealed<($($t,)*), R> for Closure
where
Closure: ?Sized + Fn($($t),*) -> R,
{}
unsafe impl<$($t: EncodeArgument,)* R: EncodeReturn> BlockFn for dyn Fn($($t),*) -> R + '_ {
type Args = ($($t,)*);
type Output = R;
#[inline]
unsafe fn __call_block(
invoke: unsafe extern "C-unwind" fn(),
block: *mut Block<Self>,
($($a,)*): Self::Args,
) -> Self::Output {
let invoke: unsafe extern "C-unwind" fn(*mut Block<Self> $(, $t)*) -> R = unsafe {
mem::transmute(invoke)
};
unsafe { invoke(block $(, $a)*) }
}
}
unsafe impl<'f, $($t,)* R, Closure> IntoBlock<'f, ($($t,)*), R> for Closure
where
$($t: EncodeArgument,)*
R: EncodeReturn,
Closure: Fn($($t),*) -> R + 'f,
{
type Dyn = dyn Fn($($t),*) -> R + 'f;
#[inline]
fn __get_invoke_stack_block() -> unsafe extern "C-unwind" fn() {
unsafe extern "C-unwind" fn invoke<'f, $($t,)* R, Closure>(
block: *mut StackBlock<'f, ($($t,)*), R, Closure>,
$($a: $t,)*
) -> R
where
Closure: Fn($($t),*) -> R + 'f
{
let closure = unsafe { &*ptr::addr_of!((*block).closure) };
(closure)($($a),*)
}
unsafe {
mem::transmute::<
unsafe extern "C-unwind" fn(*mut StackBlock<'f, ($($t,)*), R, Closure>, $($t,)*) -> R,
unsafe extern "C-unwind" fn(),
>(invoke)
}
}
}
);
}
impl_traits!();
impl_traits!(t0: T0);
impl_traits!(t0: T0, t1: T1);
impl_traits!(t0: T0, t1: T1, t2: T2);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10);
impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10, t11: T11);
pub unsafe trait ManualBlockEncoding {
type Arguments: EncodeArguments;
type Return: EncodeReturn;
const ENCODING_CSTR: &'static CStr;
}
pub(crate) struct NoBlockEncoding<A, R>
where
A: EncodeArguments,
R: EncodeReturn,
{
_a: PhantomData<A>,
_r: PhantomData<R>,
}
unsafe impl<A, R> ManualBlockEncoding for NoBlockEncoding<A, R>
where
A: EncodeArguments,
R: EncodeReturn,
{
type Arguments = A;
type Return = R;
const ENCODING_CSTR: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
}
pub(crate) trait ManualBlockEncodingExt: ManualBlockEncoding {
const IS_NONE: bool;
}
impl<E: ManualBlockEncoding> ManualBlockEncodingExt for UserSpecified<E> {
const IS_NONE: bool = false;
}
impl<A, R> ManualBlockEncodingExt for NoBlockEncoding<A, R>
where
A: EncodeArguments,
R: EncodeReturn,
{
const IS_NONE: bool = true;
}
#[repr(transparent)]
pub(crate) struct UserSpecified<E: ManualBlockEncoding>(E);
unsafe impl<E: ManualBlockEncoding> ManualBlockEncoding for UserSpecified<E> {
type Arguments = E::Arguments;
type Return = E::Return;
const ENCODING_CSTR: &'static CStr = E::ENCODING_CSTR;
}
#[cfg_attr(not(debug_assertions), inline(always))]
#[allow(unused)]
pub(crate) fn debug_assert_block_encoding<A, R, E>()
where
A: EncodeArguments,
R: EncodeReturn,
E: ManualBlockEncodingExt<Arguments = A, Return = R>,
{
#[cfg(debug_assertions)]
{
if !E::IS_NONE {
assert_eq!(
E::ENCODING_CSTR,
&*crate::encoding::block_signature_string::<A, R>()
);
}
}
}
#[cfg(test)]
mod tests {
use core::ffi::c_char;
use super::*;
#[test]
fn test_manual_block_encoding_is_none() {
struct Enc1;
unsafe impl ManualBlockEncoding for Enc1 {
type Arguments = (i32, f32);
type Return = u8;
#[cfg(target_pointer_width = "64")]
const ENCODING_CSTR: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"C16@?0i8f12\0") };
#[cfg(not(target_pointer_width = "64"))]
const ENCODING_CSTR: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"C12@?0i4f8\0") };
}
assert!(!core::convert::identity(UserSpecified::<Enc1>::IS_NONE));
struct Enc2;
unsafe impl ManualBlockEncoding for Enc2 {
type Arguments = ();
type Return = ();
#[cfg(target_pointer_width = "64")]
const ENCODING_CSTR: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"v8@?0\0") };
#[cfg(not(target_pointer_width = "64"))]
const ENCODING_CSTR: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"v4@?0\0") };
}
assert!(!core::convert::identity(UserSpecified::<Enc2>::IS_NONE));
struct Enc3;
unsafe impl ManualBlockEncoding for Enc3 {
type Arguments = ();
type Return = ();
const ENCODING_CSTR: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
}
assert!(!core::convert::identity(UserSpecified::<Enc3>::IS_NONE));
assert!(core::convert::identity(NoBlockEncoding::<(), ()>::IS_NONE));
assert!(core::convert::identity(
NoBlockEncoding::<(i32, f32), u8>::IS_NONE
));
assert!(core::convert::identity(
NoBlockEncoding::<(*const u8,), *const c_char>::IS_NONE
));
}
}