use std::ffi::CString;
use std::mem;
use libc::size_t;
use {Encode, Message};
use runtime::{Class, Imp, NO, Object, Sel, self};
pub trait IntoMethodImp {
type Callee: Message;
type Ret;
fn method_encoding() -> String;
fn into_imp(self, sel: Sel) -> Result<Imp, ()>;
}
macro_rules! count_idents {
() => (0);
($a:ident) => (1);
($a:ident, $($b:ident),+) => (1 + count_idents!($($b),*));
}
macro_rules! method_decl_impl {
(-$s:ident, $sp:ty, $($t:ident),*) => (
impl<$s, R $(, $t)*> IntoMethodImp for extern fn($sp, Sel $(, $t)*) -> R
where $s: Message, R: Encode $(, $t: Encode)* {
type Callee = $s;
type Ret = R;
fn method_encoding() -> String {
let types = [
R::encode(),
<*mut Object>::encode(),
Sel::encode(),
$($t::encode()),*
];
types.iter().map(|s| s.as_str()).collect()
}
fn into_imp(self, sel: Sel) -> Result<Imp, ()> {
let num_args = count_idents!($($t),*);
if sel.name().chars().filter(|&c| c == ':').count() == num_args {
unsafe { Ok(mem::transmute(self)) }
} else {
Err(())
}
}
}
);
($($t:ident),*) => (
method_decl_impl!(-T, &T, $($t),*);
method_decl_impl!(-T, &mut T, $($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);
pub struct ClassDecl {
cls: *mut Class,
}
impl ClassDecl {
pub fn new(superclass: &Class, name: &str) -> Option<ClassDecl> {
let name = CString::new(name).unwrap();
let cls = unsafe {
runtime::objc_allocateClassPair(superclass, name.as_ptr(), 0)
};
if cls.is_null() {
None
} else {
Some(ClassDecl { cls: cls })
}
}
pub fn add_method<F>(&mut self, sel: Sel, func: F) where F: IntoMethodImp {
let types = CString::new(F::method_encoding()).unwrap();
let imp = func.into_imp(sel).unwrap();
let success = unsafe {
runtime::class_addMethod(self.cls, sel, imp, types.as_ptr())
};
assert!(success != NO, "Failed to add method {:?}", sel);
}
pub fn add_class_method<F>(&mut self, sel: Sel, func: F)
where F: IntoMethodImp<Callee=Class> {
let types = CString::new(F::method_encoding()).unwrap();
let imp = func.into_imp(sel).unwrap();
let success = unsafe {
let cls_obj = self.cls as *const Object;
let metaclass = runtime::object_getClass(cls_obj) as *mut Class;
runtime::class_addMethod(metaclass, sel, imp, types.as_ptr())
};
assert!(success != NO, "Failed to add class method {:?}", sel);
}
pub fn add_ivar<T>(&mut self, name: &str) where T: Encode {
let c_name = CString::new(name).unwrap();
let encoding = T::encode();
let size = mem::size_of::<T>() as size_t;
let align = mem::align_of::<T>() as u8;
let success = unsafe {
runtime::class_addIvar(self.cls, c_name.as_ptr(), size, align,
encoding.as_ptr())
};
assert!(success != NO, "Failed to add ivar {}", name);
}
pub fn register(self) -> &'static Class {
unsafe {
let cls = self.cls;
runtime::objc_registerClassPair(cls);
mem::forget(self);
&*cls
}
}
}
impl Drop for ClassDecl {
fn drop(&mut self) {
unsafe {
runtime::objc_disposeClassPair(self.cls);
}
}
}
#[cfg(test)]
mod tests {
use runtime::{Object, Sel};
use test_utils;
use super::IntoMethodImp;
#[test]
fn test_custom_class() {
let obj = test_utils::custom_object();
unsafe {
let _: () = msg_send![obj, setFoo:13u32];
let result: u32 = msg_send![obj, foo];
assert!(result == 13);
}
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
unsafe {
let result: u32 = msg_send![cls, classFoo];
assert!(result == 7);
}
}
#[test]
fn test_mismatched_args() {
extern fn wrong_num_args_method(_obj: &Object, _cmd: Sel, _a: i32) { }
let sel = sel!(doSomethingWithFoo:bar:);
let f: extern fn(&Object, Sel, i32) = wrong_num_args_method;
let imp = f.into_imp(sel);
assert!(imp.is_err());
}
}