mod ivar;
mod ivar_drop;
mod ivar_forwarding_impls;
use alloc::format;
use alloc::string::ToString;
use core::mem;
use core::mem::ManuallyDrop;
use core::ptr;
use core::ptr::NonNull;
use std::ffi::CString;
use crate::encode::{Encode, EncodeArguments, Encoding, RefEncode};
use crate::ffi;
use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel};
use crate::sel;
use crate::Message;
pub use ivar::{InnerIvarType, Ivar, IvarType};
pub use ivar_drop::IvarDrop;
pub(crate) mod private {
pub trait Sealed {}
}
pub trait MethodImplementation: private::Sealed {
type Callee: RefEncode + ?Sized;
type Ret: Encode;
type Args: EncodeArguments;
#[doc(hidden)]
fn __imp(self) -> Imp;
}
macro_rules! method_decl_impl {
(@<$($l:lifetime),*> T, $r:ident, $f:ty, $($t:ident),*) => {
impl<$($l,)* T, $r, $($t),*> private::Sealed for $f
where
T: Message + ?Sized,
$r: Encode,
$($t: Encode,)*
{}
impl<$($l,)* T, $r, $($t),*> MethodImplementation for $f
where
T: Message + ?Sized,
$r: Encode,
$($t: Encode,)*
{
type Callee = T;
type Ret = $r;
type Args = ($($t,)*);
fn __imp(self) -> Imp {
unsafe { mem::transmute(self) }
}
}
};
(@<$($l:lifetime),*> Class, $r:ident, $f:ty, $($t:ident),*) => {
impl<$($l,)* $r, $($t),*> private::Sealed for $f
where
$r: Encode,
$($t: Encode,)*
{}
impl<$($l,)* $r, $($t),*> MethodImplementation for $f
where
$r: Encode,
$($t: Encode,)*
{
type Callee = Class;
type Ret = $r;
type Args = ($($t,)*);
fn __imp(self) -> Imp {
unsafe { mem::transmute(self) }
}
}
};
(# $abi:literal; $($t:ident),*) => {
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> T, R, unsafe extern $abi fn(*const T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> T, R, unsafe extern $abi fn(*mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, unsafe extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, unsafe extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> Class, R, extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> Class, R, unsafe extern $abi fn(*const Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> Class, R, unsafe extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
};
($($t:ident),*) => {
method_decl_impl!(# "C"; $($t),*);
#[cfg(feature = "unstable-c-unwind")]
method_decl_impl!(# "C-unwind"; $($t),*);
};
}
method_decl_impl!();
method_decl_impl!(A);
method_decl_impl!(A, B);
method_decl_impl!(A, B, C);
method_decl_impl!(A, B, C, D);
method_decl_impl!(A, B, C, D, E);
method_decl_impl!(A, B, C, D, E, F);
method_decl_impl!(A, B, C, D, E, F, G);
method_decl_impl!(A, B, C, D, E, F, G, H);
method_decl_impl!(A, B, C, D, E, F, G, H, I);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);
fn count_args(sel: Sel) -> usize {
sel.name().chars().filter(|&c| c == ':').count()
}
fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
let mut types = format!("{}{}{}", ret, <*mut Object>::ENCODING, Sel::ENCODING);
for enc in args {
use core::fmt::Write;
write!(&mut types, "{}", enc).unwrap();
}
CString::new(types).unwrap()
}
fn log2_align_of<T>() -> u8 {
let align = mem::align_of::<T>();
debug_assert!(align.count_ones() == 1);
align.trailing_zeros() as u8
}
#[derive(Debug)]
pub struct ClassBuilder {
cls: NonNull<Class>,
}
#[doc(hidden)]
#[deprecated = "Use `ClassBuilder` instead."]
pub type ClassDecl = ClassBuilder;
unsafe impl Send for ClassBuilder {}
unsafe impl Sync for ClassBuilder {}
impl ClassBuilder {
fn as_mut_ptr(&mut self) -> *mut ffi::objc_class {
self.cls.as_ptr().cast()
}
fn with_superclass(name: &str, superclass: Option<&Class>) -> Option<Self> {
let name = CString::new(name).unwrap();
let super_ptr = superclass.map_or(ptr::null(), |c| c).cast();
let cls = unsafe { ffi::objc_allocateClassPair(super_ptr, name.as_ptr(), 0) };
NonNull::new(cls.cast()).map(|cls| Self { cls })
}
pub fn new(name: &str, superclass: &Class) -> Option<Self> {
Self::with_superclass(name, Some(superclass))
}
pub fn root<F>(name: &str, intitialize_fn: F) -> Option<Self>
where
F: MethodImplementation<Callee = Class, Args = (), Ret = ()>,
{
Self::with_superclass(name, None).map(|mut this| {
unsafe { this.add_class_method(sel!(initialize), intitialize_fn) };
this
})
}
pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
where
T: Message + ?Sized,
F: MethodImplementation<Callee = T>,
{
let encs = F::Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&F::Ret::ENCODING, encs);
let success = Bool::from_raw(unsafe {
ffi::class_addMethod(
self.as_mut_ptr(),
sel.as_ptr(),
Some(func.__imp()),
types.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add method {:?}", sel);
}
fn metaclass_mut(&mut self) -> *mut ffi::objc_class {
unsafe { ffi::object_getClass(self.as_mut_ptr().cast()) as *mut ffi::objc_class }
}
pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)
where
F: MethodImplementation<Callee = Class>,
{
let encs = F::Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&F::Ret::ENCODING, encs);
let success = Bool::from_raw(unsafe {
ffi::class_addMethod(
self.metaclass_mut(),
sel.as_ptr(),
Some(func.__imp()),
types.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add class method {:?}", sel);
}
pub fn add_ivar<T: Encode>(&mut self, name: &str) {
unsafe { self.add_ivar_inner::<T>(name, &T::ENCODING) }
}
unsafe fn add_ivar_inner<T>(&mut self, name: &str, encoding: &Encoding) {
let c_name = CString::new(name).unwrap();
let encoding = CString::new(encoding.to_string()).unwrap();
let size = mem::size_of::<T>();
let align = log2_align_of::<T>();
let success = Bool::from_raw(unsafe {
ffi::class_addIvar(
self.as_mut_ptr(),
c_name.as_ptr(),
size,
align,
encoding.as_ptr(),
)
});
assert!(success.as_bool(), "Failed to add ivar {}", name);
}
pub fn add_static_ivar<T: IvarType>(&mut self) {
unsafe {
self.add_ivar_inner::<<T::Type as InnerIvarType>::__Inner>(
T::NAME,
&T::Type::__ENCODING,
)
}
}
pub fn add_protocol(&mut self, proto: &Protocol) {
let success = unsafe { ffi::class_addProtocol(self.as_mut_ptr(), proto.as_ptr()) };
let success = Bool::from_raw(success).as_bool();
assert!(success, "Failed to add protocol {:?}", proto);
}
pub fn register(self) -> &'static Class {
let mut this = ManuallyDrop::new(self);
unsafe { ffi::objc_registerClassPair(this.as_mut_ptr()) };
unsafe { this.cls.as_ref() }
}
}
impl Drop for ClassBuilder {
fn drop(&mut self) {
unsafe { ffi::objc_disposeClassPair(self.as_mut_ptr()) }
}
}
#[derive(Debug)]
pub struct ProtocolBuilder {
proto: NonNull<Protocol>,
}
#[doc(hidden)]
#[deprecated = "Use `ProtocolBuilder` instead."]
pub type ProtocolDecl = ProtocolBuilder;
unsafe impl Send for ProtocolBuilder {}
unsafe impl Sync for ProtocolBuilder {}
impl ProtocolBuilder {
fn as_mut_ptr(&mut self) -> *mut ffi::objc_protocol {
self.proto.as_ptr().cast()
}
pub fn new(name: &str) -> Option<Self> {
let c_name = CString::new(name).unwrap();
let proto = unsafe { ffi::objc_allocateProtocol(c_name.as_ptr()) };
NonNull::new(proto.cast()).map(|proto| Self { proto })
}
fn add_method_description_common<Args, Ret>(
&mut self,
sel: Sel,
is_required: bool,
is_instance_method: bool,
) where
Args: EncodeArguments,
Ret: Encode,
{
let encs = Args::ENCODINGS;
let sel_args = count_args(sel);
assert_eq!(
sel_args,
encs.len(),
"Selector {:?} accepts {} arguments, but function accepts {}",
sel,
sel_args,
encs.len(),
);
let types = method_type_encoding(&Ret::ENCODING, encs);
unsafe {
ffi::protocol_addMethodDescription(
self.as_mut_ptr(),
sel.as_ptr(),
types.as_ptr(),
Bool::new(is_required).as_raw(),
Bool::new(is_instance_method).as_raw(),
);
}
}
pub fn add_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where
Args: EncodeArguments,
Ret: Encode,
{
self.add_method_description_common::<Args, Ret>(sel, is_required, true)
}
pub fn add_class_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where
Args: EncodeArguments,
Ret: Encode,
{
self.add_method_description_common::<Args, Ret>(sel, is_required, false)
}
pub fn add_protocol(&mut self, proto: &Protocol) {
unsafe {
ffi::protocol_addProtocol(self.as_mut_ptr(), proto.as_ptr());
}
}
pub fn register(mut self) -> &'static Protocol {
unsafe {
ffi::objc_registerProtocol(self.as_mut_ptr());
self.proto.as_ref()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::msg_send;
use crate::test_utils;
#[test]
fn test_classbuilder_duplicate() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicate", cls).unwrap();
let _ = builder.register();
assert!(ClassBuilder::new("TestClassBuilderDuplicate", cls).is_none());
}
#[test]
#[should_panic = "Failed to add ivar xyz"]
fn duplicate_ivar() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateIvar", cls).unwrap();
let mut builder = ManuallyDrop::new(builder);
builder.add_ivar::<i32>("xyz");
builder.add_ivar::<i32>("xyz");
}
#[test]
#[should_panic = "Failed to add method xyz"]
fn duplicate_method() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateMethod", cls).unwrap();
let mut builder = ManuallyDrop::new(builder);
extern "C" fn xyz(_this: &Object, _cmd: Sel) {}
unsafe {
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
}
}
#[test]
#[should_panic = "Failed to add protocol NSObject"]
fn duplicate_protocol() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDuplicateProtocol", cls).unwrap();
let mut builder = ManuallyDrop::new(builder);
let protocol = Protocol::get("NSObject").unwrap();
builder.add_protocol(protocol);
builder.add_protocol(protocol);
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Dropping ClassBuilder has weird threading side effects on GNUStep"
)]
fn test_classbuilder_drop() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new("TestClassBuilderDrop", cls).unwrap();
drop(builder);
let _builder = ClassBuilder::new("TestClassBuilderDrop", cls).unwrap();
}
#[test]
fn test_custom_class() {
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 13u32] };
let result: u32 = unsafe { msg_send![&obj, foo] };
assert_eq!(result, 13);
}
#[test]
#[cfg(feature = "malloc")]
fn test_in_all_classes() {
fn is_present(cls: *const Class) -> bool {
Class::classes()
.into_iter()
.find(|&item| ptr::eq(cls, *item))
.is_some()
}
let superclass = test_utils::custom_class();
let builder = ClassBuilder::new("TestFetchWhileCreatingClass", superclass).unwrap();
if cfg!(all(feature = "apple", target_arch = "x86_64")) {
assert!(is_present(builder.cls.as_ptr()));
} else {
assert!(!is_present(builder.cls.as_ptr()));
}
let cls = builder.register();
assert!(is_present(cls));
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
let result: u32 = unsafe { msg_send![cls, classFoo] };
assert_eq!(result, 7);
}
}