#![doc = include_str!("../../examples/introspection.rs")]
#[cfg(feature = "malloc")]
use alloc::vec::Vec;
use core::fmt;
use core::hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use core::str;
#[cfg(feature = "malloc")]
use malloc_buf::Malloc;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[cfg(feature = "malloc")]
use std::os::raw::c_uint;
#[doc(hidden)]
pub mod __nsstring;
mod bool;
mod method_encoding_iter;
mod nscopying;
mod nsobject;
mod nsproxy;
mod nszone;
mod protocol_object;
mod retain_release_fast;
pub(crate) use self::method_encoding_iter::{EncodingParseError, MethodEncodingIter};
pub(crate) use self::retain_release_fast::{objc_release_fast, objc_retain_fast};
use crate::encode::__unstable::{EncodeArguments, EncodeConvertReturn, EncodeReturn};
use crate::encode::{Encode, Encoding, OptionEncode, RefEncode};
use crate::verify::{verify_method_signature, Inner};
use crate::{ffi, Message};
#[doc(hidden)]
pub use self::nscopying::{
Copyhelper as __Copyhelper, NSCopying as __NSCopying, NSMutableCopying as __NSMutableCopying,
};
#[doc(hidden)]
pub use self::nsproxy::NSProxy as __NSProxy;
pub use self::bool::Bool;
pub use self::nsobject::{NSObject, NSObjectProtocol};
pub use self::nszone::NSZone;
pub use self::protocol_object::{ImplementedBy, ProtocolObject};
pub use crate::verify::VerificationError;
macro_rules! standard_pointer_impls {
($name:ident) => {
impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_ptr() == other.as_ptr()
}
}
impl Eq for $name {}
impl hash::Hash for $name {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_ptr().hash(state)
}
}
};
}
#[deprecated = "Use `Bool` or `ffi::BOOL` instead"]
#[allow(non_upper_case_globals)]
pub type BOOL = ffi::BOOL;
#[deprecated = "Use `Bool::YES` or `ffi::YES` instead"]
pub const YES: ffi::BOOL = ffi::YES;
#[deprecated = "Use `Bool::NO` or `ffi::NO` instead"]
pub const NO: ffi::BOOL = ffi::NO;
#[cfg(not(feature = "unstable-c-unwind"))]
type InnerImp = unsafe extern "C" fn();
#[cfg(feature = "unstable-c-unwind")]
type InnerImp = unsafe extern "C-unwind" fn();
pub type Imp = InnerImp;
#[repr(transparent)]
#[derive(Copy, Clone)]
#[doc(alias = "SEL")]
#[doc(alias = "objc_selector")]
pub struct Sel {
ptr: NonNull<ffi::objc_selector>,
}
unsafe impl Sync for Sel {}
unsafe impl Send for Sel {}
impl UnwindSafe for Sel {}
impl RefUnwindSafe for Sel {}
impl Sel {
#[inline]
#[doc(hidden)]
pub const unsafe fn __internal_from_ptr(ptr: *const ffi::objc_selector) -> Self {
let ptr = unsafe { NonNull::new_unchecked(ptr as *mut ffi::objc_selector) };
Self { ptr }
}
#[inline]
pub(crate) unsafe fn from_ptr(ptr: *const ffi::objc_selector) -> Option<Self> {
NonNull::new(ptr as *mut ffi::objc_selector).map(|ptr| Self { ptr })
}
#[inline]
pub const fn as_ptr(&self) -> *const ffi::objc_selector {
self.ptr.as_ptr()
}
pub(crate) unsafe fn register_unchecked(name: *const c_char) -> Self {
let ptr = unsafe { ffi::sel_registerName(name) };
unsafe { Self::from_ptr(ptr).expect("failed allocating selector") }
}
#[doc(alias = "sel_registerName")]
pub fn register(name: &str) -> Self {
let name = CString::new(name).unwrap();
unsafe { Self::register_unchecked(name.as_ptr()) }
}
#[doc(alias = "sel_getName")]
pub fn name(&self) -> &str {
let ptr = unsafe { ffi::sel_getName(self.as_ptr()) };
let name = unsafe { CStr::from_ptr(ptr) };
str::from_utf8(name.to_bytes()).unwrap()
}
pub(crate) fn number_of_arguments(self) -> usize {
self.name()
.as_bytes()
.iter()
.filter(|&&b| b == b':')
.count()
}
}
#[cfg(feature = "apple")]
standard_pointer_impls!(Sel);
#[cfg(not(feature = "apple"))]
impl PartialEq for Sel {
#[inline]
fn eq(&self, other: &Self) -> bool {
unsafe { Bool::from_raw(ffi::sel_isEqual(self.as_ptr(), other.as_ptr())).as_bool() }
}
}
#[cfg(not(feature = "apple"))]
impl Eq for Sel {}
#[cfg(not(feature = "apple"))]
impl hash::Hash for Sel {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
unsafe impl Encode for Sel {
const ENCODING: Encoding = Encoding::Sel;
}
unsafe impl OptionEncode for Sel {}
impl fmt::Display for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.name(), f)
}
}
impl fmt::Debug for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Sel").field(&self.name()).finish()
}
}
impl fmt::Pointer for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr, f)
}
}
#[repr(C)]
#[doc(alias = "objc_ivar")]
pub struct Ivar(ffi::objc_ivar);
unsafe impl Sync for Ivar {}
unsafe impl Send for Ivar {}
impl UnwindSafe for Ivar {}
impl RefUnwindSafe for Ivar {}
impl Ivar {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_ivar {
let ptr: *const Self = self;
ptr.cast()
}
#[doc(alias = "ivar_getName")]
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::ivar_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
#[inline]
#[doc(alias = "ivar_getOffset")]
pub fn offset(&self) -> isize {
unsafe { ffi::ivar_getOffset(self.as_ptr()) }
}
#[doc(alias = "ivar_getTypeEncoding")]
pub fn type_encoding(&self) -> &str {
let encoding = unsafe { CStr::from_ptr(ffi::ivar_getTypeEncoding(self.as_ptr())) };
str::from_utf8(encoding.to_bytes()).unwrap()
}
}
standard_pointer_impls!(Ivar);
impl fmt::Debug for Ivar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ivar")
.field("name", &self.name())
.field("offset", &self.offset())
.field("type_encoding", &self.type_encoding())
.finish_non_exhaustive()
}
}
#[cfg_attr(not(feature = "malloc"), allow(dead_code))]
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct MethodDescription {
pub(crate) sel: Sel,
pub(crate) types: &'static str,
}
impl MethodDescription {
#[cfg_attr(not(feature = "malloc"), allow(dead_code))]
pub(crate) unsafe fn from_raw(raw: ffi::objc_method_description) -> Option<Self> {
let sel = unsafe { Sel::from_ptr(raw.name) }?;
if raw.types.is_null() {
return None;
}
let types = unsafe { CStr::from_ptr(raw.types) }.to_str().unwrap();
Some(Self { sel, types })
}
}
#[repr(C)]
#[doc(alias = "objc_method")]
pub struct Method(ffi::objc_method);
unsafe impl Sync for Method {}
unsafe impl Send for Method {}
impl UnwindSafe for Method {}
impl RefUnwindSafe for Method {}
impl Method {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_method {
let ptr: *const Self = self;
ptr.cast()
}
pub(crate) fn as_mut_ptr(&self) -> *mut ffi::objc_method {
self.as_ptr() as _
}
#[doc(alias = "method_getName")]
pub fn name(&self) -> Sel {
unsafe { Sel::from_ptr(ffi::method_getName(self.as_ptr())).unwrap() }
}
#[cfg(feature = "malloc")]
#[doc(alias = "method_copyReturnType")]
pub fn return_type(&self) -> Malloc<str> {
unsafe {
let encoding = ffi::method_copyReturnType(self.as_ptr());
Malloc::from_c_str(encoding).unwrap()
}
}
#[cfg(feature = "malloc")]
#[doc(alias = "method_copyArgumentType")]
pub fn argument_type(&self, index: usize) -> Option<Malloc<str>> {
unsafe {
let encoding = ffi::method_copyArgumentType(self.as_ptr(), index as c_uint);
NonNull::new(encoding).map(|encoding| Malloc::from_c_str(encoding.as_ptr()).unwrap())
}
}
#[doc(alias = "method_getTypeEncoding")]
pub(crate) fn types(&self) -> MethodEncodingIter<'_> {
let cstr = unsafe { ffi::method_getTypeEncoding(self.as_ptr()) };
if cstr.is_null() {
panic!("method type encoding was NULL");
}
let encoding = unsafe { CStr::from_ptr(cstr) };
let s = str::from_utf8(encoding.to_bytes()).expect("method type encoding to be UTF-8");
MethodEncodingIter::new(s)
}
#[doc(alias = "method_getNumberOfArguments")]
pub fn arguments_count(&self) -> usize {
unsafe { ffi::method_getNumberOfArguments(self.as_ptr()) as usize }
}
#[doc(alias = "method_getImplementation")]
pub fn implementation(&self) -> Imp {
unsafe { ffi::method_getImplementation(self.as_ptr()).expect("null IMP") }
}
#[inline]
#[doc(alias = "method_setImplementation")]
pub unsafe fn set_implementation(&self, imp: Imp) -> Imp {
unsafe { ffi::method_setImplementation(self.as_mut_ptr(), Some(imp)).expect("null IMP") }
}
#[inline]
#[doc(alias = "method_exchangeImplementations")]
pub unsafe fn exchange_implementation(&self, other: &Self) {
unsafe { ffi::method_exchangeImplementations(self.as_mut_ptr(), other.as_mut_ptr()) }
}
}
standard_pointer_impls!(Method);
impl fmt::Debug for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Method")
.field("name", &self.name())
.field("types", &self.types())
.field("implementation", &self.implementation())
.finish_non_exhaustive()
}
}
#[repr(C)]
#[doc(alias = "Class")]
#[doc(alias = "objc_class")]
pub struct AnyClass(ffi::objc_class);
#[deprecated = "renamed to `runtime::AnyClass`"]
pub type Class = AnyClass;
unsafe impl Sync for AnyClass {}
unsafe impl Send for AnyClass {}
impl UnwindSafe for AnyClass {}
impl RefUnwindSafe for AnyClass {}
impl AnyClass {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_class {
let ptr: *const Self = self;
ptr.cast()
}
#[doc(alias = "objc_getClass")]
pub fn get(name: &str) -> Option<&'static Self> {
let name = CString::new(name).unwrap();
let cls = unsafe { ffi::objc_getClass(name.as_ptr()) };
unsafe { cls.cast::<Self>().as_ref() }
}
#[cfg(feature = "malloc")]
#[doc(alias = "objc_copyClassList")]
pub fn classes() -> Malloc<[&'static Self]> {
unsafe {
let mut count: c_uint = 0;
let classes: *mut &Self = ffi::objc_copyClassList(&mut count).cast();
Malloc::from_array(classes, count as usize)
}
}
#[doc(alias = "objc_getClassList")]
pub fn classes_count() -> usize {
unsafe { ffi::objc_getClassList(ptr::null_mut(), 0) as usize }
}
pub(crate) unsafe fn name_raw<'a>(ptr: *const ffi::objc_class) -> &'a str {
let name = unsafe { ffi::class_getName(ptr) };
if name.is_null() {
panic!("class name was NULL");
}
let name = unsafe { CStr::from_ptr(name) };
str::from_utf8(name.to_bytes()).unwrap()
}
#[doc(alias = "class_getName")]
pub fn name(&self) -> &str {
unsafe { Self::name_raw(self.as_ptr()) }
}
#[inline]
pub(crate) unsafe fn superclass_raw<'a>(ptr: *const ffi::objc_class) -> Option<&'a AnyClass> {
let superclass = unsafe { ffi::class_getSuperclass(ptr) };
let superclass: *const AnyClass = superclass.cast();
unsafe { superclass.as_ref() }
}
#[inline]
#[doc(alias = "class_getSuperclass")]
pub fn superclass(&self) -> Option<&AnyClass> {
unsafe { Self::superclass_raw(self.as_ptr()) }
}
#[inline]
#[doc(alias = "object_getClass")]
pub fn metaclass(&self) -> &Self {
let ptr: *const Self = unsafe { ffi::object_getClass(self.as_ptr().cast()) }.cast();
unsafe { ptr.as_ref().unwrap_unchecked() }
}
#[allow(unused)]
pub(crate) fn is_metaclass(&self) -> bool {
unsafe { Bool::from_raw(ffi::class_isMetaClass(self.as_ptr())).as_bool() }
}
#[inline]
#[doc(alias = "class_getInstanceSize")]
pub fn instance_size(&self) -> usize {
unsafe { ffi::class_getInstanceSize(self.as_ptr()) }
}
#[inline]
#[doc(alias = "class_getInstanceMethod")]
pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
unsafe {
let method = ffi::class_getInstanceMethod(self.as_ptr(), sel.as_ptr());
method.cast::<Method>().as_ref()
}
}
#[inline]
#[doc(alias = "class_getClassMethod")]
pub fn class_method(&self, sel: Sel) -> Option<&Method> {
unsafe {
let method = ffi::class_getClassMethod(self.as_ptr(), sel.as_ptr());
method.cast::<Method>().as_ref()
}
}
#[doc(alias = "class_getInstanceVariable")]
pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
unsafe {
let ivar = ffi::class_getInstanceVariable(self.as_ptr(), name.as_ptr());
ivar.cast::<Ivar>().as_ref()
}
}
#[allow(unused)]
#[doc(alias = "class_getIvarLayout")]
fn instance_variable_layout(&self) -> Option<&[u8]> {
let layout: *const c_char = unsafe { ffi::class_getIvarLayout(self.as_ptr()).cast() };
if layout.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(layout) }.to_bytes())
}
}
#[allow(unused)]
#[doc(alias = "class_getClassVariable")]
fn class_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
let ivar = unsafe { ffi::class_getClassVariable(self.as_ptr(), name.as_ptr()) };
unsafe { ivar.cast::<Ivar>().as_ref() }
}
#[cfg(feature = "malloc")]
#[doc(alias = "class_copyMethodList")]
pub fn instance_methods(&self) -> Malloc<[&Method]> {
unsafe {
let mut count: c_uint = 0;
let methods: *mut &Method = ffi::class_copyMethodList(self.as_ptr(), &mut count).cast();
Malloc::from_array(methods, count as usize)
}
}
#[doc(alias = "class_conformsToProtocol")]
pub fn conforms_to(&self, proto: &AnyProtocol) -> bool {
unsafe {
Bool::from_raw(ffi::class_conformsToProtocol(self.as_ptr(), proto.as_ptr())).as_bool()
}
}
#[cfg(feature = "malloc")]
#[doc(alias = "class_copyProtocolList")]
pub fn adopted_protocols(&self) -> Malloc<[&AnyProtocol]> {
unsafe {
let mut count: c_uint = 0;
let protos: *mut &AnyProtocol =
ffi::class_copyProtocolList(self.as_ptr(), &mut count).cast();
Malloc::from_array(protos, count as usize)
}
}
#[cfg(feature = "malloc")]
#[doc(alias = "class_copyIvarList")]
pub fn instance_variables(&self) -> Malloc<[&Ivar]> {
unsafe {
let mut count: c_uint = 0;
let ivars: *mut &Ivar = ffi::class_copyIvarList(self.as_ptr(), &mut count).cast();
Malloc::from_array(ivars, count as usize)
}
}
#[inline]
#[doc(alias = "class_respondsToSelector")]
pub fn responds_to(&self, sel: Sel) -> bool {
let res = unsafe { ffi::class_respondsToSelector(self.as_ptr(), sel.as_ptr()) };
Bool::from_raw(res).as_bool()
}
pub fn verify_sel<A, R>(&self, sel: Sel) -> Result<(), VerificationError>
where
A: EncodeArguments,
R: EncodeConvertReturn,
{
let method = self.instance_method(sel).ok_or(Inner::MethodNotFound)?;
verify_method_signature(method, A::ENCODINGS, &R::__Inner::ENCODING_RETURN)
}
}
standard_pointer_impls!(AnyClass);
unsafe impl RefEncode for AnyClass {
const ENCODING_REF: Encoding = Encoding::Class;
}
impl fmt::Debug for AnyClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AnyClass")
.field("name", &self.name())
.finish_non_exhaustive()
}
}
impl fmt::Display for AnyClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.name(), f)
}
}
#[repr(C)]
#[doc(alias = "objc_protocol")]
pub struct AnyProtocol(ffi::objc_protocol);
#[deprecated = "renamed to `runtime::AnyProtocol`"]
pub type Protocol = AnyProtocol;
unsafe impl Sync for AnyProtocol {}
unsafe impl Send for AnyProtocol {}
impl UnwindSafe for AnyProtocol {}
impl RefUnwindSafe for AnyProtocol {}
impl AnyProtocol {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_protocol {
let ptr: *const Self = self;
ptr.cast()
}
#[doc(alias = "objc_getProtocol")]
pub fn get(name: &str) -> Option<&'static Self> {
let name = CString::new(name).unwrap();
unsafe {
let proto = ffi::objc_getProtocol(name.as_ptr());
proto.cast::<Self>().as_ref()
}
}
#[cfg(feature = "malloc")]
#[doc(alias = "objc_copyProtocolList")]
pub fn protocols() -> Malloc<[&'static Self]> {
unsafe {
let mut count: c_uint = 0;
let protocols: *mut &Self = ffi::objc_copyProtocolList(&mut count).cast();
Malloc::from_array(protocols, count as usize)
}
}
#[cfg(feature = "malloc")]
#[doc(alias = "protocol_copyProtocolList")]
pub fn adopted_protocols(&self) -> Malloc<[&AnyProtocol]> {
unsafe {
let mut count: c_uint = 0;
let protocols: *mut &AnyProtocol =
ffi::protocol_copyProtocolList(self.as_ptr(), &mut count).cast();
Malloc::from_array(protocols, count as usize)
}
}
#[doc(alias = "protocol_conformsToProtocol")]
pub fn conforms_to(&self, proto: &AnyProtocol) -> bool {
unsafe {
Bool::from_raw(ffi::protocol_conformsToProtocol(
self.as_ptr(),
proto.as_ptr(),
))
.as_bool()
}
}
#[doc(alias = "protocol_getName")]
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::protocol_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
#[cfg(feature = "malloc")]
fn method_descriptions_inner(&self, required: bool, instance: bool) -> Vec<MethodDescription> {
let mut count: c_uint = 0;
let descriptions = unsafe {
ffi::protocol_copyMethodDescriptionList(
self.as_ptr(),
Bool::new(required).as_raw(),
Bool::new(instance).as_raw(),
&mut count,
)
};
if descriptions.is_null() {
return Vec::new();
}
let descriptions = unsafe { Malloc::from_array(descriptions, count as usize) };
descriptions
.iter()
.map(|desc| {
unsafe { MethodDescription::from_raw(*desc) }.expect("invalid method description")
})
.collect()
}
#[cfg(feature = "malloc")]
#[allow(dead_code)]
#[doc(alias = "protocol_copyMethodDescriptionList")]
pub(crate) fn method_descriptions(&self, required: bool) -> Vec<MethodDescription> {
self.method_descriptions_inner(required, true)
}
#[cfg(feature = "malloc")]
#[allow(dead_code)]
#[doc(alias = "protocol_copyMethodDescriptionList")]
pub(crate) fn class_method_descriptions(&self, required: bool) -> Vec<MethodDescription> {
self.method_descriptions_inner(required, false)
}
}
impl PartialEq for AnyProtocol {
#[inline]
#[doc(alias = "protocol_isEqual")]
fn eq(&self, other: &Self) -> bool {
unsafe { Bool::from_raw(ffi::protocol_isEqual(self.as_ptr(), other.as_ptr())).as_bool() }
}
}
impl Eq for AnyProtocol {}
unsafe impl RefEncode for AnyProtocol {
const ENCODING_REF: Encoding = Encoding::Object;
}
impl fmt::Debug for AnyProtocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AnyProtocol")
.field("name", &self.name())
.finish_non_exhaustive()
}
}
impl fmt::Display for AnyProtocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.name(), f)
}
}
pub(crate) fn ivar_offset(cls: &AnyClass, name: &str, expected: &Encoding) -> isize {
match cls.instance_variable(name) {
Some(ivar) => {
let encoding = ivar.type_encoding();
assert!(
expected.equivalent_to_str(encoding),
"wrong encoding. Tried to retrieve ivar with encoding {encoding}, but the encoding of the given type was {expected}",
);
ivar.offset()
}
None => panic!("ivar {name} not found on class {cls}"),
}
}
#[doc(alias = "id")]
#[repr(C)]
pub struct AnyObject(ffi::objc_object);
#[deprecated = "renamed to `runtime::AnyObject`. Consider using the correct type from `icrate` instead though"]
pub type Object = AnyObject;
unsafe impl RefEncode for AnyObject {
const ENCODING_REF: Encoding = Encoding::Object;
}
unsafe impl Message for AnyObject {}
impl AnyObject {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_object {
let ptr: *const Self = self;
ptr.cast()
}
#[doc(alias = "object_getClass")]
pub fn class(&self) -> &AnyClass {
let ptr: *const AnyClass = unsafe { ffi::object_getClass(self.as_ptr()) }.cast();
unsafe { ptr.as_ref().unwrap_unchecked() }
}
#[inline]
#[doc(alias = "object_setClass")]
pub unsafe fn set_class<'s>(this: &Self, cls: &AnyClass) -> &'s AnyClass {
let ptr =
unsafe { ffi::object_setClass(this.as_ptr() as *mut ffi::objc_object, cls.as_ptr()) };
let ptr: *const AnyClass = ptr.cast();
let old_cls = unsafe { ptr.as_ref().unwrap_unchecked() };
debug_assert_eq!(
old_cls.instance_size(),
cls.instance_size(),
"old and new class sizes were not equal; this is UB!"
);
old_cls
}
#[inline]
pub(crate) unsafe fn ivar_at_offset<T>(ptr: NonNull<Self>, offset: isize) -> NonNull<T> {
let ptr: NonNull<u8> = ptr.cast();
let ptr: *mut u8 = ptr.as_ptr();
let ptr: *mut u8 = unsafe { ptr.offset(offset) };
let ptr: NonNull<u8> = unsafe { NonNull::new_unchecked(ptr) };
let ptr: NonNull<T> = ptr.cast();
ptr
}
pub unsafe fn ivar_ptr<T: Encode>(&self, name: &str) -> *mut T {
let offset = ivar_offset(self.class(), name, &T::ENCODING);
let ptr = NonNull::from(self);
let ptr = unsafe { Self::ivar_at_offset::<T>(ptr, offset) };
ptr.as_ptr()
}
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
unsafe { self.ivar_ptr::<T>(name).as_ref().unwrap_unchecked() }
}
#[deprecated = "Use `AnyObject::ivar` instead."]
pub unsafe fn get_ivar<T: Encode>(&self, name: &str) -> &T {
unsafe { self.ivar::<T>(name) }
}
pub unsafe fn ivar_mut<T: Encode>(&mut self, name: &str) -> &mut T {
let offset = ivar_offset(self.class(), name, &T::ENCODING);
let ptr = NonNull::from(self);
let mut ptr = unsafe { Self::ivar_at_offset::<T>(ptr, offset) };
unsafe { ptr.as_mut() }
}
#[deprecated = "Use `AnyObject::ivar_mut` instead."]
pub unsafe fn get_mut_ivar<T: Encode>(&mut self, name: &str) -> &mut T {
unsafe { self.ivar_mut::<T>(name) }
}
pub unsafe fn set_ivar<T: Encode>(&mut self, name: &str, value: T) {
unsafe { *self.ivar_mut::<T>(name) = value };
}
}
impl fmt::Debug for AnyObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<{}: {:p}>", self.class().name(), self.as_ptr())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::*;
use crate::test_utils;
use crate::MessageReceiver;
use crate::{msg_send, sel};
#[test]
fn test_selector() {
macro_rules! test_sel {
($s:literal, $($tt:tt)+) => {{
let sel = sel!($($tt)*);
let expected = Sel::register($s);
assert_eq!(sel, expected);
assert_eq!(sel.name(), $s);
}}
}
test_sel!("abc", abc);
test_sel!("abc:", abc:);
test_sel!("abc:def:", abc:def:);
test_sel!("abc:def:ghi:", abc:def:ghi:);
test_sel!("functionWithControlPoints::::", functionWithControlPoints::::);
test_sel!("initWithControlPoints::::", initWithControlPoints::::);
test_sel!("setFloatValue::", setFloatValue::);
test_sel!("isSupported::", isSupported::);
test_sel!("addEventListener:::", addEventListener:::);
test_sel!("test::arg::", test::arg::);
test_sel!("test::::with::spaces::", test : :: : with : : spaces : :);
test_sel!("a::b:", a::b:);
}
#[test]
fn test_empty_selector() {
let sel = Sel::register("");
assert_eq!(sel.name(), "");
let sel = Sel::register(":");
assert_eq!(sel.name(), ":");
let sel = Sel::register("::");
assert_eq!(sel.name(), "::");
}
#[test]
#[should_panic = "NulError"]
fn test_sel_register_null() {
let _ = Sel::register("\0");
}
#[test]
fn test_ivar() {
let cls = test_utils::custom_class();
let ivar = cls.instance_variable("_foo").unwrap();
assert_eq!(ivar.name(), "_foo");
assert!(<u32>::ENCODING.equivalent_to_str(ivar.type_encoding()));
assert!(ivar.offset() > 0);
#[cfg(feature = "malloc")]
assert!(cls.instance_variables().len() > 0);
}
#[test]
fn test_instance_method() {
let cls = test_utils::custom_class();
let sel = Sel::register("foo");
let method = cls.instance_method(sel).unwrap();
assert_eq!(method.name().name(), "foo");
assert_eq!(method.arguments_count(), 2);
#[cfg(feature = "malloc")]
{
assert!(<u32>::ENCODING.equivalent_to_str(&method.return_type()));
assert!(Sel::ENCODING.equivalent_to_str(&method.argument_type(1).unwrap()));
assert!(cls.instance_methods().iter().any(|m| *m == method));
}
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
let method = cls.class_method(sel!(classFoo)).unwrap();
assert_eq!(method.name().name(), "classFoo");
assert_eq!(method.arguments_count(), 2);
#[cfg(feature = "malloc")]
{
assert!(<u32>::ENCODING.equivalent_to_str(&method.return_type()));
assert!(Sel::ENCODING.equivalent_to_str(&method.argument_type(1).unwrap()));
assert!(cls
.metaclass()
.instance_methods()
.iter()
.any(|m| *m == method));
}
}
#[test]
fn test_class() {
let cls = test_utils::custom_class();
assert_eq!(cls.name(), "CustomObject");
assert!(cls.instance_size() > 0);
assert!(cls.superclass().is_none());
assert!(cls.responds_to(sel!(foo)));
assert!(cls.responds_to(sel!(setBar:)));
assert!(cls.responds_to(sel!(test::test::)));
assert!(!cls.responds_to(sel!(abc)));
assert!(!cls.responds_to(sel!(addNumber:toNumber:)));
assert_eq!(AnyClass::get(cls.name()), Some(cls));
let metaclass = cls.metaclass();
assert_eq!(metaclass.superclass().unwrap(), cls);
assert!(metaclass.responds_to(sel!(addNumber:toNumber:)));
assert!(metaclass.responds_to(sel!(test::test::)));
assert!(metaclass.responds_to(sel!(foo)));
let subclass = test_utils::custom_subclass();
assert_eq!(subclass.superclass().unwrap(), cls);
}
#[test]
fn test_classes_count() {
assert!(AnyClass::classes_count() > 0);
}
#[test]
#[cfg(feature = "malloc")]
fn test_classes() {
let classes = AnyClass::classes();
assert!(classes.len() > 0);
}
#[test]
fn test_protocol() {
let proto = test_utils::custom_protocol();
assert_eq!(proto.name(), "CustomProtocol");
let class = test_utils::custom_class();
assert!(class.conforms_to(proto));
#[cfg(feature = "malloc")]
{
if cfg!(any(not(feature = "gnustep-1-7"), feature = "gnustep-2-0")) {
let desc = MethodDescription {
sel: sel!(setBar:),
types: "v@:i",
};
assert_eq!(&proto.method_descriptions(true), &[desc]);
let desc = MethodDescription {
sel: sel!(getName),
types: "*@:",
};
assert_eq!(&proto.method_descriptions(false), &[desc]);
let desc = MethodDescription {
sel: sel!(addNumber:toNumber:),
types: "i@:ii",
};
assert_eq!(&proto.class_method_descriptions(true), &[desc]);
}
assert_eq!(&proto.class_method_descriptions(false), &[]);
assert!(class.adopted_protocols().iter().any(|p| *p == proto));
}
}
#[test]
fn test_protocol_method() {
let class = test_utils::custom_class();
let result: i32 = unsafe { msg_send![class, addNumber: 1, toNumber: 2] };
assert_eq!(result, 3);
}
#[test]
fn test_subprotocols() {
let sub_proto = test_utils::custom_subprotocol();
let super_proto = test_utils::custom_protocol();
assert!(sub_proto.conforms_to(super_proto));
#[cfg(feature = "malloc")]
assert_eq!(sub_proto.adopted_protocols()[0], super_proto);
}
#[test]
fn test_protocols() {
let _ = test_utils::custom_protocol();
#[cfg(feature = "malloc")]
assert!(AnyProtocol::protocols().len() > 0);
}
#[test]
fn test_object() {
let mut obj = test_utils::custom_object();
assert_eq!(obj.class(), test_utils::custom_class());
let result = unsafe {
obj.set_ivar::<u32>("_foo", 4);
*obj.ivar::<u32>("_foo")
};
assert_eq!(result, 4);
}
#[test]
#[should_panic = "ivar unknown not found on class CustomObject"]
fn test_object_ivar_unknown() {
let obj = test_utils::custom_object();
let _ = unsafe { *obj.ivar::<u32>("unknown") };
}
#[test]
#[should_panic = "wrong encoding. Tried to retrieve ivar with encoding I, but the encoding of the given type was C"]
fn test_object_ivar_wrong_type() {
let obj = test_utils::custom_object();
let _ = unsafe { *obj.ivar::<u8>("_foo") };
}
#[test]
fn test_encode() {
fn assert_enc<T: Encode>(expected: &str) {
assert_eq!(&T::ENCODING.to_string(), expected);
}
assert_enc::<&AnyObject>("@");
assert_enc::<*mut AnyObject>("@");
assert_enc::<&AnyClass>("#");
assert_enc::<Sel>(":");
assert_enc::<Option<Sel>>(":");
assert_enc::<Imp>("^?");
assert_enc::<Option<Imp>>("^?");
assert_enc::<&AnyProtocol>("@");
}
#[test]
fn test_send_sync() {
fn assert_send_sync<T: Send + Sync + ?Sized>() {}
assert_send_sync::<Bool>();
assert_send_sync::<AnyClass>();
assert_send_sync::<Ivar>();
assert_send_sync::<Method>();
assert_send_sync::<AnyProtocol>();
assert_send_sync::<Sel>();
}
#[test]
fn test_debug_display() {
let sel = sel!(abc:);
assert_eq!(format!("{sel}"), "abc:");
assert_eq!(format!("{sel:?}"), "Sel(\"abc:\")");
let cls = test_utils::custom_class();
assert_eq!(format!("{cls}"), "CustomObject");
assert_eq!(
format!("{cls:?}"),
"AnyClass { name: \"CustomObject\", .. }"
);
let protocol = test_utils::custom_protocol();
assert_eq!(format!("{protocol}"), "CustomProtocol");
assert_eq!(
format!("{protocol:?}"),
"AnyProtocol { name: \"CustomProtocol\", .. }"
);
let object = test_utils::custom_object();
assert_eq!(
format!("{:?}", &*object),
format!("<CustomObject: {:p}>", &*object)
);
}
#[test]
fn test_multiple_colon() {
let class = test_utils::custom_class();
let res: i32 = unsafe {
MessageReceiver::send_message(class, sel!(test::test::), (1i32, 2i32, 3i32, 4i32))
};
assert_eq!(res, 10);
let obj = test_utils::custom_object();
let res: i32 = unsafe {
MessageReceiver::send_message(&obj, sel!(test::test::), (1i32, 2i32, 3i32, 4i32))
};
assert_eq!(res, 24);
}
}