use core::fmt; use core::hash; use core::marker::PhantomData; use core::ptr::NonNull; use crate::encode::{Encoding, RefEncode}; use crate::rc::{autoreleasepool_leaking, Retained}; use crate::runtime::__nsstring::nsstring_to_str; use crate::runtime::{AnyObject, NSObjectProtocol}; use crate::Message; /// An internal helper trait for [`ProtocolObject`]. /// /// /// # Safety /// /// This is meant to be a sealed trait, and should not be implemented outside /// of the [`extern_protocol!`] macro. /// /// [`extern_protocol!`]: crate::extern_protocol pub unsafe trait ImplementedBy { #[doc(hidden)] const __INNER: (); } /// An object representing any object that implements a specified protocol. /// /// Objective-C has [a feature][protocol-type-checking] where you can write /// `id`, and then work with the protocol as-if it was an object; /// this is very similar to `dyn` traits in Rust! /// /// If we could customize how `dyn Trait` works, then this struct would not /// have been necessary; however, `dyn Trait` is a wide pointer with overhead, /// which this struct helps avoid. /// /// If the trait `T` inherits [`NSObjectProtocol`], this will implement common /// traits like `Debug`, `PartialEq`, `Eq` and `Hash`. /// /// [protocol-type-checking]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html#//apple_ref/doc/uid/TP30001163-CH15-TPXREF151 /// /// /// # Example /// /// Convert an object `MyObject` that implements the a protocol `MyProtocol` /// into a [`ProtocolObject`] for working with the protocol in a type-erased /// way. /// /// ``` /// use objc2::runtime::ProtocolObject; /// use objc2::rc::Retained; /// # use objc2::runtime::NSObject as MyObject; /// # use objc2::runtime::NSObjectProtocol as MyProtocol; /// /// let obj: Retained = MyObject::new(); /// let proto: &ProtocolObject = ProtocolObject::from_ref(&*obj); /// let proto: Retained> = ProtocolObject::from_retained(obj); /// ``` #[doc(alias = "id")] #[repr(C)] pub struct ProtocolObject { inner: AnyObject, p: PhantomData

, } // SAFETY: `Send` if the underlying trait promises `Send`. // // E.g. `ProtocolObject` is naturally `Send`. unsafe impl Send for ProtocolObject

{} // SAFETY: `Sync` if the underlying trait promises `Sync`. // // E.g. `ProtocolObject` is naturally `Sync`. unsafe impl Sync for ProtocolObject

{} // SAFETY: The type is `#[repr(C)]` and `AnyObject` internally unsafe impl RefEncode for ProtocolObject

{ const ENCODING_REF: Encoding = Encoding::Object; } // SAFETY: The type is `AnyObject` internally, and is mean to be messaged // as-if it's an object. unsafe impl Message for ProtocolObject

{} impl ProtocolObject

