diff --git a/objc2-foundation/examples/class_with_lifetime.rs b/objc2-foundation/examples/class_with_lifetime.rs index 309bd7628..9d570f83f 100644 --- a/objc2-foundation/examples/class_with_lifetime.rs +++ b/objc2-foundation/examples/class_with_lifetime.rs @@ -5,7 +5,7 @@ use std::sync::Once; use objc2::declare::ClassBuilder; use objc2::rc::{Id, Owned, Shared}; use objc2::runtime::{Class, Object, Sel}; -use objc2::{msg_send, sel}; +use objc2::{msg_send, msg_send_id, sel}; use objc2::{Encoding, Message, RefEncode}; use objc2_foundation::NSObject; @@ -28,9 +28,8 @@ static MYOBJECT_REGISTER_CLASS: Once = Once::new(); impl<'a> MyObject<'a> { fn new(number_ptr: &'a mut u8) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithPtr: number_ptr]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithPtr: number_ptr].unwrap() } } diff --git a/objc2-foundation/examples/custom_class.rs b/objc2-foundation/examples/custom_class.rs index eaede8547..b808f3877 100644 --- a/objc2-foundation/examples/custom_class.rs +++ b/objc2-foundation/examples/custom_class.rs @@ -3,7 +3,7 @@ use std::sync::Once; use objc2::declare::ClassBuilder; use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, Object, Sel}; -use objc2::{msg_send, sel}; +use objc2::{msg_send, msg_send_id, sel}; use objc2::{Encoding, Message, RefEncode}; use objc2_foundation::NSObject; @@ -25,7 +25,7 @@ static MYOBJECT_REGISTER_CLASS: Once = Once::new(); impl MYObject { fn new() -> Id { let cls = Self::class(); - unsafe { Id::new(msg_send![cls, new]).unwrap() } + unsafe { msg_send_id![cls, new].unwrap() } } fn number(&self) -> u32 { diff --git a/objc2-foundation/src/attributed_string.rs b/objc2-foundation/src/attributed_string.rs index 1dbb080bd..edfb41889 100644 --- a/objc2-foundation/src/attributed_string.rs +++ b/objc2-foundation/src/attributed_string.rs @@ -1,6 +1,6 @@ -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Shared}; use objc2::runtime::Object; +use objc2::{msg_send, msg_send_id}; use crate::{ NSCopying, NSDictionary, NSMutableAttributedString, NSMutableCopying, NSObject, NSString, @@ -47,9 +47,8 @@ impl NSAttributedString { attributes: &NSDictionary, ) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithString: string, attributes: attributes]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string, attributes: attributes].unwrap() } } @@ -57,9 +56,8 @@ impl NSAttributedString { #[doc(alias = "initWithString:")] pub fn from_nsstring(string: &NSString) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithString: string]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() } } } @@ -67,8 +65,8 @@ impl NSAttributedString { /// Querying. impl NSAttributedString { // TODO: Lifetimes? - pub fn string(&self) -> &NSString { - unsafe { msg_send![self, string] } + pub fn string(&self) -> Id { + unsafe { msg_send_id![self, string].unwrap() } } /// Alias for `self.string().len_utf16()`. diff --git a/objc2-foundation/src/copying.rs b/objc2-foundation/src/copying.rs index c0fcbc795..ee976a2fa 100644 --- a/objc2-foundation/src/copying.rs +++ b/objc2-foundation/src/copying.rs @@ -1,5 +1,5 @@ use objc2::rc::{Id, Owned, Ownership}; -use objc2::{msg_send, Message}; +use objc2::{msg_send_id, Message}; pub unsafe trait NSCopying: Message { /// Indicates whether the type is mutable or immutable. @@ -31,10 +31,7 @@ pub unsafe trait NSCopying: Message { type Output: Message; fn copy(&self) -> Id { - unsafe { - let obj: *mut Self::Output = msg_send![self, copy]; - Id::new(obj).unwrap() - } + unsafe { msg_send_id![self, copy].unwrap() } } } @@ -46,9 +43,6 @@ pub unsafe trait NSMutableCopying: Message { type Output: Message; fn mutable_copy(&self) -> Id { - unsafe { - let obj: *mut Self::Output = msg_send![self, mutableCopy]; - Id::new(obj).unwrap() - } + unsafe { msg_send_id![self, mutableCopy].unwrap() } } } diff --git a/objc2-foundation/src/data.rs b/objc2-foundation/src/data.rs index 0ecb7fd86..26a23940b 100644 --- a/objc2-foundation/src/data.rs +++ b/objc2-foundation/src/data.rs @@ -5,9 +5,9 @@ use core::ops::{Index, IndexMut, Range}; use core::slice::{self, SliceIndex}; use std::io; -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Owned, Shared}; use objc2::runtime::{Class, Object}; +use objc2::{msg_send, msg_send_id}; use super::{NSCopying, NSMutableCopying, NSObject, NSRange}; @@ -147,18 +147,16 @@ impl NSMutableData { pub fn from_data(data: &NSData) -> Id { // Not provided on NSData, one should just use NSData::copy or similar unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithData: data]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithData: data].unwrap() } } #[doc(alias = "initWithCapacity:")] pub fn with_capacity(capacity: usize) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithCapacity: capacity]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithCapacity: capacity].unwrap() } } } diff --git a/objc2-foundation/src/dictionary.rs b/objc2-foundation/src/dictionary.rs index 69fde1907..fd15bfef7 100644 --- a/objc2-foundation/src/dictionary.rs +++ b/objc2-foundation/src/dictionary.rs @@ -5,7 +5,7 @@ use core::ops::Index; use core::ptr; use objc2::rc::{DefaultId, Id, Owned, Shared, SliceId}; -use objc2::{msg_send, Message}; +use objc2::{msg_send, msg_send_id, Message}; use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject}; @@ -101,10 +101,7 @@ impl NSDictionary { } pub fn keys_array(&self) -> Id, Shared> { - unsafe { - let keys = msg_send![self, allKeys]; - Id::retain_autoreleased(keys).unwrap() - } + unsafe { msg_send_id![self, allKeys] }.unwrap() } pub fn from_keys_and_objects(keys: &[&T], vals: Vec>) -> Id @@ -115,23 +112,20 @@ impl NSDictionary { let cls = Self::class(); let count = min(keys.len(), vals.len()); - let obj: *mut Self = unsafe { msg_send![cls, alloc] }; - let obj: *mut Self = unsafe { - msg_send![ + let obj = unsafe { msg_send_id![cls, alloc] }; + unsafe { + msg_send_id![ obj, initWithObjects: vals.as_ptr(), forKeys: keys.as_ptr(), count: count, ] - }; - unsafe { Id::new(obj).unwrap() } + } + .unwrap() } pub fn into_values_array(dict: Id) -> Id, Shared> { - unsafe { - let vals = msg_send![&dict, allValues]; - Id::retain_autoreleased(vals).unwrap() - } + unsafe { msg_send_id![&dict, allValues] }.unwrap() } } diff --git a/objc2-foundation/src/macros.rs b/objc2-foundation/src/macros.rs index 184d35084..5ae7df445 100644 --- a/objc2-foundation/src/macros.rs +++ b/objc2-foundation/src/macros.rs @@ -227,7 +227,7 @@ macro_rules! unsafe_def_fn { $(#[$m])* $v fn new() -> Id { let cls = Self::class(); - unsafe { Id::new(msg_send![cls, new]).unwrap() } + unsafe { ::objc2::msg_send_id![cls, new].unwrap() } } }; } diff --git a/objc2-foundation/src/mutable_attributed_string.rs b/objc2-foundation/src/mutable_attributed_string.rs index 70e1f6b7b..c6f01e59c 100644 --- a/objc2-foundation/src/mutable_attributed_string.rs +++ b/objc2-foundation/src/mutable_attributed_string.rs @@ -1,5 +1,5 @@ -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Owned, Shared}; +use objc2::{msg_send, msg_send_id}; use crate::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString}; @@ -26,18 +26,16 @@ impl NSMutableAttributedString { #[doc(alias = "initWithString:")] pub fn from_nsstring(string: &NSString) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithString: string]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() } } #[doc(alias = "initWithAttributedString:")] pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithAttributedString: attributed_string]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithAttributedString: attributed_string].unwrap() } } } diff --git a/objc2-foundation/src/mutable_string.rs b/objc2-foundation/src/mutable_string.rs index 0fcfbb1d4..d14cb4687 100644 --- a/objc2-foundation/src/mutable_string.rs +++ b/objc2-foundation/src/mutable_string.rs @@ -3,8 +3,8 @@ use core::fmt; use core::ops::AddAssign; use core::str; -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Owned, Shared}; +use objc2::{msg_send, msg_send_id}; use crate::{NSCopying, NSMutableCopying, NSObject, NSString}; @@ -39,18 +39,16 @@ impl NSMutableString { #[doc(alias = "initWithString:")] pub fn from_nsstring(string: &NSString) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithString: string]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() } } #[doc(alias = "initWithCapacity:")] pub fn with_capacity(capacity: usize) -> Id { unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithCapacity: capacity]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithCapacity: capacity].unwrap() } } } diff --git a/objc2-foundation/src/object.rs b/objc2-foundation/src/object.rs index 1d0bcc751..204318c95 100644 --- a/objc2-foundation/src/object.rs +++ b/objc2-foundation/src/object.rs @@ -1,6 +1,6 @@ use objc2::rc::{DefaultId, Id, Owned, Shared}; use objc2::runtime::{Class, Object}; -use objc2::{msg_send, msg_send_bool}; +use objc2::{msg_send, msg_send_bool, msg_send_id}; use super::NSString; @@ -21,11 +21,8 @@ impl NSObject { } pub fn description(&self) -> Id { - unsafe { - let result: *mut NSString = msg_send![self, description]; - // TODO: Verify that description always returns a non-null string - Id::retain_autoreleased(result).unwrap() - } + // TODO: Verify that description always returns a non-null string + unsafe { msg_send_id![self, description].unwrap() } } pub fn is_kind_of(&self, cls: &Class) -> bool { diff --git a/objc2-foundation/src/process_info.rs b/objc2-foundation/src/process_info.rs index dcd8c5a99..696d4dc65 100644 --- a/objc2-foundation/src/process_info.rs +++ b/objc2-foundation/src/process_info.rs @@ -1,4 +1,4 @@ -use objc2::msg_send; +use objc2::msg_send_id; use objc2::rc::{Id, Shared}; use crate::{NSObject, NSString}; @@ -20,13 +20,11 @@ unsafe impl Sync for NSProcessInfo {} impl NSProcessInfo { pub fn process_info() -> Id { // currentThread is @property(strong), what does that mean? - let obj: *mut Self = unsafe { msg_send![Self::class(), processInfo] }; // TODO: Always available? - unsafe { Id::retain_autoreleased(obj).unwrap() } + unsafe { msg_send_id![Self::class(), processInfo].unwrap() } } pub fn process_name(&self) -> Id { - let obj: *mut NSString = unsafe { msg_send![Self::class(), processName] }; - unsafe { Id::retain_autoreleased(obj).unwrap() } + unsafe { msg_send_id![self, processName].unwrap() } } } diff --git a/objc2-foundation/src/thread.rs b/objc2-foundation/src/thread.rs index 022947b7c..d0f71f844 100644 --- a/objc2-foundation/src/thread.rs +++ b/objc2-foundation/src/thread.rs @@ -1,5 +1,5 @@ use objc2::rc::{Id, Shared}; -use objc2::{msg_send, msg_send_bool}; +use objc2::{msg_send, msg_send_bool, msg_send_id}; use crate::{NSObject, NSString}; @@ -15,20 +15,18 @@ unsafe impl Sync for NSThread {} impl NSThread { /// Returns the [`NSThread`] object representing the current thread. - pub fn current() -> Id { + pub fn current() -> Id { // TODO: currentThread is @property(strong), what does that mean? - let obj: *mut Self = unsafe { msg_send![Self::class(), currentThread] }; // TODO: Always available? - unsafe { Id::retain_autoreleased(obj).unwrap() } + unsafe { msg_send_id![Self::class(), currentThread].unwrap() } } /// Returns the [`NSThread`] object representing the main thread. pub fn main() -> Id { // TODO: mainThread is @property(strong), what does that mean? - let obj: *mut Self = unsafe { msg_send![Self::class(), mainThread] }; // The main thread static may not have been initialized // This can at least fail in GNUStep! - unsafe { Id::retain_autoreleased(obj).expect("Could not retrieve main thread.") } + unsafe { msg_send_id![Self::class(), mainThread] }.expect("Could not retrieve main thread.") } /// Returns `true` if the thread is the main thread. @@ -38,13 +36,11 @@ impl NSThread { /// The name of the thread. pub fn name(&self) -> Option> { - let obj: *mut NSString = unsafe { msg_send![self, name] }; - unsafe { Id::retain_autoreleased(obj) } + unsafe { msg_send_id![self, name] } } fn new() -> Id { - let obj: *mut Self = unsafe { msg_send![Self::class(), new] }; - unsafe { Id::new(obj) }.unwrap() + unsafe { msg_send_id![Self::class(), new] }.unwrap() } fn start(&self) { diff --git a/objc2-foundation/src/uuid.rs b/objc2-foundation/src/uuid.rs index 40300da6c..f1c4ae363 100644 --- a/objc2-foundation/src/uuid.rs +++ b/objc2-foundation/src/uuid.rs @@ -1,5 +1,5 @@ use objc2::rc::{Id, Shared}; -use objc2::{msg_send, Encode, Encoding, RefEncode}; +use objc2::{msg_send, msg_send_id, Encode, Encoding, RefEncode}; use super::{NSCopying, NSObject}; @@ -32,19 +32,14 @@ impl NSUUID { // TODO: `nil` method? pub fn new_v4() -> Id { - unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, init]; - Id::new(obj).unwrap() - } + unsafe { msg_send_id![Self::class(), new].unwrap() } } pub fn from_bytes(bytes: [u8; 16]) -> Id { let bytes = UuidBytes(bytes); unsafe { - let obj: *mut Self = msg_send![Self::class(), alloc]; - let obj: *mut Self = msg_send![obj, initWithUUIDBytes: &bytes]; - Id::new(obj).unwrap() + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithUUIDBytes: &bytes].unwrap() } } diff --git a/objc2-foundation/src/value.rs b/objc2-foundation/src/value.rs index 71b44f9f5..9213139b7 100644 --- a/objc2-foundation/src/value.rs +++ b/objc2-foundation/src/value.rs @@ -8,9 +8,9 @@ use core::{fmt, str}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Shared}; use objc2::Encode; +use objc2::{msg_send, msg_send_id}; use super::{NSCopying, NSObject}; @@ -68,13 +68,13 @@ impl NSValue { let bytes: *const c_void = bytes.cast(); let encoding = CString::new(T::ENCODING.to_string()).unwrap(); unsafe { - let obj: *mut Self = msg_send![cls, alloc]; - let obj: *mut Self = msg_send![ + let obj = msg_send_id![cls, alloc]; + msg_send_id![ obj, initWithBytes: bytes, objCType: encoding.as_ptr(), - ]; - Id::new(obj).unwrap() + ] + .unwrap() } } } diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index 9536d9412..de3d37767 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Added +* Added `msg_send_id!` to help with following Objective-C's memory management + rules. **It is highly recommended that you use this instead of doing memory + management yourself!** + + Example: + ```rust + // Before + let obj: Id = unsafe { + let obj: *mut Self = msg_send![Self::class(), alloc]; + let obj: *mut Self = msg_send![obj, init]; + Id::new(obj).unwrap() + }; + + // After + let obj: Id = unsafe { + msg_send_id![msg_send_id![Self::class(), alloc], new].unwrap() + }; + ``` + ## 0.3.0-beta.0 - 2022-06-13 diff --git a/objc2/README.md b/objc2/README.md index 8b215a7d0..7b2db649d 100644 --- a/objc2/README.md +++ b/objc2/README.md @@ -13,17 +13,16 @@ written in Objective-C; this crate enables you to interract with those. ## Example ```rust -use objc2::{class, msg_send}; +use objc2::{class, msg_send, msg_send_id}; +use objc2::ffi::NSUInteger; use objc2::rc::{Id, Owned}; -use objc2::runtime::{Class, Object}; +use objc2::runtime::Object; let cls = class!(NSObject); -let obj: *mut Object = unsafe { msg_send![cls, new] }; -let obj: Id = unsafe { Id::new(obj).unwrap() }; +let obj: Id = unsafe { msg_send_id![cls, new] }.unwrap(); -// TODO -// let isa = unsafe { obj.ivar::("isa") }; -// assert_eq!(cls, isa); +let hash: NSUInteger = unsafe { msg_send![&obj, hash] }; +println!("NSObject hash: {}", hash); ``` See [the docs](https://docs.rs/objc2/) for a more thorough overview, or jump diff --git a/objc2/examples/introspection.rs b/objc2/examples/introspection.rs index cfe126b15..a57018b63 100644 --- a/objc2/examples/introspection.rs +++ b/objc2/examples/introspection.rs @@ -1,6 +1,6 @@ use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, Object}; -use objc2::{class, msg_send}; +use objc2::{class, msg_send, msg_send_id}; #[cfg(feature = "malloc")] use objc2::{sel, Encode}; @@ -20,9 +20,8 @@ fn main() { // Allocate an instance let obj: Id = unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - let obj: *mut Object = msg_send![obj, init]; - Id::new(obj).unwrap() + let obj = msg_send_id![cls, alloc]; + msg_send_id![obj, init].unwrap() }; println!("NSObject address: {:p}", obj); diff --git a/objc2/examples/talk_to_me.rs b/objc2/examples/talk_to_me.rs index a29eb193b..7c4b27628 100644 --- a/objc2/examples/talk_to_me.rs +++ b/objc2/examples/talk_to_me.rs @@ -1,7 +1,7 @@ use objc2::ffi::NSUInteger; use objc2::rc::{Id, Owned, Shared}; use objc2::runtime::Object; -use objc2::{class, msg_send}; +use objc2::{class, msg_send, msg_send_id}; use std::ffi::c_void; #[cfg(feature = "apple")] // Does not work on GNUStep @@ -13,24 +13,24 @@ fn main() { let text = "Hello from Rust!"; const UTF8_ENCODING: NSUInteger = 4; - let string: *const Object = unsafe { msg_send![class!(NSString), alloc] }; + let string = unsafe { msg_send_id![class!(NSString), alloc] }; let text_ptr: *const c_void = text.as_ptr().cast(); - let string = unsafe { - msg_send![ + let string: Id = unsafe { + msg_send_id![ string, initWithBytes: text_ptr, length: text.len(), encoding: UTF8_ENCODING, ] - }; - let string: Id = unsafe { Id::new(string).unwrap() }; + } + .unwrap(); - let synthesizer: *mut Object = unsafe { msg_send![class!(AVSpeechSynthesizer), new] }; - let synthesizer: Id = unsafe { Id::new(synthesizer).unwrap() }; + let synthesizer: Id = + unsafe { msg_send_id![class!(AVSpeechSynthesizer), new] }.unwrap(); - let utterance: *mut Object = unsafe { msg_send![class!(AVSpeechUtterance), alloc] }; - let utterance: *mut Object = unsafe { msg_send![utterance, initWithString: &*string] }; - let utterance: Id = unsafe { Id::new(utterance).unwrap() }; + let utterance = unsafe { msg_send_id![class!(AVSpeechUtterance), alloc] }; + let utterance: Id = + unsafe { msg_send_id![utterance, initWithString: &*string] }.unwrap(); // let _: () = unsafe { msg_send![&utterance, setVolume: 90.0f32 }; // let _: () = unsafe { msg_send![&utterance, setRate: 0.50f32 }; diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs new file mode 100644 index 000000000..e8a25c457 --- /dev/null +++ b/objc2/src/__macro_helpers.rs @@ -0,0 +1,376 @@ +use crate::rc::{Id, Ownership}; +use crate::runtime::{Class, Sel}; +use crate::{Message, MessageArguments, MessageError, MessageReceiver}; + +#[doc(hidden)] +pub use core::compile_error; + +/// Helper for specifying the retain semantics for a given selector family. +/// +/// Note that we can't actually check if a method is in a method family; only +/// whether the _selector_ is in a _selector_ family. +/// +/// The slight difference here is: +/// - The method may be annotated with the `objc_method_family` attribute, +/// which would cause it to be in a different family. That this is not the +/// case is part of the `unsafe` contract of `msg_send_id!`. +/// - The method may not obey the added restrictions of the method family. +/// The added restrictions are: +/// - `new`, `alloc`, `copy` and `mutableCopy`: The method must return a +/// retainable object pointer type - we ensure this by making +/// `message_send_id` return `Id`. +/// - `init`: The method must be an instance method and must return an +/// Objective-C pointer type - We ensure this by taking `Id`, which +/// means it can't be a class method! +/// +/// While we're at it, we also limit a few other things to help the user out, +/// like only allowing `&Class` in `new` - this is not strictly required by +/// ARC though! +/// +/// +#[doc(hidden)] +pub struct RetainSemantics< + // `new` family + const NEW: bool, + // `alloc` family + const ALLOC: bool, + // `init` family + const INIT: bool, + // `copy` or `mutableCopy` family + const COPY_OR_MUT_COPY: bool, +> {} + +#[doc(hidden)] +pub trait MsgSendId { + unsafe fn send_message_id( + obj: T, + sel: Sel, + args: A, + ) -> Result, MessageError>; +} + +// `new` +impl MsgSendId<&'_ Class, Id> + for RetainSemantics +{ + #[inline(always)] + unsafe fn send_message_id( + obj: &Class, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + unsafe { MessageReceiver::send_message(obj, sel, args).map(|r| Id::new(r)) } + } +} + +// `alloc`, should mark the return value as "allocated, not initialized" somehow +impl MsgSendId<&'_ Class, Id> + for RetainSemantics +{ + #[inline(always)] + unsafe fn send_message_id( + cls: &Class, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + unsafe { MessageReceiver::send_message(cls, sel, args).map(|r| Id::new(r)) } + } +} + +// `init`, should mark the input value as "allocated, not initialized" somehow +impl MsgSendId>, Id> + for RetainSemantics +{ + #[inline(always)] + unsafe fn send_message_id( + obj: Option>, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + let ptr = Id::option_into_ptr(obj); + // SAFETY: `ptr` may be null here, but that's fine since the return + // is `*mut T`, which is one of the few types where messages to nil is + // allowed. + // + // We do this for efficiency, to avoid having a branch after every + // `alloc`, that the user did not intend. + unsafe { MessageReceiver::send_message(ptr, sel, args).map(|r| Id::new(r)) } + } +} + +// `copy` and `mutableCopy` +impl MsgSendId> + for RetainSemantics +{ + #[inline(always)] + unsafe fn send_message_id( + obj: T, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + unsafe { MessageReceiver::send_message(obj, sel, args).map(|r| Id::new(r)) } + } +} + +// All other selectors +impl MsgSendId> + for RetainSemantics +{ + #[inline(always)] + unsafe fn send_message_id( + obj: T, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + // All code between the message send and the `retain_autoreleased` + // must be able to be optimized away for this to work. + unsafe { MessageReceiver::send_message(obj, sel, args).map(|r| Id::retain_autoreleased(r)) } + } +} + +/// Checks whether a given selector is said to be in a given selector family. +/// +/// +#[doc(hidden)] +pub const fn in_selector_family(mut selector: &[u8], mut family: &[u8]) -> bool { + // Skip leading underscores from selector + loop { + selector = match selector { + [b'_', selector @ ..] => (selector), + _ => break, + } + } + + // Compare each character + loop { + (selector, family) = match (selector, family) { + // Remaining items + ([s, selector @ ..], [f, family @ ..]) => { + if *s == *f { + // Next iteration + (selector, family) + } else { + // Family does not begin with selector + return false; + } + } + // Equal + ([], []) => { + return true; + } + // Selector can't be part of familiy if smaller than it + ([], _) => { + return false; + } + // Remaining items in selector + // -> ensure next character is not lowercase + ([s, ..], []) => { + return !s.is_ascii_lowercase(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use core::ptr; + + use crate::rc::{Owned, RcTestObject, Shared, ThreadTestData}; + use crate::runtime::Object; + use crate::{Encoding, RefEncode}; + + #[test] + fn test_macro_alloc() { + let mut expected = ThreadTestData::current(); + let cls = RcTestObject::class(); + + let obj: Id = unsafe { msg_send_id![cls, alloc].unwrap() }; + expected.alloc += 1; + expected.assert_current(); + + drop(obj); + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + } + + #[test] + #[cfg_attr( + all(feature = "gnustep-1-7", feature = "verify_message"), + ignore = "NSZone's encoding is quite complex on GNUStep" + )] + fn test_alloc_with_zone() { + #[repr(C)] + struct _NSZone { + _inner: [u8; 0], + } + + unsafe impl RefEncode for _NSZone { + const ENCODING_REF: Encoding<'static> = + Encoding::Pointer(&Encoding::Struct("_NSZone", &[])); + } + + let expected = ThreadTestData::current(); + let cls = RcTestObject::class(); + + let zone: *const _NSZone = ptr::null(); + let _obj: Id = + unsafe { msg_send_id![cls, allocWithZone: zone].unwrap() }; + // `+[NSObject alloc]` delegates to `+[NSObject allocWithZone:]`, but + // `RcTestObject` only catches `alloc`. + // expected.alloc += 1; + expected.assert_current(); + } + + #[test] + fn test_macro_init() { + let mut expected = ThreadTestData::current(); + let cls = RcTestObject::class(); + + let obj: Option> = unsafe { msg_send_id![cls, alloc] }; + expected.alloc += 1; + // Don't check allocation error + let _obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + expected.init += 1; + expected.assert_current(); + + let obj: Option> = unsafe { msg_send_id![cls, alloc] }; + expected.alloc += 1; + // Check allocation error before init + let obj = obj.unwrap(); + let _obj: Id = unsafe { msg_send_id![Some(obj), init].unwrap() }; + expected.init += 1; + expected.assert_current(); + } + + #[test] + fn test_macro() { + let mut expected = ThreadTestData::current(); + let cls = RcTestObject::class(); + crate::rc::autoreleasepool(|_| { + let _obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; + expected.alloc += 1; + expected.init += 1; + expected.assert_current(); + + let obj = unsafe { msg_send_id![cls, alloc] }; + expected.alloc += 1; + expected.assert_current(); + + let obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + expected.init += 1; + expected.assert_current(); + + // TODO: + // let copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; + // let mutable_copy: Id = unsafe { msg_send_id![&obj, mutableCopy].unwrap() }; + + let _self: Id = unsafe { msg_send_id![&obj, self].unwrap() }; + expected.retain += 1; + expected.assert_current(); + + let _desc: Option> = + unsafe { msg_send_id![&obj, description] }; + expected.assert_current(); + }); + expected.release += 3; + expected.dealloc += 2; + expected.assert_current(); + } + + #[test] + fn test_in_selector_family() { + // Common cases + + assert!(in_selector_family(b"alloc", b"alloc")); + assert!(in_selector_family(b"allocWithZone:", b"alloc")); + assert!(!in_selector_family(b"dealloc", b"alloc")); + assert!(!in_selector_family(b"initialize", b"init")); + assert!(!in_selector_family(b"decimalNumberWithDecimal:", b"init")); + assert!(in_selector_family(b"initWithCapacity:", b"init")); + assert!(in_selector_family(b"_initButPrivate:withParam:", b"init")); + assert!(!in_selector_family(b"description", b"init")); + assert!(!in_selector_family(b"inIT", b"init")); + + assert!(!in_selector_family(b"init", b"copy")); + assert!(!in_selector_family(b"copyingStuff:", b"copy")); + assert!(in_selector_family(b"copyWithZone:", b"copy")); + assert!(!in_selector_family(b"initWithArray:copyItems:", b"copy")); + assert!(in_selector_family(b"copyItemAtURL:toURL:error:", b"copy")); + + assert!(!in_selector_family(b"mutableCopying", b"mutableCopy")); + assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy")); + assert!(in_selector_family(b"mutableCopyWithZone:", b"mutableCopy")); + + assert!(in_selector_family( + b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + b"new" + )); + assert!(in_selector_family( + b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + b"new" + )); + assert!(!in_selector_family(b"newsstandAssetDownload", b"new")); + + // Trying to weed out edge-cases: + + assert!(in_selector_family(b"__abcDef", b"abc")); + assert!(in_selector_family(b"_abcDef", b"abc")); + assert!(in_selector_family(b"abcDef", b"abc")); + assert!(in_selector_family(b"___a", b"a")); + assert!(in_selector_family(b"__a", b"a")); + assert!(in_selector_family(b"_a", b"a")); + assert!(in_selector_family(b"a", b"a")); + + assert!(!in_selector_family(b"_abcdef", b"abc")); + assert!(!in_selector_family(b"_abcdef", b"def")); + assert!(!in_selector_family(b"_bcdef", b"abc")); + assert!(!in_selector_family(b"a_bc", b"abc")); + assert!(!in_selector_family(b"abcdef", b"abc")); + assert!(!in_selector_family(b"abcdef", b"def")); + assert!(!in_selector_family(b"abcdef", b"abb")); + assert!(!in_selector_family(b"___", b"a")); + assert!(!in_selector_family(b"_", b"a")); + assert!(!in_selector_family(b"", b"a")); + + assert!(in_selector_family(b"copy", b"copy")); + assert!(in_selector_family(b"copy:", b"copy")); + assert!(in_selector_family(b"copyMe", b"copy")); + assert!(in_selector_family(b"_copy", b"copy")); + assert!(in_selector_family(b"_copy:", b"copy")); + assert!(in_selector_family(b"_copyMe", b"copy")); + assert!(!in_selector_family(b"copying", b"copy")); + assert!(!in_selector_family(b"copying:", b"copy")); + assert!(!in_selector_family(b"_copying", b"copy")); + assert!(!in_selector_family(b"Copy", b"copy")); + assert!(!in_selector_family(b"COPY", b"copy")); + + // Empty family (not supported) + assert!(in_selector_family(b"___", b"")); + assert!(in_selector_family(b"__", b"")); + assert!(in_selector_family(b"_", b"")); + assert!(in_selector_family(b"", b"")); + assert!(!in_selector_family(b"_a", b"")); + assert!(!in_selector_family(b"a", b"")); + assert!(in_selector_family(b"_A", b"")); + assert!(in_selector_family(b"A", b"")); + } + + mod test_trait_disambugated { + use super::*; + + trait Abc { + fn send_message_id() {} + } + + impl Abc for T {} + + #[test] + fn test_macro_still_works() { + let cls = class!(NSObject); + let _obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; + } + } +} diff --git a/objc2/src/bool.rs b/objc2/src/bool.rs index d8e69dd9e..af2fe130a 100644 --- a/objc2/src/bool.rs +++ b/objc2/src/bool.rs @@ -16,10 +16,13 @@ use core::fmt; /// # Example /// /// ```no_run -/// use objc2::{class, msg_send, msg_send_bool}; +/// use objc2::{class, msg_send_bool, msg_send_id}; +/// use objc2::rc::{Id, Shared}; /// use objc2::runtime::{Object, Bool}; -/// let ns_value: *mut Object = unsafe { msg_send![class!(NSValue), initWithBool: Bool::YES] }; -/// assert!(unsafe { msg_send_bool![ns_value, boolValue] }); +/// let ns_value: Id = unsafe { +/// msg_send_id![class!(NSNumber), numberWithBool: Bool::YES].unwrap() +/// }; +/// assert!(unsafe { msg_send_bool![&ns_value, boolValue] }); /// ``` #[repr(transparent)] // We don't implement comparison traits because they could be implemented with diff --git a/objc2/src/exception.rs b/objc2/src/exception.rs index bc65fe516..508e8f1e7 100644 --- a/objc2/src/exception.rs +++ b/objc2/src/exception.rs @@ -144,7 +144,7 @@ mod tests { #[test] fn test_throw_catch_object() { - let obj: Id = unsafe { Id::new(msg_send![class!(NSObject), new]).unwrap() }; + let obj: Id = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; let result = unsafe { catch(|| throw(Some(&obj))) }; let exception = result.unwrap_err().unwrap(); diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs index d65f8c4b0..e69737580 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -1,9 +1,9 @@ //! # Objective-C interface and runtime bindings //! //! Objective-C is1 the standard programming language on Apple -//! platforms like macOS, iOS, tvOS and watchOS. It is an object-oriented -//! language centered around sending messages to it's instances, which is for -//! the most part equivalent to a function call. +//! platforms like macOS, iOS, iPadOS, tvOS and watchOS. It is an +//! object-oriented language centered around sending messages to it's +//! instances - can for the most part be viewed as a simple method call. //! //! Most of the core libraries and frameworks that are in use on Apple systems //! are written in Objective-C, and hence we would like the ability to @@ -20,31 +20,51 @@ //! //! First, we get a reference to the `NSObject`'s [`runtime::Class`] using the //! [`class!`] macro. -//! Next, we creates a new [`runtime::Object`] pointer, and ensures it is +//! Next, we creates a new [`runtime::Object`] pointer, and ensure it is //! deallocated after we've used it by putting it into an [`rc::Owned`] //! [`rc::Id`]. -//! Now we send messages to the object to our hearts desire using -//! the [`msg_send!`] macro, and lastly, the `Id` goes out of -//! scope, and the object is deallocated. +//! Now we're free to send messages to the object to our hearts desire using +//! the [`msg_send!`], [`msg_send_bool!`] or [`msg_send_id!`] macros +//! (depending on the return type of the method). +//! Finally, the `Id` goes out of scope, and the object is released +//! and deallocated. //! #![cfg_attr(feature = "apple", doc = "```")] #![cfg_attr(not(feature = "apple"), doc = "```no_run")] -//! use objc2::{class, msg_send, msg_send_bool}; +//! use objc2::{class, msg_send, msg_send_bool, msg_send_id}; //! use objc2::ffi::NSUInteger; -//! use objc2::rc::{Id, Owned}; +//! use objc2::rc::{Id, Owned, Shared}; //! use objc2::runtime::Object; //! -//! // Creation //! let cls = class!(NSObject); -//! let obj: *mut Object = unsafe { msg_send![cls, new] }; -//! let obj: Id = unsafe { -//! Id::new(obj).expect("Failed allocating object") +//! +//! // Creation +//! +//! let obj1: Id = unsafe { +//! msg_send_id![cls, new].expect("Failed allocating") +//! }; +//! let obj2: Id = unsafe { +//! // Equivalent to using `new` +//! msg_send_id![msg_send_id![cls, alloc], init].expect("Failed allocating") //! }; //! //! // Usage -//! let hash: NSUInteger = unsafe { msg_send![&obj, hash] }; -//! let is_kind = unsafe { msg_send_bool![&obj, isKindOfClass: cls] }; +//! +//! let hash1: NSUInteger = unsafe { msg_send![&obj1, hash] }; +//! let hash2: NSUInteger = unsafe { msg_send![&obj2, hash] }; +//! assert_ne!(hash1, hash2); +//! +//! let is_kind = unsafe { msg_send_bool![&obj1, isKindOfClass: cls] }; //! assert!(is_kind); +//! +//! // We're going to create a new reference to the first object, so +//! // relinquish mutable ownership. +//! let obj1: Id = obj1.into(); +//! let obj1_self: Id = unsafe { msg_send_id![&obj1, self].unwrap() }; +//! let is_equal = unsafe { msg_send_bool![&obj1, isEqual: &*obj1_self] }; +//! assert!(is_equal); +//! +//! // Deallocation on drop //! ``` //! //! Note that this very simple example contains **a lot** of `unsafe` (which @@ -56,9 +76,10 @@ //! //! Making the ergonomics better is something that is currently being worked //! on, see e.g. the [`objc2-foundation`] crate for more ergonomic usage of at -//! least the `Foundation` framework. +//! least parts of the `Foundation` framework. //! -//! Anyhow, this nicely leads us to another feature that this crate has: +//! Anyhow, all of this `unsafe` nicely leads us to another feature that this +//! crate has: //! //! [`runtime::Class`]: crate::runtime::Class //! [`runtime::Object`]: crate::runtime::Object @@ -71,23 +92,45 @@ //! //! The Objective-C runtime includes encodings for each method that describe //! the argument and return types. See the [`objc2-encode`] crate for the -//! full overview of what this is. +//! full overview of what this is (its types are re-exported in this crate). //! -//! The important part is, to make message sending _safer_ (not fully safe), -//! all arguments and return values for messages must implement [`Encode`]. -//! This allows the Rust compiler to prevent you from passing e.g. a [`Box`] -//! into Objective-C, which would both be UB and leak the box. +//! The important part is: To make message sending safer, all arguments and +//! return values for messages must implement [`Encode`]. This allows the Rust +//! compiler to prevent you from passing e.g. a [`Box`] into Objective-C, +//! which would both be UB and leak the box. //! -//! Furthermore, this crate can take advantage of the encodings provided by -//! the runtime to verify that the types used in Rust match the types encoded -//! for the method. This is not a perfect solution for ensuring safety of -//! message sends (some Rust types have the same encoding, but are not +//! Furthermore, we can take advantage of the encodings provided by the +//! runtime to verify that the types used in Rust actually match the types +//! encoded for the method. This is not a perfect solution for ensuring safety +//! (some Rust types have the same Objective-C encoding, but are not //! equivalent), but it gets us much closer to it! //! //! To use this functionality, enable the `"verify_message"` cargo feature -//! while debugging. With this feature enabled, encoding types are checked -//! every time your send a message, and the message send will panic if they -//! are not equivalent. +//! while debugging. With this feature enabled, encodings are checked every +//! time you send a message, and the message send will panic if they are not +//! equivalent. +//! +//! To take the example above, if we changed the `hash` method's return type +//! as in the following example, it panics when the feature is enabled: +//! +#![cfg_attr( + all(feature = "apple", feature = "verify_message"), + doc = "```should_panic" +)] +#![cfg_attr( + not(all(feature = "apple", feature = "verify_message")), + doc = "```no_run" +)] +//! # use objc2::{class, msg_send, msg_send_id}; +//! # use objc2::rc::{Id, Owned}; +//! # use objc2::runtime::Object; +//! # +//! # let cls = class!(NSObject); +//! # let obj1: Id = unsafe { msg_send_id![cls, new].unwrap() }; +//! # +//! // Wrong return type - this is UB! +//! let hash1: f32 = unsafe { msg_send![&obj1, hash] }; +//! ``` //! //! [`objc2-encode`]: objc2_encode //! [`Box`]: std::boxed::Box @@ -108,12 +151,12 @@ //! see the [`objc-sys`][`objc_sys`] crate for how to configure this. //! //! -//! ## Other features +//! ## Other functionality //! -//! Anyhow, that was a quick introduction, this library also has [support for -//! handling exceptions][exc], [the ability to dynamically declare Objective-C -//! classes][declare], [more advanced reference-counting utilities][rc] and -//! more, peruse the documentation at will! +//! That was a quick introduction, this library also has [support for handling +//! exceptions][exc], [the ability to dynamically declare Objective-C +//! classes][declare], [advanced reference-counting utilities][rc], and more - +//! peruse the documentation at will! //! #![cfg_attr(feature = "exception", doc = "[exc]: crate::exception")] #![cfg_attr(not(feature = "exception"), doc = "[exc]: #exception-feature-disabled")] @@ -167,6 +210,9 @@ pub mod runtime; #[cfg(test)] mod test_utils; +#[doc(hidden)] +pub mod __macro_helpers; + /// Hacky way to make GNUStep link properly to Foundation while testing. /// /// This is a temporary solution to make our CI work for now! diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 227cfb4b2..86509da17 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -52,91 +52,116 @@ macro_rules! sel { }); } -/// Sends a message to an object or class. +/// Send a message to an object or class. /// /// This is wildly `unsafe`, even more so than sending messages in -/// Objective-C, because this macro doesn't know the expected types and -/// because Rust has more safety invariants to uphold. Make sure to review the -/// safety section below! +/// Objective-C, because this macro can't inspect header files to see the +/// expected types, and because Rust has more safety invariants to uphold. +/// Make sure to review the safety section below! /// -/// # General information +/// The recommended way of using this macro is by defining a wrapper function: +/// +/// ``` +/// # use std::os::raw::{c_int, c_char}; +/// # use objc2::msg_send; +/// # use objc2::runtime::Object; +/// unsafe fn do_something(obj: &Object, arg: c_int) -> *const c_char { +/// msg_send![obj, doSomething: arg] +/// } +/// ``` +/// +/// This way we are clearly communicating to Rust that: The method +/// `doSomething:` works with a shared reference to the object. It takes a +/// C-style signed integer, and returns a pointer to what is probably a +/// C-compatible string. Now it's much, _much_ easier to make a safe +/// abstraction around this! +/// +/// There exists two variants of this macro, [`msg_send_bool!`] and +/// [`msg_send_id!`], which can help with upholding certain requirements of +/// methods that return respectively Objective-C's `BOOL` and `id` (or any +/// object pointer). Use those whenever you want to call such a method! +/// +/// [`msg_send_bool!`]: crate::msg_send_bool +/// [`msg_send_id!`]: crate::msg_send_id /// -/// The syntax is similar to the message syntax in Objective-C, except we -/// allow an optional comma between arguments (works better with rustfmt). /// -/// The first argument (know as the "receiver") can be any type that +/// # Specification +/// +/// The syntax is similar to the message syntax in Objective-C, except with +/// an (optional, though consider that deprecated) comma between arguments, +/// since that works much better with rustfmt. +/// +/// The first expression, know as the "receiver", can be any type that /// implements [`MessageReceiver`], like a reference or a pointer to an /// object, or even a reference to an [`rc::Id`] containing an object. -/// Each subsequent argument must implement [`Encode`]. /// -/// Behind the scenes this translates into a call to [`sel!`], and afterwards -/// a fully qualified call to [`MessageReceiver::send_message`] (note that -/// this means that auto-dereferencing of the receiver is not supported, -/// making the ergonomics when using this slightly worse). +/// All arguments, and the return type, must implement [`Encode`]. +/// +/// This macro translates into a call to [`sel!`], and afterwards a fully +/// qualified call to [`MessageReceiver::send_message`]. Note that this means +/// that auto-dereferencing of the receiver is not supported, and that the +/// receiver is consumed. You may encounter a little trouble with `&mut` +/// references, try refactoring into a separate method or reborrowing the +/// reference. /// -/// Variadic arguments are not currently supported. +/// Variadic arguments are currently not supported. /// /// [`MessageReceiver`]: crate::MessageReceiver /// [`rc::Id`]: crate::rc::Id /// [`Encode`]: crate::Encode /// [`MessageReceiver::send_message`]: crate::MessageReceiver::send_message /// +/// /// # Panics /// -/// Panics if the `catch_all` feature is enabled and the Objective-C method -/// throws an exception. Exceptions may however still cause UB until we get -/// `extern "C-unwind"`, see [RFC-2945]. +/// Panics if the `"catch_all"` feature is enabled and the Objective-C method +/// throws an exception. Exceptions may still cause UB until +/// `extern "C-unwind"` is stable, see [RFC-2945]. /// -/// And panics if the `verify_message` feature is enabled and the Objective-C +/// Panics if the `"verify_message"` feature is enabled and the Objective-C /// method's argument's encoding does not match the encoding of the given /// arguments. This is highly recommended to enable while testing! /// +/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html +/// +/// /// # Safety /// -/// This macro can't inspect header files to see the expected types, so it is -/// your responsibility that the selector exists on the receiver, and that the -/// argument types and return type are what the receiver excepts for this -/// selector - similar to defining an external function in FFI. +/// Similar to defining and calling an `extern` function in a foreign function +/// interface. In particular, you must uphold the following requirements: /// -/// The recommended way of doing this is by defining a wrapper function: -/// ``` -/// # use std::os::raw::{c_int, c_char}; -/// # use objc2::msg_send; -/// # use objc2::runtime::Object; -/// unsafe fn do_something(obj: &Object, arg: c_int) -> *const c_char { -/// msg_send![obj, doSomething: arg] -/// } -/// ``` +/// 1. The selector corresponds to a valid method that is available on the +/// receiver. /// -/// This way we are clearly communicating to Rust that this method takes an -/// immutable object, a C-integer, and returns a pointer to (probably) a -/// C-compatible string. Afterwards, it becomes fairly trivial to make a safe -/// abstraction around this. +/// 2. The argument types match what the receiver excepts for this selector. /// -/// In particular, you must uphold the following requirements: +/// 3. The return type match what the receiver returns for this selector. /// -/// 1. The selector is a valid method that is available on the given receiver. +/// 4. The call must not violate Rust's mutability rules, for example if +/// passing an `&T`, the Objective-C method must not mutate the variable +/// (of course except if the variable is inside [`std::cell::UnsafeCell`]). /// -/// 2. The types of the receiver and arguments must match what is expected on -/// the Objective-C side. +/// 5. If the receiver is a raw pointer it must be valid (aligned, +/// dereferenceable, initialized and so on). Messages to `null` pointers +/// are allowed (though heavily discouraged), but _only_ if the return type +/// itself is a pointer. /// -/// 3. The call must not violate Rust's mutability rules, e.g. if passing an -/// `&T`, the Objective-C method must not mutate the variable (this is true -/// for receivers as well). +/// 6. The method must not (yet) throw an exception. /// -/// 4. If the receiver is a raw pointer the user must ensure that it is valid -/// (aligned, dereferenceable, initialized and so on). Messages to `null` -/// pointers are allowed (though heavily discouraged), but only if the -/// return type itself is a pointer. +/// 7. You must uphold any additional safety requirements (explicit and +/// implicit) that the method has. For example: +/// - Methods that take pointers usually require that the pointer is valid, +/// and sometimes non-null. +/// - Sometimes, a method may only be called on the main thread. +/// - The lifetime of returned pointers usually follows certain rules, and +/// may not be valid outside of an [`autoreleasepool`] ([`msg_send_id!`] +/// can greatly help with that). /// -/// 5. The method must not (yet, see [RFC-2945]) throw an exception. +/// 8. TODO: Maybe more? /// -/// 6. You must uphold any additional safety requirements (explicit and -/// implicit) that the method has (for example, methods that take pointers -/// usually require that the pointer is valid, and sometimes non-null. -/// Another example, some methods may only be called on the main thread). +/// [`autoreleasepool`]: crate::rc::autoreleasepool +/// [`msg_send_id!`]: crate::msg_send_id /// -/// 7. TODO: Maybe more? /// /// # Examples /// @@ -146,12 +171,11 @@ macro_rules! sel { /// let obj: *mut Object; /// # let obj: *mut Object = 0 as *mut Object; /// let description: *const Object = unsafe { msg_send![obj, description] }; -/// let _: () = unsafe { msg_send![obj, setArg1: 1 arg2: 2] }; -/// // Or with an optional comma between arguments: -/// let _: () = unsafe { msg_send![obj, setArg1: 1, arg2: 2] }; +/// // Usually you'd use msg_send_id here ^ +/// let _: () = unsafe { msg_send![obj, setArg1: 1u32, arg2: 2i32] }; +/// let arg1: i32 = unsafe { msg_send![obj, getArg1] }; +/// let arg2: i32 = unsafe { msg_send![obj, getArg2] }; /// ``` -/// -/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html #[macro_export] macro_rules! msg_send { [super($obj:expr, $superclass:expr), $selector:ident $(,)?] => ({ @@ -192,33 +216,48 @@ macro_rules! msg_send { }); } -/// A less error-prone version of [`msg_send!`] for methods returning `BOOL`. +/// [`msg_send!`] for methods returning `BOOL`. +/// +/// Objective-C's `BOOL` is different from Rust's [`bool`], see +/// [`runtime::Bool`] for more information, so a conversion step must be +/// performed before using it - this can easily be forgotted using the +/// [`msg_send!`] macro, so this is a less error-prone version does the +/// conversion for you! /// -/// Objective-C's `BOOL` is different from Rust's [`bool`] (see [`Bool`]), so -/// a conversion step must be performed before using it - this macro does that -/// for you! +/// [`runtime::Bool`]: crate::runtime::Bool /// -/// [`Bool`]: crate::runtime::Bool +/// +/// # Specification /// /// Equivalent to the following: /// -/// ```ignore +/// ```no_run /// # use objc2::msg_send; -/// # use objc2::runtime::Bool; +/// # use objc2::runtime::{Bool, Object}; /// # let obj: *mut Object = 0 as *mut Object; +/// # unsafe /// { /// let result: Bool = msg_send![obj, selector]; /// result.as_bool() /// }; /// ``` /// +/// +/// # Safety +/// +/// Same as [`msg_send!`], with the expected return type of `BOOL`. +/// +/// /// # Examples /// -/// ```no_run -/// # use objc2::msg_send_bool; +#[cfg_attr(feature = "apple", doc = "```")] +#[cfg_attr(not(feature = "apple"), doc = "```no_run")] +/// # use objc2::{class, msg_send_bool, msg_send_id}; +/// # use objc2::rc::{Id, Owned}; /// # use objc2::runtime::Object; -/// # let obj: *mut Object = 0 as *mut Object; -/// assert!(unsafe { msg_send_bool![obj, isEqual: obj] }); +/// let obj: Id; +/// # obj = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; +/// assert!(unsafe { msg_send_bool![&obj, isEqual: &*obj] }); /// ``` #[macro_export] macro_rules! msg_send_bool { @@ -227,3 +266,177 @@ macro_rules! msg_send_bool { result.as_bool() }); } + +/// [`msg_send!`] for methods returning `id`, `NSObject*`, or similar object +/// pointers. +/// +/// Object pointers in Objective-C have certain rules for when they should be +/// retained and released across function calls. This macro helps doing that, +/// and returns an [`Option`] (letting you handle failures) containing an +/// [`rc::Id`] with the object. +/// +/// [`rc::Id`]: crate::rc::Id +/// +/// +/// # A little history +/// +/// Objective-C's type system is... limited, so you can't easily tell who is +/// responsible for releasing an object. To remedy this problem, Apple/Cocoa +/// introduced approximately the following rule: +/// +/// The caller is responsible for releasing objects return from methods that +/// begin with `new`, `alloc`, `copy`, `mutableCopy` or `init`, and method +/// that begins with `init` takes ownership of the receiver. See [Cocoa's +/// Memory Management Policy][mmRules] for a user-friendly introduction to +/// this concept. +/// +/// In the past, users had to do `retain` and `release` calls themselves to +/// properly follow these rules. To avoid the memory management problems +/// associated with manual stuff like that, they [introduced "ARC"][arc-rel], +/// which codifies the rules as part of the language, and inserts the required +/// `retain` and `release` calls automatically. +/// +/// [`msg_send!`] is similar to pre-ARC; you have to know when to retain and +/// when to release an object. [`msg_send_id!`] is similar to ARC; the rules +/// are simple enough that we can do them automatically! +/// +/// [mmRules]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1 +/// [arc-rel]: https://developer.apple.com/library/archive/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226 +/// +/// [`msg_send_id!`]: crate::msg_send_id +/// +/// +/// # Specification +/// +/// The syntax is the same as in [`msg_send!`]. +/// +/// Attributes like `objc_method_family`, `ns_returns_retained`, `ns_consumed` +/// and so on must not present on the method - if they are, you should do +/// manual memory management using the [`msg_send!`] macro instead. +/// +/// The accepted receiver and return types, and how we handle them, differ +/// depending on which, if any, of the [recognized selector +/// families][sel-families] the selector belongs to (here `T: Message` and +/// `O: Ownership`): +/// +/// - The `new` family: The receiver must be `&Class`, and the return type +/// is a generic `Option>`. +/// +/// - The `alloc` family: The receiver must be `&Class`, and the return type +/// is a generic `Option>`. (This will change, see [#172]). +/// +/// - The `init` family: The receiver must be `Option>` as returned +/// from `alloc`. The receiver is consumed, and a the now-initialized +/// `Option>` (with the same `T` and `O`) is returned. +/// +/// - The `copy` family: The receiver may be anything that implements +/// [`MessageReceiver`] and the return type is a generic `Option>`. +/// +/// - The `mutableCopy` family: Same as the `copy` family. +/// +/// - No family: The receiver may be anything that implements +/// [`MessageReceiver`]. The result is retained using +/// [`Id::retain_autoreleased`], and a generic `Option>` is +/// returned. This retain is in most cases faster than using autorelease +/// pools! +/// +/// See [the clang documentation][arc-retainable] for the precise +/// specification of Objective-C's ownership rules. +/// +/// This macro doesn't support super methods yet, see [#173]. +/// The `retain`, `release` and `autorelease` selectors are not supported, use +/// [`Id::retain`], [`Id::drop`] and [`Id::autorelease`] for that. +/// +/// [sel-families]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families +/// [#172]: https://github.com/madsmtm/objc2/pull/172 +/// [`MessageReceiver`]: crate::MessageReceiver +/// [`Id::retain_autoreleased`]: crate::rc::Id::retain_autoreleased +/// [arc-retainable]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments +/// [#173]: https://github.com/madsmtm/objc2/pull/173 +/// [`Id::retain`]: crate::rc::Id::retain +/// [`Id::drop`]: crate::rc::Id::drop +/// [`Id::autorelease`]: crate::rc::Id::autorelease +/// +/// +/// # Safety +/// +/// Same as [`msg_send!`], with an expected return type of `id`, +/// `instancetype`, `NSObject*`, or other such object pointers. The method +/// must not have any attributes that changes the how it handles memory +/// management. +/// +/// +/// # Examples +/// +/// ```no_run +/// use objc2::{class, msg_send, msg_send_bool, msg_send_id}; +/// use objc2::ffi::NSUInteger; +/// use objc2::rc::{Id, Shared}; +/// use objc2::runtime::Object; +// Allocate new object +/// let obj = unsafe { msg_send_id![class!(NSObject), alloc] }; +/// // Consume the allocated object, return initialized object +/// let obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; +/// // Copy the object +/// let copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; +/// // Call ordinary selector that returns an object +/// let s: Id = unsafe { msg_send_id![&obj, description].unwrap() }; +/// ``` +#[macro_export] +macro_rules! msg_send_id { + [$obj:expr, $selector:ident $(,)?] => ({ + $crate::__msg_send_id_helper!(@verify $selector); + let sel = $crate::sel!($selector); + const NAME: &[u8] = stringify!($selector).as_bytes(); + $crate::__msg_send_id_helper!(@get_assert_consts NAME); + let result: Option<$crate::rc::Id<_, _>>; + match >::send_message_id($obj, sel, ()) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); + [$obj:expr, $($selector:ident : $argument:expr),+ $(,)?] => ({ + let sel = $crate::sel!($($selector:)+); + const NAME: &[u8] = concat!($(stringify!($selector), ':'),+).as_bytes(); + $crate::__msg_send_id_helper!(@get_assert_consts NAME); + let result: Option<$crate::rc::Id<_, _>>; + match >::send_message_id($obj, sel, ($($argument,)+)) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); +} + +/// Helper macro to avoid exposing these in the docs for [`msg_send_id!`]. +#[doc(hidden)] +#[macro_export] +macro_rules! __msg_send_id_helper { + (@verify retain) => {{ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, retain] is not supported. Use `Id::retain` instead" + ) + }}; + (@verify release) => {{ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, release] is not supported. Drop an `Id` instead" + ) + }}; + (@verify autorelease) => {{ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, autorelease] is not supported. Use `Id::autorelease`" + ) + }}; + (@verify $selector:ident) => {{}}; + (@get_assert_consts $selector:ident) => { + const NEW: bool = $crate::__macro_helpers::in_selector_family($selector, b"new"); + const ALLOC: bool = $crate::__macro_helpers::in_selector_family($selector, b"alloc"); + const INIT: bool = $crate::__macro_helpers::in_selector_family($selector, b"init"); + const COPY_OR_MUT_COPY: bool = { + $crate::__macro_helpers::in_selector_family($selector, b"copy") + || $crate::__macro_helpers::in_selector_family($selector, b"mutableCopy") + }; + type RS = $crate::__macro_helpers::RetainSemantics; + }; +} diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index 68b5edbb2..d9d999eb4 100644 --- a/objc2/src/message/mod.rs +++ b/objc2/src/message/mod.rs @@ -452,10 +452,13 @@ mod tests { #[cfg(not(feature = "verify_message"))] #[test] fn test_send_message_nil() { + use crate::rc::Shared; + let nil: *mut Object = ::core::ptr::null_mut(); - let result: *mut Object = unsafe { msg_send![nil, description] }; - assert!(result.is_null()); + // This result should not be relied on + let result: Option> = unsafe { msg_send_id![nil, description] }; + assert!(result.is_none()); // This result should not be relied on let result: usize = unsafe { msg_send![nil, hash] }; @@ -471,8 +474,13 @@ mod tests { assert_eq!(result, 0.0); // This result should not be relied on - let result: *mut Object = unsafe { msg_send![nil, multiple: 1u32, arguments: 2i8] }; - assert!(result.is_null()); + let result: Option> = + unsafe { msg_send_id![nil, multiple: 1u32, arguments: 2i8] }; + assert!(result.is_none()); + + // This result should not be relied on + let result: Option> = unsafe { msg_send_id![None, init] }; + assert!(result.is_none()); } #[test] diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index 9a75f783b..5375e75b7 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -218,20 +218,33 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// error in a future release. You can test the compile error with the /// `unstable-autoreleasesafe` crate feature on nightly Rust. /// +/// Note that this is mostly useful for preventing leaks (as any Objective-C +/// method may leak internally). If implementing an interface to an object, +/// you should try to return retained pointers with [`msg_send_id!`] wherever +/// you can instead, since having to use this function can be quite cumbersome +/// for your users! +/// +/// /// # Examples /// /// Basic usage: /// /// ```no_run -/// use objc2::{class, msg_send}; -/// use objc2::rc::{autoreleasepool, AutoreleasePool}; +/// use core::mem::ManuallyDrop; +/// use objc2::{class, msg_send, msg_send_id}; +/// use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned}; /// use objc2::runtime::Object; /// /// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { -/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; +/// let obj: Id = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; +/// let obj = ManuallyDrop::new(obj); /// let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; /// // Lifetime of the returned reference is bounded by the pool /// unsafe { pool.ptr_as_mut(obj) } +/// +/// // Or simply +/// // let obj: Id = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; +/// // obj.autorelease(pool) /// } /// /// autoreleasepool(|pool| { @@ -246,14 +259,13 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// safely take it out of the pool: /// /// ```compile_fail -/// # use objc2::{class, msg_send}; -/// # use objc2::rc::{autoreleasepool, AutoreleasePool}; +/// # use objc2::{class, msg_send_id, Id, Owned}; +/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned}; /// # use objc2::runtime::Object; /// # /// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { -/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; -/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; -/// # unsafe { pool.ptr_as_mut(obj) } +/// # let obj: Id = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; +/// # obj.autorelease(pool) /// # } /// # /// let obj = autoreleasepool(|pool| { @@ -268,14 +280,13 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// #[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail")] #[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")] -/// # use objc2::{class, msg_send}; -/// # use objc2::rc::{autoreleasepool, AutoreleasePool}; +/// # use objc2::{class, msg_send_id}; +/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned}; /// # use objc2::runtime::Object; /// # /// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { -/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; -/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; -/// # unsafe { pool.ptr_as_mut(obj) } +/// # let obj: Id = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; +/// # obj.autorelease(pool) /// # } /// # /// autoreleasepool(|outer_pool| { diff --git a/objc2/src/rc/id.rs b/objc2/src/rc/id.rs index eb7738392..74cf4b2fa 100644 --- a/objc2/src/rc/id.rs +++ b/objc2/src/rc/id.rs @@ -1,6 +1,6 @@ use core::fmt; use core::marker::PhantomData; -use core::mem::ManuallyDrop; +use core::mem::{self, ManuallyDrop}; use core::ops::{Deref, DerefMut}; use core::panic::{RefUnwindSafe, UnwindSafe}; use core::ptr::NonNull; @@ -59,13 +59,13 @@ use crate::Message; /// # Examples /// /// ```no_run -/// use objc2::msg_send; +/// use objc2::msg_send_id; /// use objc2::runtime::{Class, Object}; /// use objc2::rc::{Id, Owned, Shared, WeakId}; /// /// let cls = Class::get("NSObject").unwrap(); /// let obj: Id = unsafe { -/// Id::new(msg_send![cls, new]).unwrap() +/// msg_send_id![cls, new].unwrap() /// }; /// // obj will be released when it goes out of scope /// @@ -83,12 +83,12 @@ use crate::Message; /// ``` /// /// ```no_run -/// # use objc2::{class, msg_send}; +/// # use objc2::{class, msg_send_id}; /// # use objc2::runtime::Object; /// # use objc2::rc::{Id, Owned, Shared}; /// # type T = Object; /// let mut owned: Id; -/// # owned = unsafe { Id::new(msg_send![class!(NSObject), new]).unwrap() }; +/// # owned = unsafe { msg_send_id![class!(NSObject), new].unwrap() }; /// let mut_ref: &mut T = &mut *owned; /// // Do something with `&mut T` here /// @@ -149,25 +149,28 @@ impl Id { /// # Example /// /// ```no_run - /// # use objc2::{class, msg_send}; + /// # use objc2::{class, msg_send, msg_send_id}; /// # use objc2::runtime::{Class, Object}; /// # use objc2::rc::{Id, Owned}; /// let cls: &Class; /// # let cls = class!(NSObject); /// let obj: &mut Object = unsafe { msg_send![cls, alloc] }; /// let obj: Id = unsafe { Id::new(msg_send![obj, init]).unwrap() }; + /// // Or utilizing `msg_send_id`: + /// let obj = unsafe { msg_send_id![cls, alloc] }; + /// let obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; /// // Or in this case simply just: - /// let obj: Id = unsafe { Id::new(msg_send![cls, new]).unwrap() }; + /// let obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; /// ``` /// /// ```no_run - /// # use objc2::{class, msg_send}; + /// # use objc2::{class, msg_send_id}; /// # use objc2::runtime::Object; /// # use objc2::rc::{Id, Shared}; /// # type NSString = Object; /// let cls = class!(NSString); /// // NSString is immutable, so don't create an owned reference to it - /// let obj: Id = unsafe { Id::new(msg_send![cls, new]).unwrap() }; + /// let obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; /// ``` #[inline] // Note: We don't take a reference as a parameter since it would be too @@ -204,6 +207,15 @@ impl Id { pub(crate) fn consume_as_ptr(this: ManuallyDrop) -> *mut T { this.ptr.as_ptr() } + + #[inline] + pub(crate) fn option_into_ptr(obj: Option) -> *mut T { + // Difficult to write this in an ergonomic way with ?Sized + // So we just hack it with transmute! + + // SAFETY: Option> has the same size as *mut T + unsafe { mem::transmute::>, *mut T>(ManuallyDrop::new(obj)) } + } } impl Id { diff --git a/objc2/src/rc/mod.rs b/objc2/src/rc/mod.rs index 14fa9d0e6..bbc04e1f0 100644 --- a/objc2/src/rc/mod.rs +++ b/objc2/src/rc/mod.rs @@ -32,13 +32,13 @@ //! #![cfg_attr(feature = "apple", doc = "```")] #![cfg_attr(not(feature = "apple"), doc = "```no_run")] -//! use objc2::{class, msg_send}; +//! use objc2::{class, msg_send_id}; //! use objc2::rc::{autoreleasepool, Id, Shared, WeakId}; //! use objc2::runtime::Object; //! //! // Id will release the object when dropped //! let obj: Id = unsafe { -//! Id::new(msg_send![class!(NSObject), new]).unwrap() +//! msg_send_id![class!(NSObject), new].unwrap() //! }; //! //! // Cloning retains the object an additional time diff --git a/objc2/src/rc/test_object.rs b/objc2/src/rc/test_object.rs index 8b06c05f2..6db124120 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -77,7 +77,7 @@ impl DerefMut for RcTestObject { } impl RcTestObject { - fn class() -> &'static Class { + pub(crate) fn class() -> &'static Class { static REGISTER_CLASS: Once = Once::new(); REGISTER_CLASS.call_once(|| { @@ -152,6 +152,7 @@ impl RcTestObject { } pub(crate) fn new() -> Id { + // Use msg_send! - msg_send_id! is tested elsewhere! unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap() } } diff --git a/tests/assembly/test_msg_send_id/Cargo.toml b/tests/assembly/test_msg_send_id/Cargo.toml new file mode 100644 index 000000000..387607d2a --- /dev/null +++ b/tests/assembly/test_msg_send_id/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test_msg_send_id" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "lib.rs" + +[dependencies] +objc2 = { path = "../../../objc2", default-features = false } + +[features] +default = ["apple"] +# Runtime +apple = ["objc2/apple"] +gnustep-1-7 = ["objc2/gnustep-1-7"] +gnustep-1-8 = ["gnustep-1-7", "objc2/gnustep-1-8"] +gnustep-1-9 = ["gnustep-1-8", "objc2/gnustep-1-9"] +gnustep-2-0 = ["gnustep-1-9", "objc2/gnustep-2-0"] +gnustep-2-1 = ["gnustep-2-0", "objc2/gnustep-2-1"] diff --git a/tests/assembly/test_msg_send_id/expected/apple-aarch64.s b/tests/assembly/test_msg_send_id/expected/apple-aarch64.s new file mode 100644 index 000000000..de2bce3a9 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-aarch64.s @@ -0,0 +1,65 @@ + .section __TEXT,__text,regular,pure_instructions + .globl _handle_alloc + .p2align 2 +_handle_alloc: + b _objc_msgSend + + .globl _handle_init + .p2align 2 +_handle_init: + b _objc_msgSend + + .globl _handle_alloc_init + .p2align 2 +_handle_alloc_init: + stp x20, x19, [sp, #-32]! + stp x29, x30, [sp, #16] + add x29, sp, #16 + mov x19, x2 + bl _objc_msgSend + mov x1, x19 + ldp x29, x30, [sp, #16] + ldp x20, x19, [sp], #32 + b _objc_msgSend + + .globl _handle_alloc_release + .p2align 2 +_handle_alloc_release: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _objc_msgSend + ldp x29, x30, [sp], #16 + b _objc_release + + .globl _handle_alloc_init_release + .p2align 2 +_handle_alloc_init_release: + stp x20, x19, [sp, #-32]! + stp x29, x30, [sp, #16] + add x29, sp, #16 + mov x19, x2 + bl _objc_msgSend + mov x1, x19 + bl _objc_msgSend + ldp x29, x30, [sp, #16] + ldp x20, x19, [sp], #32 + b _objc_release + + .globl _handle_copy + .p2align 2 +_handle_copy: + b _objc_msgSend + + .globl _handle_autoreleased + .p2align 2 +_handle_autoreleased: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _objc_msgSend + ; InlineAsm Start + mov x29, x29 + ; InlineAsm End + ldp x29, x30, [sp], #16 + b _objc_retainAutoreleasedReturnValue + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/expected/apple-armv7.s b/tests/assembly/test_msg_send_id/expected/apple-armv7.s new file mode 100644 index 000000000..561d660d3 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7.s @@ -0,0 +1,69 @@ + .section __TEXT,__text,regular,pure_instructions + .syntax unified + .globl _handle_alloc + .p2align 2 + .code 32 +_handle_alloc: + b _objc_msgSend + + .globl _handle_init + .p2align 2 + .code 32 +_handle_init: + b _objc_msgSend + + .globl _handle_alloc_init + .p2align 2 + .code 32 +_handle_alloc_init: + push {r4, r7, lr} + add r7, sp, #4 + mov r4, r2 + bl _objc_msgSend + mov r1, r4 + pop {r4, r7, lr} + b _objc_msgSend + + .globl _handle_alloc_release + .p2align 2 + .code 32 +_handle_alloc_release: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + pop {r7, lr} + b _objc_release + + .globl _handle_alloc_init_release + .p2align 2 + .code 32 +_handle_alloc_init_release: + push {r4, r7, lr} + add r7, sp, #4 + mov r4, r2 + bl _objc_msgSend + mov r1, r4 + bl _objc_msgSend + pop {r4, r7, lr} + b _objc_release + + .globl _handle_copy + .p2align 2 + .code 32 +_handle_copy: + b _objc_msgSend + + .globl _handle_autoreleased + .p2align 2 + .code 32 +_handle_autoreleased: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + @ InlineAsm Start + mov r7, r7 + @ InlineAsm End + pop {r7, lr} + b _objc_retainAutoreleasedReturnValue + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/expected/apple-armv7s.s b/tests/assembly/test_msg_send_id/expected/apple-armv7s.s new file mode 100644 index 000000000..046611c4c --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7s.s @@ -0,0 +1,78 @@ + .section __TEXT,__text,regular,pure_instructions + .syntax unified + .globl _handle_alloc + .p2align 2 + .code 32 +_handle_alloc: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + pop {r7, pc} + + .globl _handle_init + .p2align 2 + .code 32 +_handle_init: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + pop {r7, pc} + + .globl _handle_alloc_init + .p2align 2 + .code 32 +_handle_alloc_init: + push {r4, r7, lr} + add r7, sp, #4 + mov r4, r2 + bl _objc_msgSend + mov r1, r4 + bl _objc_msgSend + pop {r4, r7, pc} + + .globl _handle_alloc_release + .p2align 2 + .code 32 +_handle_alloc_release: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + bl _objc_release + pop {r7, pc} + + .globl _handle_alloc_init_release + .p2align 2 + .code 32 +_handle_alloc_init_release: + push {r4, r7, lr} + add r7, sp, #4 + mov r4, r2 + bl _objc_msgSend + mov r1, r4 + bl _objc_msgSend + bl _objc_release + pop {r4, r7, pc} + + .globl _handle_copy + .p2align 2 + .code 32 +_handle_copy: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + pop {r7, pc} + + .globl _handle_autoreleased + .p2align 2 + .code 32 +_handle_autoreleased: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + @ InlineAsm Start + mov r7, r7 + @ InlineAsm End + bl _objc_retainAutoreleasedReturnValue + pop {r7, pc} + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/expected/apple-x86.s b/tests/assembly/test_msg_send_id/expected/apple-x86.s new file mode 100644 index 000000000..b42d5f569 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-x86.s @@ -0,0 +1,110 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .globl _handle_alloc + .p2align 4, 0x90 +_handle_alloc: + push ebp + mov ebp, esp + pop ebp + jmp _objc_msgSend + + .globl _handle_init + .p2align 4, 0x90 +_handle_init: + push ebp + mov ebp, esp + pop ebp + jmp _objc_msgSend + + .globl _handle_alloc_init + .p2align 4, 0x90 +_handle_alloc_init: + push ebp + mov ebp, esp + push esi + push eax + mov esi, dword ptr [ebp + 16] + sub esp, 8 + push dword ptr [ebp + 12] + push dword ptr [ebp + 8] + call _objc_msgSend + add esp, 8 + push esi + push eax + call _objc_msgSend + add esp, 20 + pop esi + pop ebp + ret + + .globl _handle_alloc_release + .p2align 4, 0x90 +_handle_alloc_release: + push ebp + mov ebp, esp + sub esp, 8 + mov eax, dword ptr [ebp + 8] + mov ecx, dword ptr [ebp + 12] + mov dword ptr [esp + 4], ecx + mov dword ptr [esp], eax + call _objc_msgSend + mov dword ptr [esp], eax + call _objc_release + add esp, 8 + pop ebp + ret + + .globl _handle_alloc_init_release + .p2align 4, 0x90 +_handle_alloc_init_release: + push ebp + mov ebp, esp + push esi + sub esp, 20 + mov esi, dword ptr [ebp + 16] + mov eax, dword ptr [ebp + 8] + mov ecx, dword ptr [ebp + 12] + mov dword ptr [esp + 4], ecx + mov dword ptr [esp], eax + call _objc_msgSend + mov dword ptr [esp + 4], esi + mov dword ptr [esp], eax + call _objc_msgSend + mov dword ptr [esp], eax + call _objc_release + add esp, 20 + pop esi + pop ebp + ret + + .globl _handle_copy + .p2align 4, 0x90 +_handle_copy: + push ebp + mov ebp, esp + pop ebp + jmp _objc_msgSend + + .globl _handle_autoreleased + .p2align 4, 0x90 +_handle_autoreleased: + push ebp + mov ebp, esp + sub esp, 8 + mov eax, dword ptr [ebp + 8] + mov ecx, dword ptr [ebp + 12] + mov dword ptr [esp + 4], ecx + mov dword ptr [esp], eax + call _objc_msgSend + ## InlineAsm Start + + mov ebp, ebp + + ## InlineAsm End + mov dword ptr [esp], eax + call _objc_retainAutoreleasedReturnValue + add esp, 8 + pop ebp + ret + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/expected/apple-x86_64.s b/tests/assembly/test_msg_send_id/expected/apple-x86_64.s new file mode 100644 index 000000000..09136fc21 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-x86_64.s @@ -0,0 +1,87 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .globl _handle_alloc + .p2align 4, 0x90 +_handle_alloc: + push rbp + mov rbp, rsp + pop rbp + jmp _objc_msgSend + + .globl _handle_init + .p2align 4, 0x90 +_handle_init: + push rbp + mov rbp, rsp + pop rbp + jmp _objc_msgSend + + .globl _handle_alloc_init + .p2align 4, 0x90 +_handle_alloc_init: + push rbp + mov rbp, rsp + push rbx + push rax + mov rbx, rdx + call _objc_msgSend + mov rdi, rax + mov rsi, rbx + add rsp, 8 + pop rbx + pop rbp + jmp _objc_msgSend + + .globl _handle_alloc_release + .p2align 4, 0x90 +_handle_alloc_release: + push rbp + mov rbp, rsp + call _objc_msgSend + mov rdi, rax + pop rbp + jmp _objc_release + + .globl _handle_alloc_init_release + .p2align 4, 0x90 +_handle_alloc_init_release: + push rbp + mov rbp, rsp + push rbx + push rax + mov rbx, rdx + call _objc_msgSend + mov rdi, rax + mov rsi, rbx + call _objc_msgSend + mov rdi, rax + add rsp, 8 + pop rbx + pop rbp + jmp _objc_release + + .globl _handle_copy + .p2align 4, 0x90 +_handle_copy: + push rbp + mov rbp, rsp + pop rbp + jmp _objc_msgSend + + .globl _handle_autoreleased + .p2align 4, 0x90 +_handle_autoreleased: + push rbp + mov rbp, rsp + call _objc_msgSend + mov rdi, rax + call _objc_retainAutoreleasedReturnValue + ## InlineAsm Start + + nop + + ## InlineAsm End + pop rbp + ret + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/lib.rs b/tests/assembly/test_msg_send_id/lib.rs new file mode 100644 index 000000000..b2826944d --- /dev/null +++ b/tests/assembly/test_msg_send_id/lib.rs @@ -0,0 +1,47 @@ +//! Test assembly output of `msg_send_id!` internals. +use objc2::__macro_helpers::{MsgSendId, RetainSemantics}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::{Class, Object, Sel}; + +#[no_mangle] +unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Option> { + >::send_message_id(obj, sel, ()).unwrap() +} + +#[no_mangle] +unsafe fn handle_init(obj: Option>, sel: Sel) -> Option> { + >::send_message_id(obj, sel, ()).unwrap() +} + +#[no_mangle] +unsafe fn handle_alloc_init(obj: &Class, sel1: Sel, sel2: Sel) -> Option> { + let obj = >::send_message_id(obj, sel1, ()).unwrap(); + >::send_message_id(obj, sel2, ()).unwrap() +} + +#[no_mangle] +unsafe fn handle_alloc_release(cls: &Class, sel: Sel) { + let _obj: Id = + >::send_message_id(cls, sel, ()) + .unwrap() + .unwrap_unchecked(); +} + +#[no_mangle] +unsafe fn handle_alloc_init_release(cls: &Class, sel1: Sel, sel2: Sel) { + let obj = >::send_message_id(cls, sel1, ()).unwrap(); + let _obj: Id = + >::send_message_id(obj, sel2, ()) + .unwrap() + .unwrap_unchecked(); +} + +#[no_mangle] +unsafe fn handle_copy(obj: &Object, sel: Sel) -> Option> { + >::send_message_id(obj, sel, ()).unwrap() +} + +#[no_mangle] +unsafe fn handle_autoreleased(obj: &Object, sel: Sel) -> Option> { + >::send_message_id(obj, sel, ()).unwrap() +} diff --git a/tests/src/test_object.rs b/tests/src/test_object.rs index 46d9d4a1d..2f6b4ce07 100644 --- a/tests/src/test_object.rs +++ b/tests/src/test_object.rs @@ -5,7 +5,7 @@ use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned}; use objc2::runtime::{Bool, Class, Object, Protocol}; #[cfg(feature = "malloc")] use objc2::sel; -use objc2::{class, msg_send, msg_send_bool}; +use objc2::{class, msg_send, msg_send_bool, msg_send_id}; use objc2_foundation::NSObject; #[repr(C)] @@ -26,7 +26,7 @@ impl MyTestObject { fn new() -> Id { let cls = Self::class(); - unsafe { Id::new(msg_send![cls, new]).unwrap() } + unsafe { msg_send_id![cls, new].unwrap() } } fn new_autoreleased<'p>(pool: &'p AutoreleasePool) -> &'p Self { @@ -35,6 +35,11 @@ impl MyTestObject { unsafe { pool.ptr_as_ref(ptr) } } + fn new_autoreleased_retained() -> Id { + let cls = Self::class(); + unsafe { msg_send_id![cls, getAutoreleasedInstance].unwrap_unchecked() } + } + fn add_numbers(a: c_int, b: c_int) -> c_int { let cls = Self::class(); unsafe { msg_send![cls, add: a, and: b] } @@ -154,6 +159,7 @@ fn test_object() { autoreleasepool(|pool| { let _obj = MyTestObject::new_autoreleased(pool); }); + let _obj = MyTestObject::new_autoreleased_retained(); let mut obj = MyTestObject::new(); assert_eq!((*obj.inner).class(), MyTestObject::class()); diff --git a/tests/ui/msg_send_id_invalid_method.rs b/tests/ui/msg_send_id_invalid_method.rs new file mode 100644 index 000000000..597a69a75 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_method.rs @@ -0,0 +1,20 @@ +//! Test invalid msg_send_id methods. +//! +//! The `__msg_send_id_helper!` macro is unfortunately leaked, but I think +//! this is better than having it show up as part of the `msg_send_id!` macro +//! itself! +use objc2::msg_send_id; +use objc2::runtime::Object; + +fn main() { + let object: &Object; + unsafe { msg_send_id![object, retain] }; + unsafe { msg_send_id![object, release] }; + unsafe { msg_send_id![object, autorelease] }; + unsafe { + msg_send_id![ + object, + retain, + ] + }; +} diff --git a/tests/ui/msg_send_id_invalid_method.stderr b/tests/ui/msg_send_id_invalid_method.stderr new file mode 100644 index 000000000..acb6e56ec --- /dev/null +++ b/tests/ui/msg_send_id_invalid_method.stderr @@ -0,0 +1,34 @@ +error: msg_send_id![obj, retain] is not supported. Use `Id::retain` instead + --> ui/msg_send_id_invalid_method.rs:11:14 + | +11 | unsafe { msg_send_id![object, retain] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `$crate::__msg_send_id_helper` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: msg_send_id![obj, release] is not supported. Drop an `Id` instead + --> ui/msg_send_id_invalid_method.rs:12:14 + | +12 | unsafe { msg_send_id![object, release] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `$crate::__msg_send_id_helper` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: msg_send_id![obj, autorelease] is not supported. Use `Id::autorelease` + --> ui/msg_send_id_invalid_method.rs:13:14 + | +13 | unsafe { msg_send_id![object, autorelease] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `$crate::__msg_send_id_helper` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: msg_send_id![obj, retain] is not supported. Use `Id::retain` instead + --> ui/msg_send_id_invalid_method.rs:15:9 + | +15 | / msg_send_id![ +16 | | object, +17 | | retain, +18 | | ] + | |_________^ + | + = note: this error originates in the macro `$crate::__msg_send_id_helper` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/msg_send_id_invalid_receiver.rs b/tests/ui/msg_send_id_invalid_receiver.rs new file mode 100644 index 000000000..2a85010f6 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_receiver.rs @@ -0,0 +1,17 @@ +//! Test compiler output with invalid msg_send_id receivers. +use objc2::msg_send_id; +use objc2::runtime::{Class, Object}; +use objc2::rc::{Id, Shared}; + +fn main() { + let obj: &Object; + let _: Id = unsafe { msg_send_id![obj, new].unwrap() }; + let _: Id = unsafe { msg_send_id![obj, alloc].unwrap() }; + let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + + let cls: &Class; + let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; + + let obj: Id; + let _: Id = unsafe { msg_send_id![obj, copy].unwrap() }; +} diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr new file mode 100644 index 000000000..b20d72a68 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -0,0 +1,79 @@ +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:8:55 + | +8 | let _: Id = unsafe { msg_send_id![obj, new].unwrap() }; + | -------------^^^------ + | | | + | | expected struct `objc2::runtime::Class`, found struct `objc2::runtime::Object` + | arguments to this function are incorrect + | + = note: expected reference `&objc2::runtime::Class` + found reference `&objc2::runtime::Object` +note: associated function defined here + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | unsafe fn send_message_id( + | ^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:9:55 + | +9 | let _: Id = unsafe { msg_send_id![obj, alloc].unwrap() }; + | -------------^^^-------- + | | | + | | expected struct `objc2::runtime::Class`, found struct `objc2::runtime::Object` + | arguments to this function are incorrect + | + = note: expected reference `&objc2::runtime::Class` + found reference `&objc2::runtime::Object` +note: associated function defined here + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | unsafe fn send_message_id( + | ^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:10:55 + | +10 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | -------------^^^------- + | | | + | | expected enum `Option`, found `&objc2::runtime::Object` + | arguments to this function are incorrect + | + = note: expected enum `Option>` + found reference `&objc2::runtime::Object` +note: associated function defined here + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | unsafe fn send_message_id( + | ^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:13:55 + | +13 | let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; + | -------------^^^------- + | | | + | | expected enum `Option`, found `&objc2::runtime::Class` + | arguments to this function are incorrect + | + = note: expected enum `Option>` + found reference `&objc2::runtime::Class` +note: associated function defined here + --> $WORKSPACE/objc2/src/__macro_helpers.rs + | + | unsafe fn send_message_id( + | ^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `Id: MessageReceiver` is not satisfied + --> ui/msg_send_id_invalid_receiver.rs:16:42 + | +16 | let _: Id = unsafe { msg_send_id![obj, copy].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MessageReceiver` is not implemented for `Id` + | + = help: the following other types implement trait `MessageReceiver`: + &'a Id + &'a mut Id + = note: required because of the requirements on the impl of `MsgSendId, Id<_, _>>` for `RetainSemantics` + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/msg_send_id_invalid_return.rs b/tests/ui/msg_send_id_invalid_return.rs new file mode 100644 index 000000000..303f71ce2 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_return.rs @@ -0,0 +1,25 @@ +//! Test compiler output with invalid msg_send_id receivers. +use objc2::msg_send_id; +use objc2::runtime::{Class, Object}; +use objc2::rc::{Id, Owned, Shared}; +use objc2_foundation::NSObject; + +fn main() { + let cls: &Class; + let _: &Object = unsafe { msg_send_id![cls, new].unwrap() }; + let _: Id = unsafe { msg_send_id![cls, new].unwrap() }; + let _: &Object = unsafe { msg_send_id![cls, alloc].unwrap() }; + let _: Id = unsafe { msg_send_id![cls, alloc].unwrap() }; + + let obj: Option>; + let _: &Object = unsafe { msg_send_id![obj, init].unwrap() }; + let obj: Option>; + let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + let obj: Option>; + let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + let obj: Option>; + let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + + let obj: Id; + let _: &Object = unsafe { msg_send_id![&obj, description].unwrap() }; +} diff --git a/tests/ui/msg_send_id_invalid_return.stderr b/tests/ui/msg_send_id_invalid_return.stderr new file mode 100644 index 000000000..3152f36d8 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_return.stderr @@ -0,0 +1,112 @@ +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:9:31 + | +9 | let _: &Object = unsafe { msg_send_id![cls, new].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `&objc2::runtime::Object`, found struct `Id` + | help: consider borrowing here: `&msg_send_id![cls, new].unwrap()` + | + = note: expected reference `&objc2::runtime::Object` + found struct `Id<_, _>` + +error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied + --> ui/msg_send_id_invalid_return.rs:10:41 + | +10 | let _: Id = unsafe { msg_send_id![cls, new].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^ the trait `Message` is not implemented for `objc2::runtime::Class` + | + = help: the following other types implement trait `Message`: + NSArray + NSAttributedString + NSData + NSDictionary + NSMutableArray + NSMutableAttributedString + NSMutableData + NSMutableString + and 7 others + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `RetainSemantics` + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:11:31 + | +11 | let _: &Object = unsafe { msg_send_id![cls, alloc].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `&objc2::runtime::Object`, found struct `Id` + | help: consider borrowing here: `&msg_send_id![cls, alloc].unwrap()` + | + = note: expected reference `&objc2::runtime::Object` + found struct `Id<_, _>` + +error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied + --> ui/msg_send_id_invalid_return.rs:12:41 + | +12 | let _: Id = unsafe { msg_send_id![cls, alloc].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Message` is not implemented for `objc2::runtime::Class` + | + = help: the following other types implement trait `Message`: + NSArray + NSAttributedString + NSData + NSDictionary + NSMutableArray + NSMutableAttributedString + NSMutableData + NSMutableString + and 7 others + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `RetainSemantics` + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:15:31 + | +15 | let _: &Object = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `&objc2::runtime::Object`, found struct `Id` + | help: consider borrowing here: `&msg_send_id![obj, init].unwrap()` + | + = note: expected reference `&objc2::runtime::Object` + found struct `Id` + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:17:41 + | +17 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `objc2::runtime::Class`, found struct `objc2::runtime::Object` + | + = note: expected struct `Id` + found struct `Id` + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:19:44 + | +19 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `NSObject`, found struct `objc2::runtime::Object` + | + = note: expected struct `Id` + found struct `Id` + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:21:41 + | +21 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `objc2::rc::Owned`, found enum `Shared` + | + = note: expected struct `Id<_, objc2::rc::Owned>` + found struct `Id<_, Shared>` + +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_return.rs:24:31 + | +24 | let _: &Object = unsafe { msg_send_id![&obj, description].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `&objc2::runtime::Object`, found struct `Id` + | help: consider borrowing here: `&msg_send_id![&obj, description].unwrap()` + | + = note: expected reference `&objc2::runtime::Object` + found struct `Id<_, _>` diff --git a/tests/ui/msg_send_id_underspecified.rs b/tests/ui/msg_send_id_underspecified.rs new file mode 100644 index 000000000..e6b638894 --- /dev/null +++ b/tests/ui/msg_send_id_underspecified.rs @@ -0,0 +1,8 @@ +//! Test compiler output of msg_send_id when ownership is not specified. +use objc2::msg_send_id; +use objc2::runtime::Object; + +fn main() { + let obj: &Object; + let _: &Object = &*unsafe { msg_send_id![obj, description].unwrap() }; +} diff --git a/tests/ui/msg_send_id_underspecified.stderr b/tests/ui/msg_send_id_underspecified.stderr new file mode 100644 index 000000000..e7ff01354 --- /dev/null +++ b/tests/ui/msg_send_id_underspecified.stderr @@ -0,0 +1,7 @@ +error[E0282]: type annotations needed + --> ui/msg_send_id_underspecified.rs:7:33 + | +7 | let _: &Object = &*unsafe { msg_send_id![obj, description].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type + | + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info)