|
| 1 | +use core::marker::PhantomData; |
| 2 | +use core::mem; |
| 3 | +use core::ops::Deref; |
| 4 | +use core::ptr; |
| 5 | +use std::os::raw::c_ulong; |
| 6 | + |
| 7 | +use objc2_encode::{Encode, EncodeArguments}; |
| 8 | + |
| 9 | +use super::{ffi, Block}; |
| 10 | +use crate::BlockArguments; |
| 11 | + |
| 12 | +// TODO: Should this be a static to help the compiler deduplicating them? |
| 13 | +const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_header { |
| 14 | + reserved: 0, |
| 15 | + size: mem::size_of::<ffi::Block_layout>() as c_ulong, |
| 16 | +}; |
| 17 | + |
| 18 | +/// An Objective-C block that does not capture it's environment. |
| 19 | +/// |
| 20 | +/// This is effectively just a glorified function pointer, and can created and |
| 21 | +/// stored in static memory using the [`global_block`][`global_block!`] macro. |
| 22 | +/// |
| 23 | +/// If [`ConcreteBlock`] is the [`Fn`]-block equivalent, this is likewise the |
| 24 | +/// [`fn`]-block equivalent. |
| 25 | +/// |
| 26 | +/// [`ConcreteBlock`]: crate::ConcreteBlock |
| 27 | +#[repr(C)] |
| 28 | +pub struct GlobalBlock<A, R = ()> { |
| 29 | + layout: ffi::Block_layout, |
| 30 | + p: PhantomData<(A, R)>, |
| 31 | +} |
| 32 | + |
| 33 | +unsafe impl<A, R> Sync for GlobalBlock<A, R> |
| 34 | +where |
| 35 | + A: BlockArguments + EncodeArguments, |
| 36 | + R: Encode, |
| 37 | +{ |
| 38 | +} |
| 39 | +unsafe impl<A, R> Send for GlobalBlock<A, R> |
| 40 | +where |
| 41 | + A: BlockArguments + EncodeArguments, |
| 42 | + R: Encode, |
| 43 | +{ |
| 44 | +} |
| 45 | + |
| 46 | +// Note: We can't put correct bounds on A and R because we have a const fn! |
| 47 | +// |
| 48 | +// Fortunately, we don't need them, since they're present on `Sync`, so |
| 49 | +// constructing the static in `global_block!` with an invalid `GlobalBlock` |
| 50 | +// triggers an error. |
| 51 | +impl<A, R> GlobalBlock<A, R> { |
| 52 | + // TODO: Use new ABI with BLOCK_HAS_SIGNATURE |
| 53 | + const FLAGS: ffi::block_flags = ffi::BLOCK_IS_GLOBAL | ffi::BLOCK_USE_STRET; |
| 54 | + |
| 55 | + #[doc(hidden)] |
| 56 | + pub const __DEFAULT_LAYOUT: ffi::Block_layout = ffi::Block_layout { |
| 57 | + // Populated in `global_block!` |
| 58 | + isa: ptr::null_mut(), |
| 59 | + flags: Self::FLAGS, |
| 60 | + reserved: 0, |
| 61 | + // Populated in `global_block!` |
| 62 | + invoke: None, |
| 63 | + descriptor: &GLOBAL_DESCRIPTOR as *const _ as *mut _, |
| 64 | + }; |
| 65 | + |
| 66 | + /// Use the [`global_block`] macro instead. |
| 67 | + #[doc(hidden)] |
| 68 | + pub const unsafe fn from_layout(layout: ffi::Block_layout) -> Self { |
| 69 | + Self { |
| 70 | + layout, |
| 71 | + p: PhantomData, |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +impl<A, R> Deref for GlobalBlock<A, R> |
| 77 | +where |
| 78 | + A: BlockArguments + EncodeArguments, |
| 79 | + R: Encode, |
| 80 | +{ |
| 81 | + type Target = Block<A, R>; |
| 82 | + |
| 83 | + fn deref(&self) -> &Block<A, R> { |
| 84 | + // TODO: SAFETY |
| 85 | + unsafe { &*(self as *const Self as *const Block<A, R>) } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +/// Construct a static [`GlobalBlock`]. |
| 90 | +/// |
| 91 | +/// The syntax is similar to a static closure. Note that the block cannot |
| 92 | +/// capture it's environment, and it's argument types and return type must be |
| 93 | +/// [`Encode`]. |
| 94 | +/// |
| 95 | +/// # Examples |
| 96 | +/// |
| 97 | +/// ``` |
| 98 | +/// use block2::global_block; |
| 99 | +/// global_block! { |
| 100 | +/// static MY_BLOCK = || -> i32 { |
| 101 | +/// 42 |
| 102 | +/// } |
| 103 | +/// }; |
| 104 | +/// assert_eq!(unsafe { MY_BLOCK.call(()) }, 42); |
| 105 | +/// ``` |
| 106 | +/// |
| 107 | +/// ``` |
| 108 | +/// use block2::global_block; |
| 109 | +/// global_block! { |
| 110 | +/// static ADDER_BLOCK = |x: i32, y: i32| -> i32 { |
| 111 | +/// x + y |
| 112 | +/// } |
| 113 | +/// }; |
| 114 | +/// assert_eq!(unsafe { ADDER_BLOCK.call((5, 7)) }, 12); |
| 115 | +/// ``` |
| 116 | +/// |
| 117 | +/// ``` |
| 118 | +/// use block2::global_block; |
| 119 | +/// global_block! { |
| 120 | +/// pub static MUTATING_BLOCK = |x: &mut i32| { |
| 121 | +/// *x = *x + 42; |
| 122 | +/// } |
| 123 | +/// }; |
| 124 | +/// let mut x = 5; |
| 125 | +/// unsafe { MUTATING_BLOCK.call((&mut x,)) }; |
| 126 | +/// assert_eq!(x, 47); |
| 127 | +/// ``` |
| 128 | +/// |
| 129 | +/// The following does not compile because [`Box`] is not [`Encode`]: |
| 130 | +/// |
| 131 | +/// ```compile_fail |
| 132 | +/// use block2::global_block; |
| 133 | +/// global_block! { |
| 134 | +/// pub static INVALID_BLOCK = |b: Box<i32>| {} |
| 135 | +/// }; |
| 136 | +/// ``` |
| 137 | +/// |
| 138 | +/// [`Box`]: std::boxed::Box |
| 139 | +#[macro_export] |
| 140 | +macro_rules! global_block { |
| 141 | + // `||` is parsed as one token |
| 142 | + ( |
| 143 | + $(#[$m:meta])* |
| 144 | + $vis:vis static $name:ident = || $(-> $r:ty)? $body:block |
| 145 | + ) => { |
| 146 | + $crate::global_block!($(#[$m])* $vis static $name = |,| $(-> $r)? $body); |
| 147 | + }; |
| 148 | + ( |
| 149 | + $(#[$m:meta])* |
| 150 | + $vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block |
| 151 | + ) => { |
| 152 | + $(#[$m])* |
| 153 | + #[allow(unused_unsafe)] |
| 154 | + $vis static $name: $crate::GlobalBlock<($($t,)*) $(, $r)?> = unsafe { |
| 155 | + let mut layout = $crate::GlobalBlock::<($($t,)*) $(, $r)?>::__DEFAULT_LAYOUT; |
| 156 | + layout.isa = &$crate::ffi::_NSConcreteGlobalBlock as *const _ as *mut _; |
| 157 | + layout.invoke = Some({ |
| 158 | + unsafe extern "C" fn inner(_: *mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? { |
| 159 | + $body |
| 160 | + } |
| 161 | + let inner: unsafe extern "C" fn(*mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? = inner; |
| 162 | + |
| 163 | + // TODO: SAFETY |
| 164 | + ::core::mem::transmute(inner) |
| 165 | + }); |
| 166 | + $crate::GlobalBlock::from_layout(layout) |
| 167 | + }; |
| 168 | + }; |
| 169 | +} |
| 170 | + |
| 171 | +#[cfg(test)] |
| 172 | +mod tests { |
| 173 | + global_block! { |
| 174 | + /// Test comments and visibility |
| 175 | + pub(super) static NOOP_BLOCK = || {} |
| 176 | + } |
| 177 | + |
| 178 | + global_block! { |
| 179 | + /// Multiple arguments + trailing comma |
| 180 | + #[allow(unused)] |
| 181 | + static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 { |
| 182 | + x + y + z + w |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + #[test] |
| 187 | + fn test_noop_block() { |
| 188 | + unsafe { NOOP_BLOCK.call(()) }; |
| 189 | + } |
| 190 | + |
| 191 | + #[test] |
| 192 | + fn test_defined_in_function() { |
| 193 | + global_block!( |
| 194 | + static MY_BLOCK = || -> i32 { |
| 195 | + 42 |
| 196 | + } |
| 197 | + ); |
| 198 | + assert_eq!(unsafe { MY_BLOCK.call(()) }, 42); |
| 199 | + } |
| 200 | +} |
0 commit comments