{ /// Get an immutable type-erased reference from a type implementing a /// protocol. #[inline] pub fn from_ref(obj: &T) -> &Self where P: ImplementedBy, { let ptr: NonNull = NonNull::from(obj); let ptr: NonNull = ptr.cast(); // SAFETY: Implementer ensures that the object conforms to the // protocol; so converting the reference here is safe. unsafe { ptr.as_ref() } } /// Get a type-erased object from a type implementing a protocol. #[deprecated = "use `ProtocolObject::from_retained` instead"] #[inline] pub fn from_id(obj: Retained) -> Retained where P: ImplementedBy + 'static, T: Message + 'static, { Self::from_retained(obj) } /// Get a type-erased object from a type implementing a protocol. #[inline] pub fn from_retained(obj: Retained) -> Retained where P: ImplementedBy + 'static, T: Message + 'static, { // SAFETY: // - The type can be represented as the casted-to type. // - Both types are `'static` (this could maybe be relaxed a bit, but // let's be on the safe side)! unsafe { Retained::cast_unchecked::(obj) } } } impl PartialEq for ProtocolObject

{ #[inline] #[doc(alias = "isEqual:")] fn eq(&self, other: &Self) -> bool { self.isEqual(Some(&other.inner)) } } impl Eq for ProtocolObject

{} impl hash::Hash for ProtocolObject

{ #[inline] fn hash(&self, state: &mut H) { ::hash(self).hash(state); } } impl fmt::Debug for ProtocolObject

{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let description = self.description(); // `NSString`s in return types, such as the one in `description`, are // in general _supposed_ to be immutable: // // // In reality, that isn't actually the case for `NSMutableString`, // the `description` of that just returns itself instead of copying. // // Luckily though, the `UTF8String` returned by mutable objects do not // return a reference to internal data, and instead always allocate // a new autoreleased object (see details in `nsstring_to_str`), so // we do not have to worry about the string being mutated in e.g. a // malicious `Write` implementation in `fmt::Formatter` while we hold // the `&str`. // We use a leaking autorelease pool since often the string will be // UTF-8, and in that case the pool will be irrelevant. Also, it // allows us to pass the formatter into the pool (since it may contain // a pool internally that it assumes is current when writing). autoreleasepool_leaking(|pool| { // SAFETY: // - The `description` selector is guaranteed to always return an // instance of `NSString`. // - We control the scope in which the string is alive, so we know // it is not moved outside the current autorelease pool // (`autoreleasepool_leaking` is greatly helping with this, // though by itself does not fully ensure it). let s = unsafe { nsstring_to_str(&description, pool) }; fmt::Display::fmt(s, f) }) } } impl AsRef> for ProtocolObject

where T: ?Sized + ImplementedBy>, { #[inline] fn as_ref(&self) -> &ProtocolObject { ProtocolObject::from_ref(self) } } // TODO: Maybe implement Borrow? impl AsRef for ProtocolObject

{ #[inline] fn as_ref(&self) -> &AnyObject { let ptr: NonNull> = NonNull::from(self); let ptr: NonNull = ptr.cast(); // SAFETY: All protocol objects are Objective-C objects too. unsafe { ptr.as_ref() } } } #[cfg(test)] #[allow(clippy::missing_safety_doc)] #[allow(dead_code)] mod tests { use alloc::format; use core::ffi::CStr; use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; use crate::runtime::{ClassBuilder, NSObject}; use crate::{define_class, extern_methods, extern_protocol, msg_send, ClassType}; extern_protocol!( unsafe trait Foo { #[unsafe(method(foo))] fn foo_class(); #[unsafe(method(foo))] fn foo_instance(&self); } ); extern_protocol!( unsafe trait Bar: NSObjectProtocol { #[unsafe(method(bar))] fn bar_class(); #[unsafe(method(bar))] fn bar_instance(&self); } ); extern_protocol!( unsafe trait FooBar: Foo + Bar { #[unsafe(method(foobar))] fn foobar_class(); #[unsafe(method(foobar))] fn foobar_instance(&self); } ); extern_protocol!( unsafe trait FooFooBar: Foo + FooBar { #[unsafe(method(foofoobar))] fn foofoobar_class(); #[unsafe(method(foofoobar))] fn foofoobar_instance(&self); } ); define_class!( #[unsafe(super(NSObject))] #[derive(Debug, PartialEq, Eq, Hash)] struct DummyClass; unsafe impl NSObjectProtocol for DummyClass {} ); unsafe impl Foo for DummyClass {} unsafe impl Bar for DummyClass {} unsafe impl FooBar for DummyClass {} // unsafe impl FooFooBar for DummyClass {} impl DummyClass { extern_methods!( #[unsafe(method(new))] fn new() -> Retained; ); } #[test] fn impl_traits() { assert_impl_all!(NSObject: NSObjectProtocol); assert_impl_all!(ProtocolObject: NSObjectProtocol); assert_not_impl_any!(ProtocolObject: Send, Sync); assert_impl_all!(ProtocolObject: NSObjectProtocol, Send); assert_not_impl_any!(ProtocolObject: Sync); assert_impl_all!(ProtocolObject: NSObjectProtocol, Sync); assert_not_impl_any!(ProtocolObject: Send); assert_impl_all!(ProtocolObject: NSObjectProtocol, Send, Sync); assert_not_impl_any!(ProtocolObject: NSObjectProtocol); assert_impl_all!(ProtocolObject: NSObjectProtocol); assert_impl_all!(ProtocolObject: NSObjectProtocol); assert_impl_all!(ProtocolObject: NSObjectProtocol); assert_impl_all!(DummyClass: NSObjectProtocol); assert_not_impl_any!(NSObject: Foo); assert_not_impl_any!(ProtocolObject: Foo); assert_impl_all!(ProtocolObject: Foo); assert_not_impl_any!(ProtocolObject: Foo); assert_impl_all!(ProtocolObject: Foo); assert_impl_all!(ProtocolObject: Foo); assert_impl_all!(DummyClass: Foo); assert_not_impl_any!(NSObject: Bar); assert_not_impl_any!(ProtocolObject: Bar); assert_not_impl_any!(ProtocolObject: Bar); assert_impl_all!(ProtocolObject: Bar); assert_impl_all!(ProtocolObject: Bar); assert_impl_all!(ProtocolObject: Bar); assert_impl_all!(DummyClass: Bar); assert_not_impl_any!(NSObject: FooBar); assert_not_impl_any!(ProtocolObject: FooBar); assert_not_impl_any!(ProtocolObject: FooBar); assert_not_impl_any!(ProtocolObject: FooBar); assert_impl_all!(ProtocolObject: FooBar); assert_impl_all!(ProtocolObject: FooBar); assert_impl_all!(DummyClass: FooBar); assert_not_impl_any!(NSObject: FooFooBar); assert_not_impl_any!(ProtocolObject: FooFooBar); assert_not_impl_any!(ProtocolObject: FooFooBar); assert_not_impl_any!(ProtocolObject: FooFooBar); assert_not_impl_any!(ProtocolObject: FooFooBar); assert_impl_all!(ProtocolObject: FooFooBar); assert_not_impl_any!(DummyClass: FooFooBar); } #[test] fn convertible() { let obj = DummyClass::new(); let foobar: &ProtocolObject = ProtocolObject::from_ref(&*obj); let foobar: &ProtocolObject = ProtocolObject::from_ref(foobar); let _bar: &ProtocolObject = ProtocolObject::from_ref(foobar); let bar: &ProtocolObject = ProtocolObject::from_ref(&*obj); let bar: &ProtocolObject = ProtocolObject::from_ref(bar); let _foo: &ProtocolObject = ProtocolObject::from_ref(foobar); let foo: &ProtocolObject = ProtocolObject::from_ref(&*obj); let _foo: &ProtocolObject = ProtocolObject::from_ref(foo); let _nsobject: &ProtocolObject = ProtocolObject::from_ref(foobar); let _nsobject: &ProtocolObject = ProtocolObject::from_ref(bar); let nsobject: &ProtocolObject = ProtocolObject::from_ref(&*obj); let _nsobject: &ProtocolObject = ProtocolObject::from_ref(nsobject); let _: &ProtocolObject = ProtocolObject::from_ref(&*obj); let _: &ProtocolObject = ProtocolObject::from_ref(&*obj); let _: &ProtocolObject = ProtocolObject::from_ref(&*obj); let _foobar: Retained> = ProtocolObject::from_retained(obj); } #[test] fn convert_to_anyobj() { let obj = NSObject::new(); let obj: Retained> = ProtocolObject::from_retained(obj); let _obj: &AnyObject = obj.as_ref(); } #[test] fn test_traits() { use core::hash::Hasher; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; let obj = DummyClass::new(); let obj2 = DummyClass::new(); let foobar: &ProtocolObject = ProtocolObject::from_ref(&*obj); let foobar2: &ProtocolObject = ProtocolObject::from_ref(&*obj2); assert_eq!( format!("{obj:?}"), format!("DummyClass {{ super: {foobar:?}, ivars: () }}"), ); assert_eq!(obj == obj2, foobar == foobar2); let mut hashstate_a = DefaultHasher::new(); let mut hashstate_b = DefaultHasher::new(); obj.hash(&mut hashstate_a); <_ as Hash>::hash(foobar, &mut hashstate_b); assert_eq!(hashstate_a.finish(), hashstate_b.finish()); } // We use `debug_assertions` here just because it's something that we know // our CI already tests. extern_protocol!( #[cfg(debug_assertions)] unsafe trait CfgTest {} ); #[test] #[cfg(debug_assertions)] fn test_meta() { if false { let _protocol = ::protocol(); } } #[test] #[cfg_attr( feature = "gnustep-1-7", ignore = "depends on the platform's NSString unicode handling" )] fn debug_non_utf8_classname() { // Some class with invalid UTF-8 character inside let s = CStr::from_bytes_with_nul(b"My\xF0\x90\x80Class\0").unwrap(); let cls = ClassBuilder::new(s, NSObject::class()).unwrap().register(); let obj: Retained = unsafe { msg_send![cls, new] }; let expected = format!("", &*obj); assert_eq!(format!("{obj:?}"), expected); } }