From 7c26f19c70ae08e7968aabc492c757c525df1adc Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 01:52:02 +0200 Subject: [PATCH 01/22] Add msg_send_id to help with following memory management rules --- objc2/src/__macro_helpers.rs | 129 +++++++++++++++++++++++++++++++++++ objc2/src/lib.rs | 3 + objc2/src/macros.rs | 35 ++++++++++ 3 files changed, 167 insertions(+) create mode 100644 objc2/src/__macro_helpers.rs diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs new file mode 100644 index 000000000..fcc9c8e33 --- /dev/null +++ b/objc2/src/__macro_helpers.rs @@ -0,0 +1,129 @@ +use core::mem::ManuallyDrop; + +use crate::rc::{Id, Ownership}; +use crate::runtime::{Class, Sel}; +use crate::{Message, MessageArguments, MessageError, MessageReceiver}; + +#[doc(hidden)] +pub struct Assert {} + +#[doc(hidden)] +pub trait MsgSendId { + unsafe fn send_message_id( + obj: T, + sel: Sel, + args: A, + ) -> Result; +} + +// `alloc`, should mark the return value as "allocated, not initialized" somehow +impl MsgSendId<&'_ Class, Id> + for Assert +{ + #[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).expect("Failed allocating")) + } + } +} + +// `init`, should mark the input value as "allocated, not initialized" somehow +impl MsgSendId, Option>> + for Assert +{ + #[inline(always)] + unsafe fn send_message_id( + obj: Id, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + let obj = ManuallyDrop::new(obj); + unsafe { MessageReceiver::send_message(obj, sel, args).map(|r| Id::new(r)) } + } +} + +// `copy`, `mutableCopy` and `new` +impl MsgSendId>> + for Assert +{ + #[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 Assert +{ + #[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)) } + } +} + +#[doc(hidden)] +pub const fn starts_with_str(haystack: &[u8], needle: &[u8]) -> bool { + if needle.len() > haystack.len() { + return false; + } + let mut i = 0; + while i < needle.len() { + if needle[i] != haystack[i] { + return false; + } + i += 1; + } + true +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rc::{Id, Owned, Shared}; + use crate::runtime::Object; + + #[test] + fn test_macro() { + let cls = class!(NSObject); + + let _obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; + + let obj = unsafe { msg_send_id![cls, alloc] }; + + let obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + + // TODO: + // let copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; + // let mutable_copy: Id = unsafe { msg_send_id![&obj, mutableCopy].unwrap() }; + + let _desc: Option> = unsafe { msg_send_id![&obj, description] }; + } + + #[test] + fn test_starts_with_str() { + assert!(starts_with_str(b"abcdef", b"abc")); + assert!(starts_with_str(b"a", b"")); + assert!(starts_with_str(b"", b"")); + + assert!(!starts_with_str(b"abcdef", b"def")); + assert!(!starts_with_str(b"abcdef", b"abb")); + assert!(!starts_with_str(b"", b"a")); + } +} diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs index d65f8c4b0..76bf0d976 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -167,6 +167,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..880c85199 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -146,6 +146,7 @@ macro_rules! sel { /// let obj: *mut Object; /// # let obj: *mut Object = 0 as *mut Object; /// let description: *const Object = unsafe { msg_send![obj, description] }; +/// // Usually you'd use msg_send_id here ^ /// 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] }; @@ -227,3 +228,37 @@ macro_rules! msg_send_bool { result.as_bool() }); } + +/// TODO +#[macro_export] +macro_rules! msg_send_id { + [$obj:expr, $selector:ident $(,)?] => ({ + let sel = $crate::sel!($selector); + const NAME: &[u8] = stringify!($selector).as_bytes(); + $crate::msg_send_id!(@__inner $obj, NAME, sel, ()) + }); + [$obj:expr, $($selector:ident : $argument:expr),+ $(,)?] => ({ + let sel = $crate::sel!($($selector:)+); + const NAME: &[u8] = concat!($(stringify!($selector), ':'),+).as_bytes(); + $crate::msg_send_id!(@__inner $obj, NAME, sel, ($($argument,)+)) + }); + (@__inner $obj:expr, $name:ident, $sel:ident, $args:expr) => {{ + const ALLOC: bool = $crate::__macro_helpers::starts_with_str($name, b"alloc"); + const INIT: bool = $crate::__macro_helpers::starts_with_str($name, b"init"); + const RETAINED: bool = { + $crate::__macro_helpers::starts_with_str($name, b"alloc") + || $crate::__macro_helpers::starts_with_str($name, b"new") + || $crate::__macro_helpers::starts_with_str($name, b"copy") + || $crate::__macro_helpers::starts_with_str($name, b"mutableCopy") + || $crate::__macro_helpers::starts_with_str($name, b"init") + }; + + use $crate::__macro_helpers::{MsgSendId, Assert}; + let result; + match >::send_message_id($obj, $sel, $args) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }}; +} From b4d4a77b99d38a4c57494f530eb2373eb5304899 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 01:52:56 +0200 Subject: [PATCH 02/22] Use msg_send_id everywhere --- .../examples/class_with_lifetime.rs | 7 ++--- objc2-foundation/examples/custom_class.rs | 4 +-- objc2-foundation/src/attributed_string.rs | 16 +++++----- objc2-foundation/src/copying.rs | 12 ++------ objc2-foundation/src/data.rs | 12 ++++---- objc2-foundation/src/dictionary.rs | 22 +++++--------- objc2-foundation/src/macros.rs | 2 +- .../src/mutable_attributed_string.rs | 12 ++++---- objc2-foundation/src/mutable_string.rs | 12 ++++---- objc2-foundation/src/object.rs | 9 ++---- objc2-foundation/src/process_info.rs | 8 ++--- objc2-foundation/src/thread.rs | 16 ++++------ objc2-foundation/src/uuid.rs | 13 +++----- objc2-foundation/src/value.rs | 10 +++---- objc2/README.md | 5 ++-- objc2/examples/introspection.rs | 7 ++--- objc2/examples/talk_to_me.rs | 22 +++++++------- objc2/src/bool.rs | 9 ++++-- objc2/src/exception.rs | 2 +- objc2/src/lib.rs | 5 ++-- objc2/src/message/mod.rs | 12 +++++--- objc2/src/rc/autorelease.rs | 30 +++++++++++-------- objc2/src/rc/id.rs | 19 +++++++----- objc2/src/rc/mod.rs | 4 +-- objc2/src/rc/test_object.rs | 4 +-- tests/src/test_object.rs | 10 +++++-- 26 files changed, 133 insertions(+), 151 deletions(-) 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/README.md b/objc2/README.md index 8b215a7d0..700b7d132 100644 --- a/objc2/README.md +++ b/objc2/README.md @@ -13,13 +13,12 @@ written in Objective-C; this crate enables you to interract with those. ## Example ```rust -use objc2::{class, msg_send}; +use objc2::{class, msg_send_id}; use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, 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") }; 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/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 76bf0d976..965c4c491 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -29,16 +29,15 @@ //! #![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::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") +//! msg_send_id![cls, new].expect("Failed allocating object") //! }; //! //! // Usage diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index 68b5edbb2..cf01baed0 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,9 @@ 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()); } #[test] diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index 9a75f783b..fdff5bbf7 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -223,15 +223,21 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// 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 +252,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 +273,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..0a60e0437 100644 --- a/objc2/src/rc/id.rs +++ b/objc2/src/rc/id.rs @@ -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 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..f062688b3 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -5,7 +5,7 @@ use std::sync::Once; use super::{Id, Owned}; use crate::declare::ClassBuilder; use crate::runtime::{Bool, Class, Object, Sel}; -use crate::{msg_send, msg_send_bool}; +use crate::{msg_send, msg_send_bool, msg_send_id}; use crate::{Encoding, Message, RefEncode}; #[derive(Debug, Clone, Default, PartialEq)] @@ -152,6 +152,6 @@ impl RcTestObject { } pub(crate) fn new() -> Id { - unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap() + unsafe { msg_send_id![Self::class(), new] }.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()); From c93b5494633256045a5fedd8ae9fcb108e1f6960 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Apr 2022 01:54:57 +0200 Subject: [PATCH 03/22] Disambiguate trait in msg_send_id --- objc2/src/__macro_helpers.rs | 16 ++++++++++++++++ objc2/src/macros.rs | 30 ++++++++++++++++++------------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index fcc9c8e33..f343b9d5a 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -126,4 +126,20 @@ mod tests { assert!(!starts_with_str(b"abcdef", b"abb")); assert!(!starts_with_str(b"", b"a")); } + + 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/macros.rs b/objc2/src/macros.rs index 880c85199..1a67816b0 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -235,14 +235,28 @@ macro_rules! msg_send_id { [$obj:expr, $selector:ident $(,)?] => ({ let sel = $crate::sel!($selector); const NAME: &[u8] = stringify!($selector).as_bytes(); - $crate::msg_send_id!(@__inner $obj, NAME, sel, ()) + $crate::msg_send_id!(@__get_assert_consts NAME); + use $crate::__macro_helpers::{MsgSendId, Assert}; + let result; + match as MsgSendId<_, _>>::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!(@__inner $obj, NAME, sel, ($($argument,)+)) + $crate::msg_send_id!(@__get_assert_consts NAME); + use $crate::__macro_helpers::{MsgSendId, Assert}; + let result; + match as MsgSendId<_, _>>::send_message_id($obj, sel, ($($argument,)+)) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result }); - (@__inner $obj:expr, $name:ident, $sel:ident, $args:expr) => {{ + (@__get_assert_consts $name:ident) => { const ALLOC: bool = $crate::__macro_helpers::starts_with_str($name, b"alloc"); const INIT: bool = $crate::__macro_helpers::starts_with_str($name, b"init"); const RETAINED: bool = { @@ -252,13 +266,5 @@ macro_rules! msg_send_id { || $crate::__macro_helpers::starts_with_str($name, b"mutableCopy") || $crate::__macro_helpers::starts_with_str($name, b"init") }; - - use $crate::__macro_helpers::{MsgSendId, Assert}; - let result; - match >::send_message_id($obj, $sel, $args) { - Err(s) => panic!("{}", s), - Ok(r) => result = r, - } - result - }}; + }; } From c035c1d97474b93e7a9b4982392ee8e1078a9206 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 00:16:44 +0200 Subject: [PATCH 04/22] Fix method family comparison in msg_send_id --- objc2/src/__macro_helpers.rs | 127 ++++++++++++++++++++++++++++++----- objc2/src/macros.rs | 16 +++-- 2 files changed, 119 insertions(+), 24 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index f343b9d5a..093c7ed30 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -78,19 +78,45 @@ impl MsgSendId> } } +// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families #[doc(hidden)] -pub const fn starts_with_str(haystack: &[u8], needle: &[u8]) -> bool { - if needle.len() > haystack.len() { - return false; +pub const fn in_method_family(mut selector: &[u8], mut family: &[u8]) -> bool { + // Skip leading underscores from selector + loop { + selector = match selector { + [b'_', selector @ ..] => (selector), + _ => break, + } } - let mut i = 0; - while i < needle.len() { - if needle[i] != haystack[i] { - return false; + + // 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(); + } } - i += 1; } - true } #[cfg(test)] @@ -117,14 +143,81 @@ mod tests { } #[test] - fn test_starts_with_str() { - assert!(starts_with_str(b"abcdef", b"abc")); - assert!(starts_with_str(b"a", b"")); - assert!(starts_with_str(b"", b"")); - - assert!(!starts_with_str(b"abcdef", b"def")); - assert!(!starts_with_str(b"abcdef", b"abb")); - assert!(!starts_with_str(b"", b"a")); + fn test_in_method_family() { + // Common cases + + assert!(in_method_family(b"alloc", b"alloc")); + assert!(in_method_family(b"allocWithZone:", b"alloc")); + assert!(!in_method_family(b"dealloc", b"alloc")); + assert!(!in_method_family(b"initialize", b"init")); + assert!(!in_method_family(b"decimalNumberWithDecimal:", b"init")); + assert!(in_method_family(b"initWithCapacity:", b"init")); + assert!(in_method_family(b"_initButPrivate:withParam:", b"init")); + assert!(!in_method_family(b"description", b"init")); + assert!(!in_method_family(b"inIT", b"init")); + + assert!(!in_method_family(b"init", b"copy")); + assert!(!in_method_family(b"copyingStuff:", b"copy")); + assert!(in_method_family(b"copyWithZone:", b"copy")); + assert!(!in_method_family(b"initWithArray:copyItems:", b"copy")); + assert!(in_method_family(b"copyItemAtURL:toURL:error:", b"copy")); + + assert!(!in_method_family(b"mutableCopying", b"mutableCopy")); + assert!(in_method_family(b"mutableCopyWithZone:", b"mutableCopy")); + assert!(in_method_family(b"mutableCopyWithZone:", b"mutableCopy")); + + assert!(in_method_family( + b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + b"new" + )); + assert!(in_method_family( + b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", + b"new" + )); + assert!(!in_method_family(b"newsstandAssetDownload", b"new")); + + // Trying to weed out edge-cases: + + assert!(in_method_family(b"__abcDef", b"abc")); + assert!(in_method_family(b"_abcDef", b"abc")); + assert!(in_method_family(b"abcDef", b"abc")); + assert!(in_method_family(b"___a", b"a")); + assert!(in_method_family(b"__a", b"a")); + assert!(in_method_family(b"_a", b"a")); + assert!(in_method_family(b"a", b"a")); + + assert!(!in_method_family(b"_abcdef", b"abc")); + assert!(!in_method_family(b"_abcdef", b"def")); + assert!(!in_method_family(b"_bcdef", b"abc")); + assert!(!in_method_family(b"a_bc", b"abc")); + assert!(!in_method_family(b"abcdef", b"abc")); + assert!(!in_method_family(b"abcdef", b"def")); + assert!(!in_method_family(b"abcdef", b"abb")); + assert!(!in_method_family(b"___", b"a")); + assert!(!in_method_family(b"_", b"a")); + assert!(!in_method_family(b"", b"a")); + + assert!(in_method_family(b"copy", b"copy")); + assert!(in_method_family(b"copy:", b"copy")); + assert!(in_method_family(b"copyMe", b"copy")); + assert!(in_method_family(b"_copy", b"copy")); + assert!(in_method_family(b"_copy:", b"copy")); + assert!(in_method_family(b"_copyMe", b"copy")); + assert!(!in_method_family(b"copying", b"copy")); + assert!(!in_method_family(b"copying:", b"copy")); + assert!(!in_method_family(b"_copying", b"copy")); + assert!(!in_method_family(b"Copy", b"copy")); + assert!(!in_method_family(b"COPY", b"copy")); + + // Empty family (not supported) + assert!(in_method_family(b"___", b"")); + assert!(in_method_family(b"__", b"")); + assert!(in_method_family(b"_", b"")); + assert!(in_method_family(b"", b"")); + assert!(!in_method_family(b"_a", b"")); + assert!(!in_method_family(b"a", b"")); + assert!(in_method_family(b"_A", b"")); + assert!(in_method_family(b"A", b"")); } mod test_trait_disambugated { diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 1a67816b0..683621858 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -257,14 +257,16 @@ macro_rules! msg_send_id { result }); (@__get_assert_consts $name:ident) => { - const ALLOC: bool = $crate::__macro_helpers::starts_with_str($name, b"alloc"); - const INIT: bool = $crate::__macro_helpers::starts_with_str($name, b"init"); + const ALLOC: bool = $crate::__macro_helpers::in_method_family($name, b"alloc"); + // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#consumed-parameters + const INIT: bool = $crate::__macro_helpers::in_method_family($name, b"init"); + // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retained-return-values const RETAINED: bool = { - $crate::__macro_helpers::starts_with_str($name, b"alloc") - || $crate::__macro_helpers::starts_with_str($name, b"new") - || $crate::__macro_helpers::starts_with_str($name, b"copy") - || $crate::__macro_helpers::starts_with_str($name, b"mutableCopy") - || $crate::__macro_helpers::starts_with_str($name, b"init") + $crate::__macro_helpers::in_method_family($name, b"alloc") + || $crate::__macro_helpers::in_method_family($name, b"new") + || $crate::__macro_helpers::in_method_family($name, b"copy") + || $crate::__macro_helpers::in_method_family($name, b"mutableCopy") + || $crate::__macro_helpers::in_method_family($name, b"init") }; }; } From c9198d62ca49f47dd8b5ba9ef9364f4b4585e924 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 01:48:09 +0200 Subject: [PATCH 05/22] Add assembly tests for msg_send_id --- tests/assembly/test_msg_send_id/Cargo.toml | 21 ++ .../test_msg_send_id/expected/apple-aarch64.s | 138 +++++++++++++ .../test_msg_send_id/expected/apple-armv7.s | 140 +++++++++++++ .../test_msg_send_id/expected/apple-armv7s.s | 146 ++++++++++++++ .../test_msg_send_id/expected/apple-x86.s | 188 ++++++++++++++++++ .../test_msg_send_id/expected/apple-x86_64.s | 129 ++++++++++++ tests/assembly/test_msg_send_id/lib.rs | 44 ++++ 7 files changed, 806 insertions(+) create mode 100644 tests/assembly/test_msg_send_id/Cargo.toml create mode 100644 tests/assembly/test_msg_send_id/expected/apple-aarch64.s create mode 100644 tests/assembly/test_msg_send_id/expected/apple-armv7.s create mode 100644 tests/assembly/test_msg_send_id/expected/apple-armv7s.s create mode 100644 tests/assembly/test_msg_send_id/expected/apple-x86.s create mode 100644 tests/assembly/test_msg_send_id/expected/apple-x86_64.s create mode 100644 tests/assembly/test_msg_send_id/lib.rs 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..9e888fadd --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-aarch64.s @@ -0,0 +1,138 @@ + .section __TEXT,__text,regular,pure_instructions + .globl _handle_alloc + .p2align 2 +_handle_alloc: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _objc_msgSend + cbz x0, LBB0_2 + ldp x29, x30, [sp], #16 + ret +LBB0_2: +Lloh0: + adrp x0, l___unnamed_1@PAGE +Lloh1: + add x0, x0, l___unnamed_1@PAGEOFF +Lloh2: + adrp x2, l___unnamed_2@PAGE +Lloh3: + add x2, x2, l___unnamed_2@PAGEOFF + mov w1, #17 + bl __ZN4core6option13expect_failed17h16e38b99483f925bE + .loh AdrpAdd Lloh2, Lloh3 + .loh AdrpAdd Lloh0, Lloh1 + + .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 + cbz x0, LBB2_2 + mov x1, x19 + ldp x29, x30, [sp, #16] + ldp x20, x19, [sp], #32 + b _objc_msgSend +LBB2_2: +Lloh4: + adrp x0, l___unnamed_1@PAGE +Lloh5: + add x0, x0, l___unnamed_1@PAGEOFF +Lloh6: + adrp x2, l___unnamed_2@PAGE +Lloh7: + add x2, x2, l___unnamed_2@PAGEOFF + mov w1, #17 + bl __ZN4core6option13expect_failed17h16e38b99483f925bE + .loh AdrpAdd Lloh6, Lloh7 + .loh AdrpAdd Lloh4, Lloh5 + + .globl _handle_alloc_release + .p2align 2 +_handle_alloc_release: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _objc_msgSend + cbz x0, LBB3_2 + ldp x29, x30, [sp], #16 + b _objc_release +LBB3_2: +Lloh8: + adrp x0, l___unnamed_1@PAGE +Lloh9: + add x0, x0, l___unnamed_1@PAGEOFF +Lloh10: + adrp x2, l___unnamed_2@PAGE +Lloh11: + add x2, x2, l___unnamed_2@PAGEOFF + mov w1, #17 + bl __ZN4core6option13expect_failed17h16e38b99483f925bE + .loh AdrpAdd Lloh10, Lloh11 + .loh AdrpAdd Lloh8, Lloh9 + + .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 + cbz x0, LBB4_2 + mov x1, x19 + bl _objc_msgSend + ldp x29, x30, [sp, #16] + ldp x20, x19, [sp], #32 + b _objc_release +LBB4_2: +Lloh12: + adrp x0, l___unnamed_1@PAGE +Lloh13: + add x0, x0, l___unnamed_1@PAGEOFF +Lloh14: + adrp x2, l___unnamed_2@PAGE +Lloh15: + add x2, x2, l___unnamed_2@PAGEOFF + mov w1, #17 + bl __ZN4core6option13expect_failed17h16e38b99483f925bE + .loh AdrpAdd Lloh14, Lloh15 + .loh AdrpAdd Lloh12, Lloh13 + + .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 + + .section __TEXT,__const +l___unnamed_1: + .ascii "Failed allocating" + +l___unnamed_3: + .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" + + .section __DATA,__const + .p2align 3 +l___unnamed_2: + .quad l___unnamed_3 + .asciz "B\000\000\000\000\000\000\000\037\000\000\000%\000\000" + +.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..2fd182281 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7.s @@ -0,0 +1,140 @@ + .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 + cmp r0, #0 + popne {r7, pc} +LBB0_1: + movw r0, :lower16:(l___unnamed_1-(LPC0_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC0_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC0_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC0_1+8)) +LPC0_0: + add r0, pc, r0 +LPC0_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E + + .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 + cmp r0, #0 + beq LBB2_2 + mov r1, r4 + pop {r4, r7, lr} + b _objc_msgSend +LBB2_2: + movw r0, :lower16:(l___unnamed_1-(LPC2_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC2_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC2_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC2_1+8)) +LPC2_0: + add r0, pc, r0 +LPC2_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E + + .globl _handle_alloc_release + .p2align 2 + .code 32 +_handle_alloc_release: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + cmp r0, #0 + beq LBB3_2 + pop {r7, lr} + b _objc_release +LBB3_2: + movw r0, :lower16:(l___unnamed_1-(LPC3_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC3_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC3_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC3_1+8)) +LPC3_0: + add r0, pc, r0 +LPC3_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E + + .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 + cmp r0, #0 + beq LBB4_2 + mov r1, r4 + bl _objc_msgSend + pop {r4, r7, lr} + b _objc_release +LBB4_2: + movw r0, :lower16:(l___unnamed_1-(LPC4_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC4_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC4_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC4_1+8)) +LPC4_0: + add r0, pc, r0 +LPC4_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E + + .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 + + .section __TEXT,__const +l___unnamed_1: + .ascii "Failed allocating" + +l___unnamed_3: + .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" + + .section __DATA,__const + .p2align 2 +l___unnamed_2: + .long l___unnamed_3 + .asciz "B\000\000\000\037\000\000\000%\000\000" + +.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..f695810f6 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7s.s @@ -0,0 +1,146 @@ + .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 + cmp r0, #0 + popne {r7, pc} +LBB0_1: + movw r0, :lower16:(l___unnamed_1-(LPC0_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC0_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC0_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC0_1+8)) +LPC0_0: + add r0, pc, r0 +LPC0_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9461c0e6a2661999E + + .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 + cmp r0, #0 + beq LBB2_2 + mov r1, r4 + bl _objc_msgSend + pop {r4, r7, pc} +LBB2_2: + movw r0, :lower16:(l___unnamed_1-(LPC2_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC2_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC2_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC2_1+8)) +LPC2_0: + add r0, pc, r0 +LPC2_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9461c0e6a2661999E + + .globl _handle_alloc_release + .p2align 2 + .code 32 +_handle_alloc_release: + push {r7, lr} + mov r7, sp + bl _objc_msgSend + cmp r0, #0 + beq LBB3_2 + bl _objc_release + pop {r7, pc} +LBB3_2: + movw r0, :lower16:(l___unnamed_1-(LPC3_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC3_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC3_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC3_1+8)) +LPC3_0: + add r0, pc, r0 +LPC3_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9461c0e6a2661999E + + .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 + cmp r0, #0 + beq LBB4_2 + mov r1, r4 + bl _objc_msgSend + bl _objc_release + pop {r4, r7, pc} +LBB4_2: + movw r0, :lower16:(l___unnamed_1-(LPC4_0+8)) + mov r1, #17 + movt r0, :upper16:(l___unnamed_1-(LPC4_0+8)) + movw r2, :lower16:(l___unnamed_2-(LPC4_1+8)) + movt r2, :upper16:(l___unnamed_2-(LPC4_1+8)) +LPC4_0: + add r0, pc, r0 +LPC4_1: + add r2, pc, r2 + mov lr, pc + b __ZN4core6option13expect_failed17h9461c0e6a2661999E + + .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} + + .section __TEXT,__const +l___unnamed_1: + .ascii "Failed allocating" + +l___unnamed_3: + .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" + + .section __DATA,__const + .p2align 2 +l___unnamed_2: + .long l___unnamed_3 + .asciz "B\000\000\000\037\000\000\000%\000\000" + +.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..df56e3d30 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-x86.s @@ -0,0 +1,188 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .globl _handle_alloc + .p2align 4, 0x90 +_handle_alloc: + push ebp + mov ebp, esp + push esi + push eax + call L0$pb +L0$pb: + pop esi + sub esp, 8 + push dword ptr [ebp + 12] + push dword ptr [ebp + 8] + call _objc_msgSend + add esp, 16 + test eax, eax + je LBB0_2 + add esp, 4 + pop esi + pop ebp + ret +LBB0_2: + sub esp, 4 + lea eax, [esi + l___unnamed_1-L0$pb] + lea ecx, [esi + l___unnamed_2-L0$pb] + push eax + push 17 + push ecx + call __ZN4core6option13expect_failed17hc70f05bfe703751eE + + .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 + call L2$pb +L2$pb: + pop esi + sub esp, 8 + push dword ptr [ebp + 12] + push dword ptr [ebp + 8] + call _objc_msgSend + add esp, 16 + test eax, eax + je LBB2_2 + sub esp, 8 + push dword ptr [ebp + 16] + push eax + call _objc_msgSend + add esp, 20 + pop esi + pop ebp + ret +LBB2_2: + sub esp, 4 + lea eax, [esi + l___unnamed_1-L2$pb] + lea ecx, [esi + l___unnamed_2-L2$pb] + push eax + push 17 + push ecx + call __ZN4core6option13expect_failed17hc70f05bfe703751eE + + .globl _handle_alloc_release + .p2align 4, 0x90 +_handle_alloc_release: + push ebp + mov ebp, esp + push esi + push eax + call L3$pb +L3$pb: + pop esi + sub esp, 8 + push dword ptr [ebp + 12] + push dword ptr [ebp + 8] + call _objc_msgSend + add esp, 16 + test eax, eax + je LBB3_2 + sub esp, 12 + push eax + call _objc_release + add esp, 20 + pop esi + pop ebp + ret +LBB3_2: + sub esp, 4 + lea eax, [esi + l___unnamed_1-L3$pb] + lea ecx, [esi + l___unnamed_2-L3$pb] + push eax + push 17 + push ecx + call __ZN4core6option13expect_failed17hc70f05bfe703751eE + + .globl _handle_alloc_init_release + .p2align 4, 0x90 +_handle_alloc_init_release: + push ebp + mov ebp, esp + push esi + push eax + call L4$pb +L4$pb: + pop esi + sub esp, 8 + push dword ptr [ebp + 12] + push dword ptr [ebp + 8] + call _objc_msgSend + add esp, 16 + test eax, eax + je LBB4_2 + sub esp, 8 + push dword ptr [ebp + 16] + push eax + call _objc_msgSend + add esp, 4 + push eax + call _objc_release + add esp, 20 + pop esi + pop ebp + ret +LBB4_2: + sub esp, 4 + lea eax, [esi + l___unnamed_1-L4$pb] + lea ecx, [esi + l___unnamed_2-L4$pb] + push eax + push 17 + push ecx + call __ZN4core6option13expect_failed17hc70f05bfe703751eE + + .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 + + .section __TEXT,__const +l___unnamed_2: + .ascii "Failed allocating" + +l___unnamed_3: + .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" + + .section __DATA,__const + .p2align 2 +l___unnamed_1: + .long l___unnamed_3 + .asciz "B\000\000\000\037\000\000\000%\000\000" + +.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..656a56fe5 --- /dev/null +++ b/tests/assembly/test_msg_send_id/expected/apple-x86_64.s @@ -0,0 +1,129 @@ + .section __TEXT,__text,regular,pure_instructions + .intel_syntax noprefix + .globl _handle_alloc + .p2align 4, 0x90 +_handle_alloc: + push rbp + mov rbp, rsp + call _objc_msgSend + test rax, rax + je LBB0_2 + pop rbp + ret +LBB0_2: + lea rdi, [rip + l___unnamed_1] + lea rdx, [rip + l___unnamed_2] + mov esi, 17 + call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE + + .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 + test rax, rax + je LBB2_1 + mov rdi, rax + mov rsi, rbx + add rsp, 8 + pop rbx + pop rbp + jmp _objc_msgSend +LBB2_1: + lea rdi, [rip + l___unnamed_1] + lea rdx, [rip + l___unnamed_2] + mov esi, 17 + call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE + + .globl _handle_alloc_release + .p2align 4, 0x90 +_handle_alloc_release: + push rbp + mov rbp, rsp + call _objc_msgSend + test rax, rax + je LBB3_1 + mov rdi, rax + pop rbp + jmp _objc_release +LBB3_1: + lea rdi, [rip + l___unnamed_1] + lea rdx, [rip + l___unnamed_2] + mov esi, 17 + call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE + + .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 + test rax, rax + je LBB4_1 + mov rdi, rax + mov rsi, rbx + call _objc_msgSend + mov rdi, rax + add rsp, 8 + pop rbx + pop rbp + jmp _objc_release +LBB4_1: + lea rdi, [rip + l___unnamed_1] + lea rdx, [rip + l___unnamed_2] + mov esi, 17 + call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE + + .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 + + .section __TEXT,__const +l___unnamed_1: + .ascii "Failed allocating" + +l___unnamed_3: + .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" + + .section __DATA,__const + .p2align 3 +l___unnamed_2: + .quad l___unnamed_3 + .asciz "B\000\000\000\000\000\000\000\037\000\000\000%\000\000" + +.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..33c73c242 --- /dev/null +++ b/tests/assembly/test_msg_send_id/lib.rs @@ -0,0 +1,44 @@ +//! Test assembly output of `msg_send_id!` internals. +use objc2::__macro_helpers::{Assert, MsgSendId}; +use objc2::rc::{Id, Shared}; +use objc2::runtime::{Class, Object, Sel}; + +#[no_mangle] +unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Id { + >::send_message_id(obj, sel, ()).unwrap() +} + +#[no_mangle] +unsafe fn handle_init(obj: Id, 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(); +} + +#[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() +} From c3b047c3f5ebf0af26cf4088c552e8e247535f91 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 01:49:29 +0200 Subject: [PATCH 06/22] Remove extra branch between `msg_send_id!` alloc + init calls --- objc2/src/__macro_helpers.rs | 25 ++-- objc2/src/message/mod.rs | 4 + objc2/src/rc/id.rs | 11 +- .../test_msg_send_id/expected/apple-aarch64.s | 75 +---------- .../test_msg_send_id/expected/apple-armv7.s | 73 +---------- .../test_msg_send_id/expected/apple-armv7s.s | 70 +---------- .../test_msg_send_id/expected/apple-x86.s | 116 +++--------------- .../test_msg_send_id/expected/apple-x86_64.s | 44 +------ tests/assembly/test_msg_send_id/lib.rs | 9 +- 9 files changed, 55 insertions(+), 372 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 093c7ed30..5fd74a14b 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -1,5 +1,3 @@ -use core::mem::ManuallyDrop; - use crate::rc::{Id, Ownership}; use crate::runtime::{Class, Sel}; use crate::{Message, MessageArguments, MessageError, MessageReceiver}; @@ -17,7 +15,7 @@ pub trait MsgSendId { } // `alloc`, should mark the return value as "allocated, not initialized" somehow -impl MsgSendId<&'_ Class, Id> +impl MsgSendId<&'_ Class, Option>> for Assert { #[inline(always)] @@ -25,26 +23,29 @@ impl MsgSendId<&'_ Class, Id> cls: &Class, sel: Sel, args: A, - ) -> Result, MessageError> { - unsafe { - MessageReceiver::send_message(cls, sel, args) - .map(|r| Id::new(r).expect("Failed allocating")) - } + ) -> 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, Option>> +impl MsgSendId>, Option>> for Assert { #[inline(always)] unsafe fn send_message_id( - obj: Id, + obj: Option>, sel: Sel, args: A, ) -> Result>, MessageError> { - let obj = ManuallyDrop::new(obj); - unsafe { MessageReceiver::send_message(obj, sel, args).map(|r| Id::new(r)) } + 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)) } } } diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index cf01baed0..d9d999eb4 100644 --- a/objc2/src/message/mod.rs +++ b/objc2/src/message/mod.rs @@ -477,6 +477,10 @@ mod tests { 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/id.rs b/objc2/src/rc/id.rs index 0a60e0437..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; @@ -207,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/tests/assembly/test_msg_send_id/expected/apple-aarch64.s b/tests/assembly/test_msg_send_id/expected/apple-aarch64.s index 9e888fadd..de2bce3a9 100644 --- a/tests/assembly/test_msg_send_id/expected/apple-aarch64.s +++ b/tests/assembly/test_msg_send_id/expected/apple-aarch64.s @@ -2,25 +2,7 @@ .globl _handle_alloc .p2align 2 _handle_alloc: - stp x29, x30, [sp, #-16]! - mov x29, sp - bl _objc_msgSend - cbz x0, LBB0_2 - ldp x29, x30, [sp], #16 - ret -LBB0_2: -Lloh0: - adrp x0, l___unnamed_1@PAGE -Lloh1: - add x0, x0, l___unnamed_1@PAGEOFF -Lloh2: - adrp x2, l___unnamed_2@PAGE -Lloh3: - add x2, x2, l___unnamed_2@PAGEOFF - mov w1, #17 - bl __ZN4core6option13expect_failed17h16e38b99483f925bE - .loh AdrpAdd Lloh2, Lloh3 - .loh AdrpAdd Lloh0, Lloh1 + b _objc_msgSend .globl _handle_init .p2align 2 @@ -35,24 +17,10 @@ _handle_alloc_init: add x29, sp, #16 mov x19, x2 bl _objc_msgSend - cbz x0, LBB2_2 mov x1, x19 ldp x29, x30, [sp, #16] ldp x20, x19, [sp], #32 b _objc_msgSend -LBB2_2: -Lloh4: - adrp x0, l___unnamed_1@PAGE -Lloh5: - add x0, x0, l___unnamed_1@PAGEOFF -Lloh6: - adrp x2, l___unnamed_2@PAGE -Lloh7: - add x2, x2, l___unnamed_2@PAGEOFF - mov w1, #17 - bl __ZN4core6option13expect_failed17h16e38b99483f925bE - .loh AdrpAdd Lloh6, Lloh7 - .loh AdrpAdd Lloh4, Lloh5 .globl _handle_alloc_release .p2align 2 @@ -60,22 +28,8 @@ _handle_alloc_release: stp x29, x30, [sp, #-16]! mov x29, sp bl _objc_msgSend - cbz x0, LBB3_2 ldp x29, x30, [sp], #16 b _objc_release -LBB3_2: -Lloh8: - adrp x0, l___unnamed_1@PAGE -Lloh9: - add x0, x0, l___unnamed_1@PAGEOFF -Lloh10: - adrp x2, l___unnamed_2@PAGE -Lloh11: - add x2, x2, l___unnamed_2@PAGEOFF - mov w1, #17 - bl __ZN4core6option13expect_failed17h16e38b99483f925bE - .loh AdrpAdd Lloh10, Lloh11 - .loh AdrpAdd Lloh8, Lloh9 .globl _handle_alloc_init_release .p2align 2 @@ -85,25 +39,11 @@ _handle_alloc_init_release: add x29, sp, #16 mov x19, x2 bl _objc_msgSend - cbz x0, LBB4_2 mov x1, x19 bl _objc_msgSend ldp x29, x30, [sp, #16] ldp x20, x19, [sp], #32 b _objc_release -LBB4_2: -Lloh12: - adrp x0, l___unnamed_1@PAGE -Lloh13: - add x0, x0, l___unnamed_1@PAGEOFF -Lloh14: - adrp x2, l___unnamed_2@PAGE -Lloh15: - add x2, x2, l___unnamed_2@PAGEOFF - mov w1, #17 - bl __ZN4core6option13expect_failed17h16e38b99483f925bE - .loh AdrpAdd Lloh14, Lloh15 - .loh AdrpAdd Lloh12, Lloh13 .globl _handle_copy .p2align 2 @@ -122,17 +62,4 @@ _handle_autoreleased: ldp x29, x30, [sp], #16 b _objc_retainAutoreleasedReturnValue - .section __TEXT,__const -l___unnamed_1: - .ascii "Failed allocating" - -l___unnamed_3: - .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" - - .section __DATA,__const - .p2align 3 -l___unnamed_2: - .quad l___unnamed_3 - .asciz "B\000\000\000\000\000\000\000\037\000\000\000%\000\000" - .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 index 2fd182281..561d660d3 100644 --- a/tests/assembly/test_msg_send_id/expected/apple-armv7.s +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7.s @@ -4,23 +4,7 @@ .p2align 2 .code 32 _handle_alloc: - push {r7, lr} - mov r7, sp - bl _objc_msgSend - cmp r0, #0 - popne {r7, pc} -LBB0_1: - movw r0, :lower16:(l___unnamed_1-(LPC0_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC0_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC0_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC0_1+8)) -LPC0_0: - add r0, pc, r0 -LPC0_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E + b _objc_msgSend .globl _handle_init .p2align 2 @@ -36,23 +20,9 @@ _handle_alloc_init: add r7, sp, #4 mov r4, r2 bl _objc_msgSend - cmp r0, #0 - beq LBB2_2 mov r1, r4 pop {r4, r7, lr} b _objc_msgSend -LBB2_2: - movw r0, :lower16:(l___unnamed_1-(LPC2_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC2_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC2_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC2_1+8)) -LPC2_0: - add r0, pc, r0 -LPC2_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E .globl _handle_alloc_release .p2align 2 @@ -61,22 +31,8 @@ _handle_alloc_release: push {r7, lr} mov r7, sp bl _objc_msgSend - cmp r0, #0 - beq LBB3_2 pop {r7, lr} b _objc_release -LBB3_2: - movw r0, :lower16:(l___unnamed_1-(LPC3_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC3_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC3_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC3_1+8)) -LPC3_0: - add r0, pc, r0 -LPC3_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E .globl _handle_alloc_init_release .p2align 2 @@ -86,24 +42,10 @@ _handle_alloc_init_release: add r7, sp, #4 mov r4, r2 bl _objc_msgSend - cmp r0, #0 - beq LBB4_2 mov r1, r4 bl _objc_msgSend pop {r4, r7, lr} b _objc_release -LBB4_2: - movw r0, :lower16:(l___unnamed_1-(LPC4_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC4_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC4_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC4_1+8)) -LPC4_0: - add r0, pc, r0 -LPC4_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9fa2fcc9b79e39c1E .globl _handle_copy .p2align 2 @@ -124,17 +66,4 @@ _handle_autoreleased: pop {r7, lr} b _objc_retainAutoreleasedReturnValue - .section __TEXT,__const -l___unnamed_1: - .ascii "Failed allocating" - -l___unnamed_3: - .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" - - .section __DATA,__const - .p2align 2 -l___unnamed_2: - .long l___unnamed_3 - .asciz "B\000\000\000\037\000\000\000%\000\000" - .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 index f695810f6..046611c4c 100644 --- a/tests/assembly/test_msg_send_id/expected/apple-armv7s.s +++ b/tests/assembly/test_msg_send_id/expected/apple-armv7s.s @@ -7,20 +7,7 @@ _handle_alloc: push {r7, lr} mov r7, sp bl _objc_msgSend - cmp r0, #0 - popne {r7, pc} -LBB0_1: - movw r0, :lower16:(l___unnamed_1-(LPC0_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC0_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC0_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC0_1+8)) -LPC0_0: - add r0, pc, r0 -LPC0_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9461c0e6a2661999E + pop {r7, pc} .globl _handle_init .p2align 2 @@ -39,23 +26,9 @@ _handle_alloc_init: add r7, sp, #4 mov r4, r2 bl _objc_msgSend - cmp r0, #0 - beq LBB2_2 mov r1, r4 bl _objc_msgSend pop {r4, r7, pc} -LBB2_2: - movw r0, :lower16:(l___unnamed_1-(LPC2_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC2_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC2_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC2_1+8)) -LPC2_0: - add r0, pc, r0 -LPC2_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9461c0e6a2661999E .globl _handle_alloc_release .p2align 2 @@ -64,22 +37,8 @@ _handle_alloc_release: push {r7, lr} mov r7, sp bl _objc_msgSend - cmp r0, #0 - beq LBB3_2 bl _objc_release pop {r7, pc} -LBB3_2: - movw r0, :lower16:(l___unnamed_1-(LPC3_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC3_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC3_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC3_1+8)) -LPC3_0: - add r0, pc, r0 -LPC3_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9461c0e6a2661999E .globl _handle_alloc_init_release .p2align 2 @@ -89,24 +48,10 @@ _handle_alloc_init_release: add r7, sp, #4 mov r4, r2 bl _objc_msgSend - cmp r0, #0 - beq LBB4_2 mov r1, r4 bl _objc_msgSend bl _objc_release pop {r4, r7, pc} -LBB4_2: - movw r0, :lower16:(l___unnamed_1-(LPC4_0+8)) - mov r1, #17 - movt r0, :upper16:(l___unnamed_1-(LPC4_0+8)) - movw r2, :lower16:(l___unnamed_2-(LPC4_1+8)) - movt r2, :upper16:(l___unnamed_2-(LPC4_1+8)) -LPC4_0: - add r0, pc, r0 -LPC4_1: - add r2, pc, r2 - mov lr, pc - b __ZN4core6option13expect_failed17h9461c0e6a2661999E .globl _handle_copy .p2align 2 @@ -130,17 +75,4 @@ _handle_autoreleased: bl _objc_retainAutoreleasedReturnValue pop {r7, pc} - .section __TEXT,__const -l___unnamed_1: - .ascii "Failed allocating" - -l___unnamed_3: - .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" - - .section __DATA,__const - .p2align 2 -l___unnamed_2: - .long l___unnamed_3 - .asciz "B\000\000\000\037\000\000\000%\000\000" - .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 index df56e3d30..b42d5f569 100644 --- a/tests/assembly/test_msg_send_id/expected/apple-x86.s +++ b/tests/assembly/test_msg_send_id/expected/apple-x86.s @@ -5,30 +5,8 @@ _handle_alloc: push ebp mov ebp, esp - push esi - push eax - call L0$pb -L0$pb: - pop esi - sub esp, 8 - push dword ptr [ebp + 12] - push dword ptr [ebp + 8] - call _objc_msgSend - add esp, 16 - test eax, eax - je LBB0_2 - add esp, 4 - pop esi pop ebp - ret -LBB0_2: - sub esp, 4 - lea eax, [esi + l___unnamed_1-L0$pb] - lea ecx, [esi + l___unnamed_2-L0$pb] - push eax - push 17 - push ecx - call __ZN4core6option13expect_failed17hc70f05bfe703751eE + jmp _objc_msgSend .globl _handle_init .p2align 4, 0x90 @@ -45,65 +23,36 @@ _handle_alloc_init: mov ebp, esp push esi push eax - call L2$pb -L2$pb: - pop esi + mov esi, dword ptr [ebp + 16] sub esp, 8 push dword ptr [ebp + 12] push dword ptr [ebp + 8] call _objc_msgSend - add esp, 16 - test eax, eax - je LBB2_2 - sub esp, 8 - push dword ptr [ebp + 16] + add esp, 8 + push esi push eax call _objc_msgSend add esp, 20 pop esi pop ebp ret -LBB2_2: - sub esp, 4 - lea eax, [esi + l___unnamed_1-L2$pb] - lea ecx, [esi + l___unnamed_2-L2$pb] - push eax - push 17 - push ecx - call __ZN4core6option13expect_failed17hc70f05bfe703751eE .globl _handle_alloc_release .p2align 4, 0x90 _handle_alloc_release: push ebp mov ebp, esp - push esi - push eax - call L3$pb -L3$pb: - pop esi sub esp, 8 - push dword ptr [ebp + 12] - push dword ptr [ebp + 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 - add esp, 16 - test eax, eax - je LBB3_2 - sub esp, 12 - push eax + mov dword ptr [esp], eax call _objc_release - add esp, 20 - pop esi + add esp, 8 pop ebp ret -LBB3_2: - sub esp, 4 - lea eax, [esi + l___unnamed_1-L3$pb] - lea ecx, [esi + l___unnamed_2-L3$pb] - push eax - push 17 - push ecx - call __ZN4core6option13expect_failed17hc70f05bfe703751eE .globl _handle_alloc_init_release .p2align 4, 0x90 @@ -111,36 +60,22 @@ _handle_alloc_init_release: push ebp mov ebp, esp push esi - push eax - call L4$pb -L4$pb: - pop esi - sub esp, 8 - push dword ptr [ebp + 12] - push dword ptr [ebp + 8] + 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 - add esp, 16 - test eax, eax - je LBB4_2 - sub esp, 8 - push dword ptr [ebp + 16] - push eax + mov dword ptr [esp + 4], esi + mov dword ptr [esp], eax call _objc_msgSend - add esp, 4 - push eax + mov dword ptr [esp], eax call _objc_release add esp, 20 pop esi pop ebp ret -LBB4_2: - sub esp, 4 - lea eax, [esi + l___unnamed_1-L4$pb] - lea ecx, [esi + l___unnamed_2-L4$pb] - push eax - push 17 - push ecx - call __ZN4core6option13expect_failed17hc70f05bfe703751eE .globl _handle_copy .p2align 4, 0x90 @@ -172,17 +107,4 @@ _handle_autoreleased: pop ebp ret - .section __TEXT,__const -l___unnamed_2: - .ascii "Failed allocating" - -l___unnamed_3: - .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" - - .section __DATA,__const - .p2align 2 -l___unnamed_1: - .long l___unnamed_3 - .asciz "B\000\000\000\037\000\000\000%\000\000" - .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 index 656a56fe5..09136fc21 100644 --- a/tests/assembly/test_msg_send_id/expected/apple-x86_64.s +++ b/tests/assembly/test_msg_send_id/expected/apple-x86_64.s @@ -5,16 +5,8 @@ _handle_alloc: push rbp mov rbp, rsp - call _objc_msgSend - test rax, rax - je LBB0_2 pop rbp - ret -LBB0_2: - lea rdi, [rip + l___unnamed_1] - lea rdx, [rip + l___unnamed_2] - mov esi, 17 - call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE + jmp _objc_msgSend .globl _handle_init .p2align 4, 0x90 @@ -33,19 +25,12 @@ _handle_alloc_init: push rax mov rbx, rdx call _objc_msgSend - test rax, rax - je LBB2_1 mov rdi, rax mov rsi, rbx add rsp, 8 pop rbx pop rbp jmp _objc_msgSend -LBB2_1: - lea rdi, [rip + l___unnamed_1] - lea rdx, [rip + l___unnamed_2] - mov esi, 17 - call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE .globl _handle_alloc_release .p2align 4, 0x90 @@ -53,16 +38,9 @@ _handle_alloc_release: push rbp mov rbp, rsp call _objc_msgSend - test rax, rax - je LBB3_1 mov rdi, rax pop rbp jmp _objc_release -LBB3_1: - lea rdi, [rip + l___unnamed_1] - lea rdx, [rip + l___unnamed_2] - mov esi, 17 - call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE .globl _handle_alloc_init_release .p2align 4, 0x90 @@ -73,8 +51,6 @@ _handle_alloc_init_release: push rax mov rbx, rdx call _objc_msgSend - test rax, rax - je LBB4_1 mov rdi, rax mov rsi, rbx call _objc_msgSend @@ -83,11 +59,6 @@ _handle_alloc_init_release: pop rbx pop rbp jmp _objc_release -LBB4_1: - lea rdi, [rip + l___unnamed_1] - lea rdx, [rip + l___unnamed_2] - mov esi, 17 - call __ZN4core6option13expect_failed17h5222b7f29d1fceaaE .globl _handle_copy .p2align 4, 0x90 @@ -113,17 +84,4 @@ _handle_autoreleased: pop rbp ret - .section __TEXT,__const -l___unnamed_1: - .ascii "Failed allocating" - -l___unnamed_3: - .ascii "$WORKSPACE/objc2/src/__macro_helpers.rs" - - .section __DATA,__const - .p2align 3 -l___unnamed_2: - .quad l___unnamed_3 - .asciz "B\000\000\000\000\000\000\000\037\000\000\000%\000\000" - .subsections_via_symbols diff --git a/tests/assembly/test_msg_send_id/lib.rs b/tests/assembly/test_msg_send_id/lib.rs index 33c73c242..4501ebdde 100644 --- a/tests/assembly/test_msg_send_id/lib.rs +++ b/tests/assembly/test_msg_send_id/lib.rs @@ -4,12 +4,12 @@ use objc2::rc::{Id, Shared}; use objc2::runtime::{Class, Object, Sel}; #[no_mangle] -unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Id { +unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Option> { >::send_message_id(obj, sel, ()).unwrap() } #[no_mangle] -unsafe fn handle_init(obj: Id, sel: Sel) -> Option> { +unsafe fn handle_init(obj: Option>, sel: Sel) -> Option> { >::send_message_id(obj, sel, ()).unwrap() } @@ -21,8 +21,9 @@ unsafe fn handle_alloc_init(obj: &Class, sel1: Sel, sel2: Sel) -> Option = - >::send_message_id(cls, sel, ()).unwrap(); + let _obj: Id = >::send_message_id(cls, sel, ()) + .unwrap() + .unwrap_unchecked(); } #[no_mangle] From 8f8b0e91fd8fd2da44fc3450a501d76e217c0968 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 04:33:58 +0200 Subject: [PATCH 07/22] Add UI tests for msg_send_id! --- tests/ui/msg_send_id_invalid_receiver.rs | 16 +++ tests/ui/msg_send_id_invalid_receiver.stderr | 62 ++++++++++ tests/ui/msg_send_id_invalid_return.rs | 25 +++++ tests/ui/msg_send_id_invalid_return.stderr | 112 +++++++++++++++++++ tests/ui/msg_send_id_underspecified.rs | 10 ++ tests/ui/msg_send_id_underspecified.stderr | 12 ++ 6 files changed, 237 insertions(+) create mode 100644 tests/ui/msg_send_id_invalid_receiver.rs create mode 100644 tests/ui/msg_send_id_invalid_receiver.stderr create mode 100644 tests/ui/msg_send_id_invalid_return.rs create mode 100644 tests/ui/msg_send_id_invalid_return.stderr create mode 100644 tests/ui/msg_send_id_underspecified.rs create mode 100644 tests/ui/msg_send_id_underspecified.stderr 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..d16dbc606 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_receiver.rs @@ -0,0 +1,16 @@ +//! 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, 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..1b7f6d99b --- /dev/null +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -0,0 +1,62 @@ +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:8:55 + | +8 | 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:9:55 + | +9 | 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:12:55 + | +12 | 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:15:42 + | +15 | 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, Option>>` for `Assert` + = 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..1c788aafd --- /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, Option>>` for `Assert` + = 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, Option>>` for `Assert` + = 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..3a5ff69b1 --- /dev/null +++ b/tests/ui/msg_send_id_underspecified.rs @@ -0,0 +1,10 @@ +//! Test compiler output of msg_send_id when ownership is not specified. +//! +//! Don't think it's really possible for us to improve this diagnostic? +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..cd5d04ad9 --- /dev/null +++ b/tests/ui/msg_send_id_underspecified.stderr @@ -0,0 +1,12 @@ +error[E0282]: type annotations needed for `Option>` + --> ui/msg_send_id_underspecified.rs:9:33 + | +9 | let _: &Object = &*unsafe { msg_send_id![obj, description].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider giving `result` an explicit type, where the type for type parameter `O` is specified + --> $WORKSPACE/objc2/src/macros.rs + | + | let result: Option>; + | +++++++++++++++++++++++++++++++++++++++ From 4252102d65c976132a49c87b9d81e3c43d7240d8 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 03:08:44 +0200 Subject: [PATCH 08/22] Disallow `retain`, `release` and `autorelease` in msg_send_id! --- objc2/src/__macro_helpers.rs | 3 ++ objc2/src/macros.rs | 22 ++++++++++++++ tests/ui/msg_send_id_invalid_method.rs | 16 ++++++++++ tests/ui/msg_send_id_invalid_method.stderr | 34 ++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 tests/ui/msg_send_id_invalid_method.rs create mode 100644 tests/ui/msg_send_id_invalid_method.stderr diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 5fd74a14b..10f75ebb7 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -2,6 +2,9 @@ use crate::rc::{Id, Ownership}; use crate::runtime::{Class, Sel}; use crate::{Message, MessageArguments, MessageError, MessageReceiver}; +#[doc(hidden)] +pub use core::compile_error; + #[doc(hidden)] pub struct Assert {} diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 683621858..ccb2f504d 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -230,8 +230,30 @@ macro_rules! msg_send_bool { } /// TODO +/// +/// The `retain`, `release` and `autorelease` selectors are not supported, use +/// [`Id::retain`], [`Id::drop`] and [`Id::autorelease`] for that. +/// +/// [`Id::retain`]: crate::rc::Id::retain +/// [`Id::drop`]: crate::rc::Id::drop +/// [`Id::autorelease`]: crate::rc::Id::autorelease #[macro_export] macro_rules! msg_send_id { + [$obj:expr, retain $(,)?] => ({ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, retain] is not supported. Use `Id::retain` instead" + ) + }); + [$obj:expr, release $(,)?] => ({ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, release] is not supported. Drop an `Id` instead" + ) + }); + [$obj:expr, autorelease $(,)?] => ({ + $crate::__macro_helpers::compile_error!( + "msg_send_id![obj, autorelease] is not supported. Use `Id::autorelease`" + ) + }); [$obj:expr, $selector:ident $(,)?] => ({ let sel = $crate::sel!($selector); const NAME: &[u8] = stringify!($selector).as_bytes(); 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..9272e5899 --- /dev/null +++ b/tests/ui/msg_send_id_invalid_method.rs @@ -0,0 +1,16 @@ +//! Test invalid msg_send_id methods. +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..4334d93db --- /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:7:14 + | +7 | unsafe { msg_send_id![object, retain] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `msg_send_id` (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:8:14 + | +8 | unsafe { msg_send_id![object, release] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `msg_send_id` (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:9:14 + | +9 | unsafe { msg_send_id![object, autorelease] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `msg_send_id` (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:11:9 + | +11 | / msg_send_id![ +12 | | object, +13 | | retain, +14 | | ] + | |_________^ + | + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) From 4177dfffd04a436205365a26f20cea78030208d1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 03:45:06 +0200 Subject: [PATCH 09/22] Allow msg_send_id![obj, init] to non-option wrapped Id --- objc2/src/__macro_helpers.rs | 57 +++++++++++++++++++- tests/ui/msg_send_id_invalid_receiver.stderr | 54 +++++++++---------- 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 10f75ebb7..67dbfbad1 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -1,3 +1,5 @@ +use core::mem::ManuallyDrop; + use crate::rc::{Id, Ownership}; use crate::runtime::{Class, Sel}; use crate::{Message, MessageArguments, MessageError, MessageReceiver}; @@ -52,6 +54,21 @@ impl MsgSendId>, Option MsgSendId, Option>> + for Assert +{ + #[inline(always)] + unsafe fn send_message_id( + obj: Id, + sel: Sel, + args: A, + ) -> Result>, MessageError> { + let ptr = Id::consume_as_ptr(ManuallyDrop::new(obj)); + unsafe { MessageReceiver::send_message(ptr, sel, args).map(|r| Id::new(r)) } + } +} + // `copy`, `mutableCopy` and `new` impl MsgSendId>> for Assert @@ -126,8 +143,46 @@ pub const fn in_method_family(mut selector: &[u8], mut family: &[u8]) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::rc::{Id, Owned, Shared}; + + use core::ptr; + + use crate::rc::{Owned, Shared}; use crate::runtime::Object; + use crate::{Encoding, RefEncode}; + + #[repr(C)] + struct _NSZone { + _inner: [u8; 0], + } + + unsafe impl RefEncode for _NSZone { + const ENCODING_REF: Encoding<'static> = + Encoding::Pointer(&Encoding::Struct("_NSZone", &[])); + } + + #[test] + fn test_macro_alloc() { + let cls = class!(NSObject); + + let _obj: Option> = unsafe { msg_send_id![cls, alloc] }; + + let zone: *const _NSZone = ptr::null(); + let _obj: Option> = unsafe { msg_send_id![cls, allocWithZone: zone] }; + } + + #[test] + fn test_macro_init() { + let cls = class!(NSObject); + + let obj: Option> = unsafe { msg_send_id![cls, alloc] }; + // Don't check allocation error + let _obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + + let obj: Option> = unsafe { msg_send_id![cls, alloc] }; + // Check allocation error before init + let obj = obj.unwrap(); + let _obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + } #[test] fn test_macro() { diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 1b7f6d99b..67f7deec5 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -15,39 +15,33 @@ note: associated function defined here | 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, 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[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Object, _>` is not satisfied + --> ui/msg_send_id_invalid_receiver.rs:9:42 + | +9 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Object, _>` is not implemented for `Assert` + | + = help: the following other types implement trait `MsgSendId`: + as MsgSendId>>> + as MsgSendId>>> + as MsgSendId, Option>>> + as MsgSendId>, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> + = 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_receiver.rs:12:55 +error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Class, _>` is not satisfied + --> ui/msg_send_id_invalid_receiver.rs:12:42 | 12 | 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( - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Class, _>` is not implemented for `Assert` + | + = help: the following other types implement trait `MsgSendId`: + as MsgSendId>>> + as MsgSendId>>> + as MsgSendId, Option>>> + as MsgSendId>, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Id: MessageReceiver` is not satisfied --> ui/msg_send_id_invalid_receiver.rs:15:42 From b79f3cf8aca1291055e13ba6493bc7dbcac34f49 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 04:35:55 +0200 Subject: [PATCH 10/22] Special-case `msg_send_id[cls, new]` methods to only accept &Class --- objc2/src/__macro_helpers.rs | 34 ++++++++-- objc2/src/macros.rs | 25 +++---- tests/assembly/test_msg_send_id/lib.rs | 28 ++++---- tests/ui/msg_send_id_invalid_receiver.rs | 1 + tests/ui/msg_send_id_invalid_receiver.stderr | 71 +++++++++++++------- tests/ui/msg_send_id_invalid_return.stderr | 4 +- 6 files changed, 100 insertions(+), 63 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 67dbfbad1..75d54bd65 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -7,8 +7,14 @@ use crate::{Message, MessageArguments, MessageError, MessageReceiver}; #[doc(hidden)] pub use core::compile_error; +// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments #[doc(hidden)] -pub struct Assert {} +pub struct Assert< + const NEW: bool, + const ALLOC: bool, + const INIT: bool, + const COPY_OR_MUT_COPY: bool, +> {} #[doc(hidden)] pub trait MsgSendId { @@ -19,9 +25,23 @@ pub trait MsgSendId { ) -> Result; } +// `new` +impl MsgSendId<&'_ Class, Option>> + for Assert +{ + #[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, Option>> - for Assert + for Assert { #[inline(always)] unsafe fn send_message_id( @@ -35,7 +55,7 @@ impl MsgSendId<&'_ Class, Option>> // `init`, should mark the input value as "allocated, not initialized" somehow impl MsgSendId>, Option>> - for Assert + for Assert { #[inline(always)] unsafe fn send_message_id( @@ -56,7 +76,7 @@ impl MsgSendId>, Option MsgSendId, Option>> - for Assert + for Assert { #[inline(always)] unsafe fn send_message_id( @@ -69,9 +89,9 @@ impl MsgSendId, Option>> } } -// `copy`, `mutableCopy` and `new` +// `copy` and `mutableCopy` impl MsgSendId>> - for Assert + for Assert { #[inline(always)] unsafe fn send_message_id( @@ -85,7 +105,7 @@ impl MsgSendId MsgSendId>> - for Assert + for Assert { #[inline(always)] unsafe fn send_message_id( diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index ccb2f504d..d33abfe89 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -258,9 +258,8 @@ macro_rules! msg_send_id { let sel = $crate::sel!($selector); const NAME: &[u8] = stringify!($selector).as_bytes(); $crate::msg_send_id!(@__get_assert_consts NAME); - use $crate::__macro_helpers::{MsgSendId, Assert}; let result; - match as MsgSendId<_, _>>::send_message_id($obj, sel, ()) { + match >::send_message_id($obj, sel, ()) { Err(s) => panic!("{}", s), Ok(r) => result = r, } @@ -270,25 +269,21 @@ macro_rules! msg_send_id { let sel = $crate::sel!($($selector:)+); const NAME: &[u8] = concat!($(stringify!($selector), ':'),+).as_bytes(); $crate::msg_send_id!(@__get_assert_consts NAME); - use $crate::__macro_helpers::{MsgSendId, Assert}; let result; - match as MsgSendId<_, _>>::send_message_id($obj, sel, ($($argument,)+)) { + match >::send_message_id($obj, sel, ($($argument,)+)) { Err(s) => panic!("{}", s), Ok(r) => result = r, } result }); - (@__get_assert_consts $name:ident) => { - const ALLOC: bool = $crate::__macro_helpers::in_method_family($name, b"alloc"); - // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#consumed-parameters - const INIT: bool = $crate::__macro_helpers::in_method_family($name, b"init"); - // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retained-return-values - const RETAINED: bool = { - $crate::__macro_helpers::in_method_family($name, b"alloc") - || $crate::__macro_helpers::in_method_family($name, b"new") - || $crate::__macro_helpers::in_method_family($name, b"copy") - || $crate::__macro_helpers::in_method_family($name, b"mutableCopy") - || $crate::__macro_helpers::in_method_family($name, b"init") + (@__get_assert_consts $selector:ident) => { + const NEW: bool = $crate::__macro_helpers::in_method_family($selector, b"new"); + const ALLOC: bool = $crate::__macro_helpers::in_method_family($selector, b"alloc"); + const INIT: bool = $crate::__macro_helpers::in_method_family($selector, b"init"); + const COPY_OR_MUT_COPY: bool = { + $crate::__macro_helpers::in_method_family($selector, b"copy") + || $crate::__macro_helpers::in_method_family($selector, b"mutableCopy") }; + type X = $crate::__macro_helpers::Assert; }; } diff --git a/tests/assembly/test_msg_send_id/lib.rs b/tests/assembly/test_msg_send_id/lib.rs index 4501ebdde..8e176ebd1 100644 --- a/tests/assembly/test_msg_send_id/lib.rs +++ b/tests/assembly/test_msg_send_id/lib.rs @@ -5,41 +5,43 @@ use objc2::runtime::{Class, Object, Sel}; #[no_mangle] unsafe fn handle_alloc(obj: &Class, sel: Sel) -> Option> { - >::send_message_id(obj, sel, ()).unwrap() + >::send_message_id(obj, sel, ()).unwrap() } #[no_mangle] unsafe fn handle_init(obj: Option>, sel: Sel) -> Option> { - >::send_message_id(obj, sel, ()).unwrap() + >::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() + 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(); + 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(); + 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() + >::send_message_id(obj, sel, ()).unwrap() } #[no_mangle] unsafe fn handle_autoreleased(obj: &Object, sel: Sel) -> Option> { - >::send_message_id(obj, sel, ()).unwrap() + >::send_message_id(obj, sel, ()).unwrap() } diff --git a/tests/ui/msg_send_id_invalid_receiver.rs b/tests/ui/msg_send_id_invalid_receiver.rs index d16dbc606..2a85010f6 100644 --- a/tests/ui/msg_send_id_invalid_receiver.rs +++ b/tests/ui/msg_send_id_invalid_receiver.rs @@ -5,6 +5,7 @@ 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() }; diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 67f7deec5..3098a4a26 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -1,7 +1,24 @@ error[E0308]: mismatched types --> ui/msg_send_id_invalid_receiver.rs:8:55 | -8 | let _: Id = unsafe { msg_send_id![obj, alloc].unwrap() }; +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` @@ -15,42 +32,44 @@ note: associated function defined here | unsafe fn send_message_id( | ^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Object, _>` is not satisfied - --> ui/msg_send_id_invalid_receiver.rs:9:42 - | -9 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Object, _>` is not implemented for `Assert` - | - = help: the following other types implement trait `MsgSendId`: - as MsgSendId>>> - as MsgSendId>>> - as MsgSendId, Option>>> - as MsgSendId>, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Object, _>` is not satisfied + --> ui/msg_send_id_invalid_receiver.rs:10:42 + | +10 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Object, _>` is not implemented for `Assert` + | + = help: the following other types implement trait `MsgSendId`: + as MsgSendId>>> + as MsgSendId>>> + as MsgSendId, Option>>> + as MsgSendId>, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> + = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Class, _>` is not satisfied - --> ui/msg_send_id_invalid_receiver.rs:12:42 +error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Class, _>` is not satisfied + --> ui/msg_send_id_invalid_receiver.rs:13:42 | -12 | let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Class, _>` is not implemented for `Assert` +13 | let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Class, _>` is not implemented for `Assert` | = help: the following other types implement trait `MsgSendId`: - as MsgSendId>>> - as MsgSendId>>> - as MsgSendId, Option>>> - as MsgSendId>, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> + as MsgSendId>>> + as MsgSendId>>> + as MsgSendId, Option>>> + as MsgSendId>, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> + as MsgSendId<&objc2::runtime::Class, Option>>> = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Id: MessageReceiver` is not satisfied - --> ui/msg_send_id_invalid_receiver.rs:15:42 + --> ui/msg_send_id_invalid_receiver.rs:16:42 | -15 | let _: Id = unsafe { msg_send_id![obj, copy].unwrap() }; +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, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId, Option>>` for `Assert` = 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.stderr b/tests/ui/msg_send_id_invalid_return.stderr index 1c788aafd..6fe384fc4 100644 --- a/tests/ui/msg_send_id_invalid_return.stderr +++ b/tests/ui/msg_send_id_invalid_return.stderr @@ -26,7 +26,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` = 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 @@ -57,7 +57,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` = 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 From d635b89c731ea169a3117797bdaab934edbb66f7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 04:36:23 +0200 Subject: [PATCH 11/22] Fix UI compilation regression in msg_send_id![obj, init] --- objc2/src/__macro_helpers.rs | 25 +++----------- tests/ui/msg_send_id_invalid_receiver.stderr | 34 +++++++++----------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 75d54bd65..738a0cf11 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -1,5 +1,3 @@ -use core::mem::ManuallyDrop; - use crate::rc::{Id, Ownership}; use crate::runtime::{Class, Sel}; use crate::{Message, MessageArguments, MessageError, MessageReceiver}; @@ -54,16 +52,18 @@ impl MsgSendId<&'_ Class, Option>> } // `init`, should mark the input value as "allocated, not initialized" somehow -impl MsgSendId>, Option>> +// +// The generic bound allows `init` to take both `Option` and `Id`. +impl>>, T: ?Sized + Message, O: Ownership> MsgSendId>> for Assert { #[inline(always)] unsafe fn send_message_id( - obj: Option>, + obj: X, sel: Sel, args: A, ) -> Result>, MessageError> { - let ptr = Id::option_into_ptr(obj); + let ptr = Id::option_into_ptr(obj.into()); // 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. @@ -74,21 +74,6 @@ impl MsgSendId>, Option MsgSendId, Option>> - for Assert -{ - #[inline(always)] - unsafe fn send_message_id( - obj: Id, - sel: Sel, - args: A, - ) -> Result>, MessageError> { - let ptr = Id::consume_as_ptr(ManuallyDrop::new(obj)); - unsafe { MessageReceiver::send_message(ptr, sel, args).map(|r| Id::new(r)) } - } -} - // `copy` and `mutableCopy` impl MsgSendId>> for Assert diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 3098a4a26..20a9d185a 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -32,34 +32,32 @@ note: associated function defined here | unsafe fn send_message_id( | ^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Object, _>` is not satisfied +error[E0277]: the trait bound `Option>: From<&objc2::runtime::Object>` is not satisfied --> ui/msg_send_id_invalid_receiver.rs:10:42 | 10 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Object, _>` is not implemented for `Assert` + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&objc2::runtime::Object>` is not implemented for `Option>` | - = help: the following other types implement trait `MsgSendId`: - as MsgSendId>>> - as MsgSendId>>> - as MsgSendId, Option>>> - as MsgSendId>, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> + = help: the following other types implement trait `From`: + as From<&'a Option>> + as From<&'a mut Option>> + as From> + = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Object` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, Option>>` for `Assert` = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Assert: MsgSendId<&objc2::runtime::Class, _>` is not satisfied +error[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` is not satisfied --> ui/msg_send_id_invalid_receiver.rs:13:42 | 13 | let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `MsgSendId<&objc2::runtime::Class, _>` is not implemented for `Assert` + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&objc2::runtime::Class>` is not implemented for `Option>` | - = help: the following other types implement trait `MsgSendId`: - as MsgSendId>>> - as MsgSendId>>> - as MsgSendId, Option>>> - as MsgSendId>, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> - as MsgSendId<&objc2::runtime::Class, Option>>> + = help: the following other types implement trait `From`: + as From<&'a Option>> + as From<&'a mut Option>> + as From> + = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Class` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Id: MessageReceiver` is not satisfied From aed5cdd834f7a6cf21201995935fe57b05637c55 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 03:50:52 +0200 Subject: [PATCH 12/22] Improve msg_send_id! macro diagnostics a bit more --- objc2/src/__macro_helpers.rs | 12 ++++++------ objc2/src/macros.rs | 4 ++-- tests/ui/msg_send_id_invalid_receiver.stderr | 6 +++--- tests/ui/msg_send_id_invalid_return.stderr | 4 ++-- tests/ui/msg_send_id_underspecified.rs | 2 -- tests/ui/msg_send_id_underspecified.stderr | 13 ++++--------- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 738a0cf11..106f9a9c7 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -20,11 +20,11 @@ pub trait MsgSendId { obj: T, sel: Sel, args: A, - ) -> Result; + ) -> Result, MessageError>; } // `new` -impl MsgSendId<&'_ Class, Option>> +impl MsgSendId<&'_ Class, Id> for Assert { #[inline(always)] @@ -38,7 +38,7 @@ impl MsgSendId<&'_ Class, Option>> } // `alloc`, should mark the return value as "allocated, not initialized" somehow -impl MsgSendId<&'_ Class, Option>> +impl MsgSendId<&'_ Class, Id> for Assert { #[inline(always)] @@ -54,7 +54,7 @@ impl MsgSendId<&'_ Class, Option>> // `init`, should mark the input value as "allocated, not initialized" somehow // // The generic bound allows `init` to take both `Option` and `Id`. -impl>>, T: ?Sized + Message, O: Ownership> MsgSendId>> +impl>>, T: ?Sized + Message, O: Ownership> MsgSendId> for Assert { #[inline(always)] @@ -75,7 +75,7 @@ impl>>, T: ?Sized + Message, O: Ownership> MsgSendId MsgSendId>> +impl MsgSendId> for Assert { #[inline(always)] @@ -89,7 +89,7 @@ impl MsgSendId MsgSendId>> +impl MsgSendId> for Assert { #[inline(always)] diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index d33abfe89..716a0a3ac 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -258,7 +258,7 @@ macro_rules! msg_send_id { let sel = $crate::sel!($selector); const NAME: &[u8] = stringify!($selector).as_bytes(); $crate::msg_send_id!(@__get_assert_consts NAME); - let result; + let result: Option<$crate::rc::Id<_, _>>; match >::send_message_id($obj, sel, ()) { Err(s) => panic!("{}", s), Ok(r) => result = r, @@ -269,7 +269,7 @@ macro_rules! msg_send_id { let sel = $crate::sel!($($selector:)+); const NAME: &[u8] = concat!($(stringify!($selector), ':'),+).as_bytes(); $crate::msg_send_id!(@__get_assert_consts NAME); - let result; + let result: Option<$crate::rc::Id<_, _>>; match >::send_message_id($obj, sel, ($($argument,)+)) { Err(s) => panic!("{}", s), Ok(r) => result = r, diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 20a9d185a..3a93a3691 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -43,7 +43,7 @@ error[E0277]: the trait bound `Option>: From<&objc2::runtime::Object>` as From<&'a mut Option>> as From> = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Object` - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, Id<_, _>>` for `Assert` = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` is not satisfied @@ -57,7 +57,7 @@ error[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` i as From<&'a mut Option>> as From> = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Class` - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id<_, _>>` for `Assert` = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Id: MessageReceiver` is not satisfied @@ -69,5 +69,5 @@ error[E0277]: the trait bound `Id: MessageReceiv = help: the following other types implement trait `MessageReceiver`: &'a Id &'a mut Id - = note: required because of the requirements on the impl of `MsgSendId, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId, Id<_, _>>` for `Assert` = 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.stderr b/tests/ui/msg_send_id_invalid_return.stderr index 6fe384fc4..42d8b6cd5 100644 --- a/tests/ui/msg_send_id_invalid_return.stderr +++ b/tests/ui/msg_send_id_invalid_return.stderr @@ -26,7 +26,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `Assert` = 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 @@ -57,7 +57,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Option>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `Assert` = 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 diff --git a/tests/ui/msg_send_id_underspecified.rs b/tests/ui/msg_send_id_underspecified.rs index 3a5ff69b1..e6b638894 100644 --- a/tests/ui/msg_send_id_underspecified.rs +++ b/tests/ui/msg_send_id_underspecified.rs @@ -1,6 +1,4 @@ //! Test compiler output of msg_send_id when ownership is not specified. -//! -//! Don't think it's really possible for us to improve this diagnostic? use objc2::msg_send_id; use objc2::runtime::Object; diff --git a/tests/ui/msg_send_id_underspecified.stderr b/tests/ui/msg_send_id_underspecified.stderr index cd5d04ad9..e7ff01354 100644 --- a/tests/ui/msg_send_id_underspecified.stderr +++ b/tests/ui/msg_send_id_underspecified.stderr @@ -1,12 +1,7 @@ -error[E0282]: type annotations needed for `Option>` - --> ui/msg_send_id_underspecified.rs:9:33 +error[E0282]: type annotations needed + --> ui/msg_send_id_underspecified.rs:7:33 | -9 | let _: &Object = &*unsafe { msg_send_id![obj, description].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +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) -help: consider giving `result` an explicit type, where the type for type parameter `O` is specified - --> $WORKSPACE/objc2/src/macros.rs - | - | let result: Option>; - | +++++++++++++++++++++++++++++++++++++++ From f1ca00ae71d8e92cd708a72feeb5c7ad76a65bdd Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 06:19:18 +0200 Subject: [PATCH 13/22] Test reference-counting properties of msg_send_id! --- objc2/src/__macro_helpers.rs | 108 +++++++++++++++++++++++++---------- objc2/src/rc/test_object.rs | 7 ++- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 106f9a9c7..7511c9b56 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -151,59 +151,107 @@ mod tests { use core::ptr; - use crate::rc::{Owned, Shared}; + use crate::rc::{Owned, RcTestObject, Shared, ThreadTestData}; use crate::runtime::Object; use crate::{Encoding, RefEncode}; - #[repr(C)] - struct _NSZone { - _inner: [u8; 0], - } + #[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(); - unsafe impl RefEncode for _NSZone { - const ENCODING_REF: Encoding<'static> = - Encoding::Pointer(&Encoding::Struct("_NSZone", &[])); + drop(obj); + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); } #[test] - fn test_macro_alloc() { - let cls = class!(NSObject); + #[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], + } - let _obj: Option> = unsafe { msg_send_id![cls, alloc] }; + 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: Option> = unsafe { msg_send_id![cls, allocWithZone: zone] }; + 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 cls = class!(NSObject); + let mut expected = ThreadTestData::current(); + let cls = RcTestObject::class(); - let obj: Option> = unsafe { msg_send_id![cls, alloc] }; + 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() }; + 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] }; + 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![obj, init].unwrap() }; + let _obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + expected.init += 1; + expected.assert_current(); } #[test] fn test_macro() { - let cls = class!(NSObject); - - let _obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; - - let obj = unsafe { msg_send_id![cls, alloc] }; - - let obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; - - // TODO: - // let copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; - // let mutable_copy: Id = unsafe { msg_send_id![&obj, mutableCopy].unwrap() }; - - let _desc: Option> = unsafe { msg_send_id![&obj, description] }; + 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] diff --git a/objc2/src/rc/test_object.rs b/objc2/src/rc/test_object.rs index f062688b3..90d66540e 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -5,7 +5,7 @@ use std::sync::Once; use super::{Id, Owned}; use crate::declare::ClassBuilder; use crate::runtime::{Bool, Class, Object, Sel}; -use crate::{msg_send, msg_send_bool, msg_send_id}; +use crate::{msg_send, msg_send_bool}; use crate::{Encoding, Message, RefEncode}; #[derive(Debug, Clone, Default, PartialEq)] @@ -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 { - unsafe { msg_send_id![Self::class(), new] }.unwrap() + // Use msg_send! to test that; msg_send_id! is tested elsewhere! + unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap() } } From 67633bb37d282f8c49a7a93f5e377e00797f5512 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 06:47:29 +0200 Subject: [PATCH 14/22] Don't leak macro implementation details in `msg_send_id!` docs --- objc2/src/macros.rs | 44 ++++++++++++-------- tests/ui/msg_send_id_invalid_method.rs | 4 ++ tests/ui/msg_send_id_invalid_method.stderr | 48 +++++++++++----------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 716a0a3ac..9fda16cbc 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -239,25 +239,11 @@ macro_rules! msg_send_bool { /// [`Id::autorelease`]: crate::rc::Id::autorelease #[macro_export] macro_rules! msg_send_id { - [$obj:expr, retain $(,)?] => ({ - $crate::__macro_helpers::compile_error!( - "msg_send_id![obj, retain] is not supported. Use `Id::retain` instead" - ) - }); - [$obj:expr, release $(,)?] => ({ - $crate::__macro_helpers::compile_error!( - "msg_send_id![obj, release] is not supported. Drop an `Id` instead" - ) - }); - [$obj:expr, autorelease $(,)?] => ({ - $crate::__macro_helpers::compile_error!( - "msg_send_id![obj, autorelease] is not supported. Use `Id::autorelease`" - ) - }); [$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!(@__get_assert_consts NAME); + $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), @@ -268,7 +254,7 @@ macro_rules! msg_send_id { [$obj:expr, $($selector:ident : $argument:expr),+ $(,)?] => ({ let sel = $crate::sel!($($selector:)+); const NAME: &[u8] = concat!($(stringify!($selector), ':'),+).as_bytes(); - $crate::msg_send_id!(@__get_assert_consts NAME); + $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), @@ -276,7 +262,29 @@ macro_rules! msg_send_id { } result }); - (@__get_assert_consts $selector:ident) => { +} + +/// 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_method_family($selector, b"new"); const ALLOC: bool = $crate::__macro_helpers::in_method_family($selector, b"alloc"); const INIT: bool = $crate::__macro_helpers::in_method_family($selector, b"init"); diff --git a/tests/ui/msg_send_id_invalid_method.rs b/tests/ui/msg_send_id_invalid_method.rs index 9272e5899..597a69a75 100644 --- a/tests/ui/msg_send_id_invalid_method.rs +++ b/tests/ui/msg_send_id_invalid_method.rs @@ -1,4 +1,8 @@ //! 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; diff --git a/tests/ui/msg_send_id_invalid_method.stderr b/tests/ui/msg_send_id_invalid_method.stderr index 4334d93db..acb6e56ec 100644 --- a/tests/ui/msg_send_id_invalid_method.stderr +++ b/tests/ui/msg_send_id_invalid_method.stderr @@ -1,34 +1,34 @@ error: msg_send_id![obj, retain] is not supported. Use `Id::retain` instead - --> ui/msg_send_id_invalid_method.rs:7:14 - | -7 | unsafe { msg_send_id![object, retain] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + --> 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:8:14 - | -8 | unsafe { msg_send_id![object, release] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + --> 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:9:14 - | -9 | unsafe { msg_send_id![object, autorelease] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + --> 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:11:9 + --> ui/msg_send_id_invalid_method.rs:15:9 | -11 | / msg_send_id![ -12 | | object, -13 | | retain, -14 | | ] +15 | / msg_send_id![ +16 | | object, +17 | | retain, +18 | | ] | |_________^ | - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__msg_send_id_helper` (in Nightly builds, run with -Z macro-backtrace for more info) From e93af9817d740b725cbf81ac25dfb18b72bb64cc Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 07:51:34 +0200 Subject: [PATCH 15/22] Rename internal macro helpers so their names are strictly more correct Assert -> RetainSemantics in_method_family -> in_selector_family --- objc2/src/__macro_helpers.rs | 170 +++++++++++-------- objc2/src/macros.rs | 18 +- tests/assembly/test_msg_send_id/lib.rs | 20 +-- tests/ui/msg_send_id_invalid_receiver.stderr | 6 +- tests/ui/msg_send_id_invalid_return.stderr | 4 +- 5 files changed, 124 insertions(+), 94 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 7511c9b56..1ef6df989 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -5,12 +5,38 @@ use crate::{Message, MessageArguments, MessageError, MessageReceiver}; #[doc(hidden)] pub use core::compile_error; -// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments +/// 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 Assert< +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, > {} @@ -25,7 +51,7 @@ pub trait MsgSendId { // `new` impl MsgSendId<&'_ Class, Id> - for Assert + for RetainSemantics { #[inline(always)] unsafe fn send_message_id( @@ -39,7 +65,7 @@ impl MsgSendId<&'_ Class, Id> // `alloc`, should mark the return value as "allocated, not initialized" somehow impl MsgSendId<&'_ Class, Id> - for Assert + for RetainSemantics { #[inline(always)] unsafe fn send_message_id( @@ -55,7 +81,7 @@ impl MsgSendId<&'_ Class, Id> // // The generic bound allows `init` to take both `Option` and `Id`. impl>>, T: ?Sized + Message, O: Ownership> MsgSendId> - for Assert + for RetainSemantics { #[inline(always)] unsafe fn send_message_id( @@ -76,7 +102,7 @@ impl>>, T: ?Sized + Message, O: Ownership> MsgSendId MsgSendId> - for Assert + for RetainSemantics { #[inline(always)] unsafe fn send_message_id( @@ -90,7 +116,7 @@ impl MsgSendId MsgSendId> - for Assert + for RetainSemantics { #[inline(always)] unsafe fn send_message_id( @@ -104,9 +130,11 @@ impl MsgSendId> } } -// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families +/// Checks whether a given selector is said to be in a given selector family. +/// +/// #[doc(hidden)] -pub const fn in_method_family(mut selector: &[u8], mut family: &[u8]) -> bool { +pub const fn in_selector_family(mut selector: &[u8], mut family: &[u8]) -> bool { // Skip leading underscores from selector loop { selector = match selector { @@ -255,81 +283,81 @@ mod tests { } #[test] - fn test_in_method_family() { + fn test_in_selector_family() { // Common cases - assert!(in_method_family(b"alloc", b"alloc")); - assert!(in_method_family(b"allocWithZone:", b"alloc")); - assert!(!in_method_family(b"dealloc", b"alloc")); - assert!(!in_method_family(b"initialize", b"init")); - assert!(!in_method_family(b"decimalNumberWithDecimal:", b"init")); - assert!(in_method_family(b"initWithCapacity:", b"init")); - assert!(in_method_family(b"_initButPrivate:withParam:", b"init")); - assert!(!in_method_family(b"description", b"init")); - assert!(!in_method_family(b"inIT", b"init")); - - assert!(!in_method_family(b"init", b"copy")); - assert!(!in_method_family(b"copyingStuff:", b"copy")); - assert!(in_method_family(b"copyWithZone:", b"copy")); - assert!(!in_method_family(b"initWithArray:copyItems:", b"copy")); - assert!(in_method_family(b"copyItemAtURL:toURL:error:", b"copy")); - - assert!(!in_method_family(b"mutableCopying", b"mutableCopy")); - assert!(in_method_family(b"mutableCopyWithZone:", b"mutableCopy")); - assert!(in_method_family(b"mutableCopyWithZone:", b"mutableCopy")); - - assert!(in_method_family( + 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_method_family( + assert!(in_selector_family( b"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", b"new" )); - assert!(!in_method_family(b"newsstandAssetDownload", b"new")); + assert!(!in_selector_family(b"newsstandAssetDownload", b"new")); // Trying to weed out edge-cases: - assert!(in_method_family(b"__abcDef", b"abc")); - assert!(in_method_family(b"_abcDef", b"abc")); - assert!(in_method_family(b"abcDef", b"abc")); - assert!(in_method_family(b"___a", b"a")); - assert!(in_method_family(b"__a", b"a")); - assert!(in_method_family(b"_a", b"a")); - assert!(in_method_family(b"a", b"a")); - - assert!(!in_method_family(b"_abcdef", b"abc")); - assert!(!in_method_family(b"_abcdef", b"def")); - assert!(!in_method_family(b"_bcdef", b"abc")); - assert!(!in_method_family(b"a_bc", b"abc")); - assert!(!in_method_family(b"abcdef", b"abc")); - assert!(!in_method_family(b"abcdef", b"def")); - assert!(!in_method_family(b"abcdef", b"abb")); - assert!(!in_method_family(b"___", b"a")); - assert!(!in_method_family(b"_", b"a")); - assert!(!in_method_family(b"", b"a")); - - assert!(in_method_family(b"copy", b"copy")); - assert!(in_method_family(b"copy:", b"copy")); - assert!(in_method_family(b"copyMe", b"copy")); - assert!(in_method_family(b"_copy", b"copy")); - assert!(in_method_family(b"_copy:", b"copy")); - assert!(in_method_family(b"_copyMe", b"copy")); - assert!(!in_method_family(b"copying", b"copy")); - assert!(!in_method_family(b"copying:", b"copy")); - assert!(!in_method_family(b"_copying", b"copy")); - assert!(!in_method_family(b"Copy", b"copy")); - assert!(!in_method_family(b"COPY", b"copy")); + 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_method_family(b"___", b"")); - assert!(in_method_family(b"__", b"")); - assert!(in_method_family(b"_", b"")); - assert!(in_method_family(b"", b"")); - assert!(!in_method_family(b"_a", b"")); - assert!(!in_method_family(b"a", b"")); - assert!(in_method_family(b"_A", b"")); - assert!(in_method_family(b"A", b"")); + 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 { diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 9fda16cbc..ae62f3c35 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -231,6 +231,8 @@ macro_rules! msg_send_bool { /// TODO /// +/// TODO: Assumes that attributes like `objc_method_family`, `ns_returns_retained`, `ns_consumed` and so on are not present. +/// /// The `retain`, `release` and `autorelease` selectors are not supported, use /// [`Id::retain`], [`Id::drop`] and [`Id::autorelease`] for that. /// @@ -245,7 +247,7 @@ macro_rules! msg_send_id { 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, ()) { + match >::send_message_id($obj, sel, ()) { Err(s) => panic!("{}", s), Ok(r) => result = r, } @@ -256,7 +258,7 @@ macro_rules! msg_send_id { 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,)+)) { + match >::send_message_id($obj, sel, ($($argument,)+)) { Err(s) => panic!("{}", s), Ok(r) => result = r, } @@ -285,13 +287,13 @@ macro_rules! __msg_send_id_helper { }}; (@verify $selector:ident) => {{}}; (@get_assert_consts $selector:ident) => { - const NEW: bool = $crate::__macro_helpers::in_method_family($selector, b"new"); - const ALLOC: bool = $crate::__macro_helpers::in_method_family($selector, b"alloc"); - const INIT: bool = $crate::__macro_helpers::in_method_family($selector, b"init"); + 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_method_family($selector, b"copy") - || $crate::__macro_helpers::in_method_family($selector, b"mutableCopy") + $crate::__macro_helpers::in_selector_family($selector, b"copy") + || $crate::__macro_helpers::in_selector_family($selector, b"mutableCopy") }; - type X = $crate::__macro_helpers::Assert; + type RS = $crate::__macro_helpers::RetainSemantics; }; } diff --git a/tests/assembly/test_msg_send_id/lib.rs b/tests/assembly/test_msg_send_id/lib.rs index 8e176ebd1..b2826944d 100644 --- a/tests/assembly/test_msg_send_id/lib.rs +++ b/tests/assembly/test_msg_send_id/lib.rs @@ -1,47 +1,47 @@ //! Test assembly output of `msg_send_id!` internals. -use objc2::__macro_helpers::{Assert, MsgSendId}; +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() + >::send_message_id(obj, sel, ()).unwrap() } #[no_mangle] unsafe fn handle_init(obj: Option>, sel: Sel) -> Option> { - >::send_message_id(obj, sel, ()).unwrap() + >::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() + 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, ()) + >::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 = >::send_message_id(cls, sel1, ()).unwrap(); let _obj: Id = - >::send_message_id(obj, sel2, ()) + >::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() + >::send_message_id(obj, sel, ()).unwrap() } #[no_mangle] unsafe fn handle_autoreleased(obj: &Object, sel: Sel) -> Option> { - >::send_message_id(obj, sel, ()).unwrap() + >::send_message_id(obj, sel, ()).unwrap() } diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 3a93a3691..386b245d5 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -43,7 +43,7 @@ error[E0277]: the trait bound `Option>: From<&objc2::runtime::Object>` as From<&'a mut Option>> as From> = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Object` - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, Id<_, _>>` for `Assert` + = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, 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[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` is not satisfied @@ -57,7 +57,7 @@ error[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` i as From<&'a mut Option>> as From> = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Class` - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id<_, _>>` for `Assert` + = 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[E0277]: the trait bound `Id: MessageReceiver` is not satisfied @@ -69,5 +69,5 @@ error[E0277]: the trait bound `Id: MessageReceiv = 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 `Assert` + = 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.stderr b/tests/ui/msg_send_id_invalid_return.stderr index 42d8b6cd5..3152f36d8 100644 --- a/tests/ui/msg_send_id_invalid_return.stderr +++ b/tests/ui/msg_send_id_invalid_return.stderr @@ -26,7 +26,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `Assert` + = 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 @@ -57,7 +57,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSMutableData NSMutableString and 7 others - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id>` for `Assert` + = 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 From cd8bde5b7d4b341ce65b2661f1bf0b715336805c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 18:54:55 +0200 Subject: [PATCH 16/22] Rewrite objc2 crate introductory text to further mention msg_send_id! --- objc2/README.md | 10 ++--- objc2/src/lib.rs | 108 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/objc2/README.md b/objc2/README.md index 700b7d132..7b2db649d 100644 --- a/objc2/README.md +++ b/objc2/README.md @@ -13,16 +13,16 @@ written in Objective-C; this crate enables you to interract with those. ## Example ```rust -use objc2::{class, msg_send_id}; +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: 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/src/lib.rs b/objc2/src/lib.rs index 965c4c491..01d386649 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,30 +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, 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: Id = unsafe { -//! msg_send_id![cls, new].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 @@ -55,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 @@ -70,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 your 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 @@ -107,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")] From ea7bc9778cebb11e56600de4d07b4286ee33fbae Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 22:09:47 +0200 Subject: [PATCH 17/22] Rewrite msg_send! and msg_send_bool! docs --- objc2/src/macros.rs | 160 ++++++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 66 deletions(-) diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index ae62f3c35..30609a663 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -52,91 +52,104 @@ 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: /// -/// The syntax is similar to the message syntax in Objective-C, except we -/// allow an optional comma between arguments (works better with rustfmt). +/// ``` +/// # 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 on shared references to an 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! /// -/// 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`]. /// -/// Variadic arguments are not currently supported. +/// 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. +/// +/// 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! /// -/// # Safety +/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html /// -/// 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. /// -/// 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] -/// } -/// ``` +/// # Safety +/// +/// Similar to defining and calling an `extern` function in a foreign function +/// interface. In particular, you must uphold the following requirements: /// -/// 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. +/// 1. The selector corresponds to a valid method that is available on the +/// receiver. /// -/// In particular, you must uphold the following requirements: +/// 2. The argument types must match what the receiver excepts for this +/// selector. /// -/// 1. The selector is a valid method that is available on the given receiver. +/// 3. The return type must match what the receiver returns for this selector. /// -/// 2. The types of the receiver and arguments must match what is expected on -/// the Objective-C side. +/// 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`]). /// -/// 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). +/// 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. /// -/// 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. +/// 6. The method must not (yet) throw an exception. /// -/// 5. The method must not (yet, see [RFC-2945]) throw an exception. +/// 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. +/// Another example, some methods may only be called on the main thread. /// -/// 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). +/// 8. TODO: Maybe more? /// -/// 7. TODO: Maybe more? /// /// # Examples /// @@ -151,8 +164,6 @@ macro_rules! sel { /// // Or with an optional comma between arguments: /// let _: () = unsafe { msg_send![obj, setArg1: 1, arg2: 2] }; /// ``` -/// -/// [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 $(,)?] => ({ @@ -193,33 +204,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 { @@ -229,6 +255,8 @@ macro_rules! msg_send_bool { }); } +/// [`msg_send!`] for methods returning `id` or other object pointers. +/// /// TODO /// /// TODO: Assumes that attributes like `objc_method_family`, `ns_returns_retained`, `ns_consumed` and so on are not present. From 4639e3841ef9ab185b42296c1bdf286199c9b7ca Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 21:26:50 +0200 Subject: [PATCH 18/22] Document msg_send_id! --- objc2/src/macros.rs | 121 ++++++++++++++++++++++++++++++++++-- objc2/src/rc/autorelease.rs | 7 +++ 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 30609a663..c7cac7550 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -144,12 +144,18 @@ macro_rules! sel { /// 6. The method must not (yet) throw an exception. /// /// 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. -/// Another example, some methods may only be called on the main thread. +/// 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). /// /// 8. TODO: Maybe more? /// +/// [`autoreleasepool`]: crate::rc::autoreleasepool +/// /// /// # Examples /// @@ -255,18 +261,121 @@ macro_rules! msg_send_bool { }); } -/// [`msg_send!`] for methods returning `id` or other object pointers. +/// [`msg_send!`] for methods returning `id`, `NSObject*`, or similar object +/// pointers. +/// +/// Objective-C's object pointers 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 +/// +/// +/// # 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`): /// -/// TODO +/// - The `new` family: The receiver must be `&Class`, and the return type +/// is a generic `Option>`. /// -/// TODO: Assumes that attributes like `objc_method_family`, `ns_returns_retained`, `ns_consumed` and so on are not present. +/// - 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 either `Id` or +/// `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. +/// +/// 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 $(,)?] => ({ diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index fdff5bbf7..5375e75b7 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -218,6 +218,13 @@ 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: From f837735d9e76aa935a10261a2c61283de0615482 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 22:06:56 +0200 Subject: [PATCH 19/22] Add changelog entry --- objc2/CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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 From 3f1f13858d6b29297068e0e2097015fe7d7c403b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 22:04:19 +0200 Subject: [PATCH 20/22] Small documentation fixes --- objc2/src/lib.rs | 2 +- objc2/src/macros.rs | 35 ++++++++++++++++++----------------- objc2/src/rc/test_object.rs | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs index 01d386649..e69737580 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -107,7 +107,7 @@ //! //! To use this functionality, enable the `"verify_message"` cargo feature //! while debugging. With this feature enabled, encodings are checked every -//! time your send a message, and the message send will panic if they are not +//! 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 diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index c7cac7550..99aeed7ee 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -71,10 +71,10 @@ macro_rules! sel { /// ``` /// /// This way we are clearly communicating to Rust that: The method -/// `doSomething:` works on shared references to an 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! +/// `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 @@ -94,9 +94,12 @@ macro_rules! sel { /// /// All arguments, and the return type, must implement [`Encode`]. /// -/// 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. +/// 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 currently not supported. /// @@ -127,10 +130,9 @@ macro_rules! sel { /// 1. The selector corresponds to a valid method that is available on the /// receiver. /// -/// 2. The argument types must match what the receiver excepts for this -/// selector. +/// 2. The argument types match what the receiver excepts for this selector. /// -/// 3. The return type must match what the receiver returns for this selector. +/// 3. The return type match what the receiver returns for this selector. /// /// 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 @@ -166,9 +168,9 @@ macro_rules! sel { /// # let obj: *mut Object = 0 as *mut Object; /// let description: *const Object = unsafe { msg_send![obj, description] }; /// // Usually you'd use msg_send_id here ^ -/// 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] }; +/// 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] }; /// ``` #[macro_export] macro_rules! msg_send { @@ -264,7 +266,7 @@ macro_rules! msg_send_bool { /// [`msg_send!`] for methods returning `id`, `NSObject*`, or similar object /// pointers. /// -/// Objective-C's object pointers have certain rules for when they should be +/// 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. @@ -334,10 +336,9 @@ macro_rules! msg_send_bool { /// pools! /// /// See [the clang documentation][arc-retainable] for the precise -/// specification. +/// 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. /// @@ -403,7 +404,7 @@ macro_rules! msg_send_id { }); } -/// Helper macro: To avoid exposing these in the docs for [`msg_send_id!`]. +/// Helper macro to avoid exposing these in the docs for [`msg_send_id!`]. #[doc(hidden)] #[macro_export] macro_rules! __msg_send_id_helper { diff --git a/objc2/src/rc/test_object.rs b/objc2/src/rc/test_object.rs index 90d66540e..6db124120 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -152,7 +152,7 @@ impl RcTestObject { } pub(crate) fn new() -> Id { - // Use msg_send! to test that; msg_send_id! is tested elsewhere! + // Use msg_send! - msg_send_id! is tested elsewhere! unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap() } } From 0e92a0a7918c2cfa620e16cb8e8f57e8bd45f0d2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 22:20:53 +0200 Subject: [PATCH 21/22] Fix cargo +nightly doc --- objc2/src/macros.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 99aeed7ee..9aaa25a5b 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -81,6 +81,9 @@ macro_rules! sel { /// 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 +/// /// /// # Specification /// @@ -157,6 +160,7 @@ macro_rules! sel { /// 8. TODO: Maybe more? /// /// [`autoreleasepool`]: crate::rc::autoreleasepool +/// [`msg_send_id!`]: crate::msg_send_id /// /// /// # Examples @@ -299,6 +303,8 @@ macro_rules! msg_send_bool { /// [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 /// From 726c6606fb511d8125f433c0b5eddb2e456673b7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jun 2022 22:34:55 +0200 Subject: [PATCH 22/22] Revert "Allow msg_send_id![obj, init] to non-option wrapped Id" It makes error messages and documentation worse (which _is_ a big deal), and I doubt it'll really be useful. Besides, we can always re-add it without breaking changes! --- objc2/src/__macro_helpers.rs | 10 ++--- objc2/src/macros.rs | 7 ++- tests/ui/msg_send_id_invalid_receiver.stderr | 46 +++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index 1ef6df989..e8a25c457 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -78,18 +78,16 @@ impl MsgSendId<&'_ Class, Id> } // `init`, should mark the input value as "allocated, not initialized" somehow -// -// The generic bound allows `init` to take both `Option` and `Id`. -impl>>, T: ?Sized + Message, O: Ownership> MsgSendId> +impl MsgSendId>, Id> for RetainSemantics { #[inline(always)] unsafe fn send_message_id( - obj: X, + obj: Option>, sel: Sel, args: A, ) -> Result>, MessageError> { - let ptr = Id::option_into_ptr(obj.into()); + 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. @@ -242,7 +240,7 @@ mod tests { expected.alloc += 1; // Check allocation error before init let obj = obj.unwrap(); - let _obj: Id = unsafe { msg_send_id![obj, init].unwrap() }; + let _obj: Id = unsafe { msg_send_id![Some(obj), init].unwrap() }; expected.init += 1; expected.assert_current(); } diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 9aaa25a5b..86509da17 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -325,10 +325,9 @@ macro_rules! msg_send_bool { /// - 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 either `Id` or -/// `Option>` as returned from `alloc`. The receiver is consumed, -/// and a the now-initialized `Option>` (with the same `T` and `O`) -/// is returned. +/// - 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>`. diff --git a/tests/ui/msg_send_id_invalid_receiver.stderr b/tests/ui/msg_send_id_invalid_receiver.stderr index 386b245d5..b20d72a68 100644 --- a/tests/ui/msg_send_id_invalid_receiver.stderr +++ b/tests/ui/msg_send_id_invalid_receiver.stderr @@ -32,33 +32,39 @@ note: associated function defined here | unsafe fn send_message_id( | ^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `Option>: From<&objc2::runtime::Object>` is not satisfied - --> ui/msg_send_id_invalid_receiver.rs:10:42 +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:10:55 | 10 | let _: Id = unsafe { msg_send_id![obj, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&objc2::runtime::Object>` is not implemented for `Option>` + | -------------^^^------- + | | | + | | expected enum `Option`, found `&objc2::runtime::Object` + | arguments to this function are incorrect | - = help: the following other types implement trait `From`: - as From<&'a Option>> - as From<&'a mut Option>> - as From> - = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Object` - = note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Object, Id<_, _>>` for `RetainSemantics` - = note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info) + = 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[E0277]: the trait bound `Option>: From<&objc2::runtime::Class>` is not satisfied - --> ui/msg_send_id_invalid_receiver.rs:13:42 +error[E0308]: mismatched types + --> ui/msg_send_id_invalid_receiver.rs:13:55 | 13 | let _: Id = unsafe { msg_send_id![cls, init].unwrap() }; - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&objc2::runtime::Class>` is not implemented for `Option>` + | -------------^^^------- + | | | + | | expected enum `Option`, found `&objc2::runtime::Class` + | arguments to this function are incorrect | - = help: the following other types implement trait `From`: - as From<&'a Option>> - as From<&'a mut Option>> - as From> - = note: required because of the requirements on the impl of `Into>>` for `&objc2::runtime::Class` - = 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) + = 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