diff --git a/objc2-foundation/src/object.rs b/objc2-foundation/src/object.rs index b844675da..ea73af139 100644 --- a/objc2-foundation/src/object.rs +++ b/objc2-foundation/src/object.rs @@ -1,6 +1,6 @@ -use objc2::msg_send; use objc2::rc::{DefaultId, Id, Owned, Shared}; -use objc2::runtime::{Bool, Class, Object}; +use objc2::runtime::{Class, Object}; +use objc2::{msg_send, msg_send_bool}; use super::NSString; @@ -17,8 +17,7 @@ impl NSObject { } pub fn is_equal(&self, other: &NSObject) -> bool { - let result: Bool = unsafe { msg_send![self, isEqual: other] }; - result.as_bool() + unsafe { msg_send_bool![self, isEqual: other] } } pub fn description(&self) -> Id { @@ -30,8 +29,7 @@ impl NSObject { } pub fn is_kind_of(&self, cls: &Class) -> bool { - let result: Bool = unsafe { msg_send![self, isKindOfClass: cls] }; - result.as_bool() + unsafe { msg_send_bool![self, isKindOfClass: cls] } } } diff --git a/objc2-foundation/src/string.rs b/objc2-foundation/src/string.rs index 49572b6ce..2e401301c 100644 --- a/objc2-foundation/src/string.rs +++ b/objc2-foundation/src/string.rs @@ -8,11 +8,11 @@ use std::os::raw::c_char; use alloc::borrow::ToOwned; use objc2::ffi; -use objc2::msg_send; use objc2::rc::DefaultId; use objc2::rc::{autoreleasepool, AutoreleasePool}; use objc2::rc::{Id, Shared}; -use objc2::runtime::{Bool, Class, Object}; +use objc2::runtime::{Class, Object}; +use objc2::{msg_send, msg_send_bool}; use crate::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject}; @@ -182,8 +182,7 @@ impl NSString { #[doc(alias = "hasPrefix")] #[doc(alias = "hasPrefix:")] pub fn has_prefix(&self, prefix: &NSString) -> bool { - let res: Bool = unsafe { msg_send![self, hasPrefix: prefix] }; - res.is_true() + unsafe { msg_send_bool![self, hasPrefix: prefix] } } /// Whether the given string matches the ending characters of this string. @@ -192,8 +191,7 @@ impl NSString { #[doc(alias = "hasSuffix")] #[doc(alias = "hasSuffix:")] pub fn has_suffix(&self, suffix: &NSString) -> bool { - let res: Bool = unsafe { msg_send![self, hasSuffix: suffix] }; - res.is_true() + unsafe { msg_send_bool![self, hasSuffix: suffix] } } // pub fn from_nsrange(range: NSRange) -> Id diff --git a/objc2-foundation/src/thread.rs b/objc2-foundation/src/thread.rs index 6ea3393d1..76b1470c5 100644 --- a/objc2-foundation/src/thread.rs +++ b/objc2-foundation/src/thread.rs @@ -1,6 +1,5 @@ -use objc2::msg_send; use objc2::rc::{Id, Shared}; -use objc2::runtime::Bool; +use objc2::{msg_send, msg_send_bool}; use crate::{NSObject, NSString}; @@ -34,8 +33,7 @@ impl NSThread { /// Returns `true` if the thread is the main thread. pub fn is_main(&self) -> bool { - let res: Bool = unsafe { msg_send![self, isMainThread] }; - res.is_true() + unsafe { msg_send_bool![self, isMainThread] } } /// The name of the thread. @@ -47,14 +45,12 @@ impl NSThread { /// Whether the application is multithreaded according to Cocoa. pub fn is_multi_threaded() -> bool { - let res: Bool = unsafe { msg_send![NSThread::class(), isMultiThreaded] }; - res.is_true() + unsafe { msg_send_bool![NSThread::class(), isMultiThreaded] } } /// Whether the current thread is the main thread. pub fn is_main_thread() -> bool { - let res: Bool = unsafe { msg_send![NSThread::class(), isMainThread] }; - res.is_true() + unsafe { msg_send_bool![NSThread::class(), isMainThread] } } #[cfg(test)] diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index 88b6fdcce..7155d1ada 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * The `objc2-encode` dependency is now exposed as `objc2::encode`. * Added `Id::retain_autoreleased` to allow following Cocoas memory management rules more efficiently. +* Consistently allow trailing commas in `msg_send!`. +* Added `msg_send_bool!`, a less error-prone version of `msg_send!` for + Objective-C methods that return `BOOL`. ### Changed * **BREAKING**: Changed signature of `Id::new` and `Id::retain` from diff --git a/objc2/src/bool.rs b/objc2/src/bool.rs index e6b94a740..d8e69dd9e 100644 --- a/objc2/src/bool.rs +++ b/objc2/src/bool.rs @@ -16,11 +16,10 @@ use core::fmt; /// # Example /// /// ```no_run -/// use objc2::{class, msg_send}; +/// use objc2::{class, msg_send, msg_send_bool}; /// use objc2::runtime::{Object, Bool}; /// let ns_value: *mut Object = unsafe { msg_send![class!(NSValue), initWithBool: Bool::YES] }; -/// let rtn: Bool = unsafe { msg_send![ns_value, boolValue] }; -/// assert!(rtn.as_bool()); +/// 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/lib.rs b/objc2/src/lib.rs index e9c13a896..94879faa6 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -29,7 +29,7 @@ //! #![cfg_attr(apple, doc = "```")] #![cfg_attr(not(apple), doc = "```no_run")] -//! use objc2::{class, msg_send}; +//! use objc2::{class, msg_send, msg_send_bool}; //! use objc2::ffi::NSUInteger; //! use objc2::rc::{Id, Owned}; //! use objc2::runtime::{Bool, Object}; @@ -43,8 +43,8 @@ //! //! // Usage //! let hash: NSUInteger = unsafe { msg_send![obj, hash] }; -//! let is_kind: Bool = unsafe { msg_send![obj, isKindOfClass: cls] }; -//! assert!(is_kind.as_bool()); +//! let is_kind = unsafe { msg_send_bool![obj, isKindOfClass: cls] }; +//! assert!(is_kind); //! ``` //! //! Note that this very simple example contains **a lot** of `unsafe` (which diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index f29b9af4a..6d9ea7356 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -44,15 +44,9 @@ macro_rules! class { /// ``` #[macro_export] macro_rules! sel { - ($name:ident) => ({ + ($first:ident $(: $($rest:ident :)*)?) => ({ static SEL: $crate::__CachedSel = $crate::__CachedSel::new(); - let name = concat!(stringify!($name), '\0'); - #[allow(unused_unsafe)] - unsafe { SEL.get(name) } - }); - ($($name:ident :)+) => ({ - static SEL: $crate::__CachedSel = $crate::__CachedSel::new(); - let name = concat!($(stringify!($name), ':'),+, '\0'); + let name = concat!(stringify!($first), $(':', $(stringify!($rest), ':',)*)? '\0'); #[allow(unused_unsafe)] unsafe { SEL.get(name) } }); @@ -135,8 +129,8 @@ macro_rules! sel { /// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html #[macro_export] macro_rules! msg_send { - (super($obj:expr, $superclass:expr), $name:ident) => ({ - let sel = $crate::sel!($name); + [super($obj:expr, $superclass:expr), $selector:ident $(,)?] => ({ + let sel = $crate::sel!($selector); let result; match $crate::MessageReceiver::send_super_message(&$obj, $superclass, sel, ()) { Err(s) => panic!("{}", s), @@ -144,17 +138,17 @@ macro_rules! msg_send { } result }); - (super($obj:expr, $superclass:expr), $($name:ident : $arg:expr $(,)?)+) => ({ - let sel = $crate::sel!($($name:)+); + [super($obj:expr, $superclass:expr), $($selector:ident : $argument:expr $(,)?)+] => ({ + let sel = $crate::sel!($($selector :)+); let result; - match $crate::MessageReceiver::send_super_message(&$obj, $superclass, sel, ($($arg,)+)) { + match $crate::MessageReceiver::send_super_message(&$obj, $superclass, sel, ($($argument,)+)) { Err(s) => panic!("{}", s), Ok(r) => result = r, } result }); - ($obj:expr, $name:ident) => ({ - let sel = $crate::sel!($name); + [$obj:expr, $selector:ident $(,)?] => ({ + let sel = $crate::sel!($selector); let result; match $crate::MessageReceiver::send_message(&$obj, sel, ()) { Err(s) => panic!("{}", s), @@ -162,13 +156,49 @@ macro_rules! msg_send { } result }); - ($obj:expr, $($name:ident : $arg:expr $(,)?)+) => ({ - let sel = $crate::sel!($($name:)+); + [$obj:expr, $($selector:ident : $argument:expr $(,)?)+] => ({ + let sel = $crate::sel!($($selector :)+); let result; - match $crate::MessageReceiver::send_message(&$obj, sel, ($($arg,)+)) { + match $crate::MessageReceiver::send_message(&$obj, sel, ($($argument,)+)) { Err(s) => panic!("{}", s), Ok(r) => result = r, } result }); } + +/// A less error-prone version of [`msg_send!`] for methods returning `BOOL`. +/// +/// 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! +/// +/// [`Bool`]: crate::runtime::Bool +/// +/// Equivalent to the following: +/// +/// ```ignore +/// # use objc2::msg_send; +/// # use objc2::runtime::Bool; +/// # let obj: *mut Object = 0 as *mut Object; +/// { +/// let result: Bool = msg_send![obj, selector]; +/// result.as_bool() +/// }; +/// ``` +/// +/// # Examples +/// +/// ```no_run +/// # use objc2::msg_send_bool; +/// # use objc2::runtime::Object; +/// # let obj: *mut Object = 0 as *mut Object; +/// assert!(unsafe { msg_send_bool![obj, isEqual: obj] }); +/// ``` +#[macro_export] +macro_rules! msg_send_bool { + [$($msg_send_args:tt)+] => ({ + let result: $crate::runtime::Bool = $crate::msg_send![$($msg_send_args)+]; + result.as_bool() + }); +} diff --git a/objc2/src/runtime.rs b/objc2/src/runtime.rs index 7934fb0f1..7bf2d5ade 100644 --- a/objc2/src/runtime.rs +++ b/objc2/src/runtime.rs @@ -170,8 +170,7 @@ impl Ivar { /// Returns the offset of self. pub fn offset(&self) -> isize { - let offset = unsafe { ffi::ivar_getOffset(self.as_ptr()) }; - offset as isize + unsafe { ffi::ivar_getOffset(self.as_ptr()) } } /// Returns the `Encoding` of self. @@ -627,6 +626,7 @@ mod tests { test_sel!("abc", abc); test_sel!("abc:", abc:); test_sel!("abc:def:", abc:def:); + test_sel!("abc:def:ghi:", abc:def:ghi:); } #[test] diff --git a/objc2/tests/use_macros.rs b/objc2/tests/use_macros.rs index 10dae5ae0..4a36889e0 100644 --- a/objc2/tests/use_macros.rs +++ b/objc2/tests/use_macros.rs @@ -1,4 +1,4 @@ -use objc2::runtime::Object; +use objc2::runtime::{Class, Object}; use objc2::{class, msg_send, sel}; #[cfg(gnustep)] @@ -22,3 +22,28 @@ fn use_sel() { let _sel = sel!(description); let _sel = sel!(setObject:forKey:); } + +#[allow(unused)] +fn test_msg_send_comma_handling(obj: &Object, superclass: &Class) { + unsafe { + let _: () = msg_send![obj, a]; + let _: () = msg_send![obj, a,]; + let _: () = msg_send![obj, a: 32i32]; + let _: () = msg_send![obj, a: 32i32,]; + let _: () = msg_send![obj, a: 32i32 b: 32i32]; + let _: () = msg_send![obj, a: 32i32 b: 32i32,]; + let _: () = msg_send![obj, a: 32i32, b: 32i32]; + let _: () = msg_send![obj, a: 32i32, b: 32i32,]; + } + + unsafe { + let _: () = msg_send![super(obj, superclass), a]; + let _: () = msg_send![super(obj, superclass), a,]; + let _: () = msg_send![super(obj, superclass), a: 32i32]; + let _: () = msg_send![super(obj, superclass), a: 32i32,]; + let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32]; + let _: () = msg_send![super(obj, superclass), a: 32i32 b: 32i32,]; + let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32]; + let _: () = msg_send![super(obj, superclass), a: 32i32, b: 32i32,]; + } +} diff --git a/tests/ui/invalid_msg_send.rs b/tests/ui/invalid_msg_send.rs new file mode 100644 index 000000000..21e493c3c --- /dev/null +++ b/tests/ui/invalid_msg_send.rs @@ -0,0 +1,16 @@ +//! Test invalid msg_send syntax +use objc2::msg_send; +use objc2::runtime::Object; + +fn main() { + let obj: &Object; + let b = 32i32; + let d = 32i32; + let _: () = unsafe { msg_send![obj] }; + let _: () = unsafe { msg_send![obj,] }; + let _: () = unsafe { msg_send![obj, a:] }; + let _: () = unsafe { msg_send![obj, a: b c] }; + let _: () = unsafe { msg_send![obj, a: b: c] }; + let _: () = unsafe { msg_send![obj, a: b, c d] }; + let _: () = unsafe { msg_send![obj, a: b: c] }; +} diff --git a/tests/ui/invalid_msg_send.stderr b/tests/ui/invalid_msg_send.stderr new file mode 100644 index 000000000..58e14b638 --- /dev/null +++ b/tests/ui/invalid_msg_send.stderr @@ -0,0 +1,41 @@ +error: unexpected end of macro invocation + --> ui/invalid_msg_send.rs:9:39 + | +9 | let _: () = unsafe { msg_send![obj] }; + | ^ missing tokens in macro arguments + +error: unexpected end of macro invocation + --> ui/invalid_msg_send.rs:10:40 + | +10 | let _: () = unsafe { msg_send![obj,] }; + | ^ missing tokens in macro arguments + +error: unexpected end of macro invocation + --> ui/invalid_msg_send.rs:11:43 + | +11 | let _: () = unsafe { msg_send![obj, a:] }; + | ^ missing tokens in macro arguments + +error: unexpected end of macro invocation + --> ui/invalid_msg_send.rs:12:47 + | +12 | let _: () = unsafe { msg_send![obj, a: b c] }; + | ^ missing tokens in macro arguments + +error: no rules expected the token `d` + --> ui/invalid_msg_send.rs:14:49 + | +14 | let _: () = unsafe { msg_send![obj, a: b, c d] }; + | ^ no rules expected this token in macro call + +error[E0412]: cannot find type `c` in this scope + --> ui/invalid_msg_send.rs:13:47 + | +13 | let _: () = unsafe { msg_send![obj, a: b: c] }; + | ^ expecting a type here because of type ascription + +error[E0412]: cannot find type `c` in this scope + --> ui/invalid_msg_send.rs:15:47 + | +15 | let _: () = unsafe { msg_send![obj, a: b: c] }; + | ^ expecting a type here because of type ascription diff --git a/tests/ui/invalid_msg_send_super.rs b/tests/ui/invalid_msg_send_super.rs new file mode 100644 index 000000000..8d83a5b74 --- /dev/null +++ b/tests/ui/invalid_msg_send_super.rs @@ -0,0 +1,14 @@ +//! Test invalid msg_send![super(...)] syntax +use objc2::msg_send; +use objc2::runtime::{Class, Object}; + +fn main() { + let obj: &Object; + let superclass: &Class; + + let _: () = unsafe { msg_send![super, init] }; + let _: () = unsafe { msg_send![super(), init] }; + let _: () = unsafe { msg_send![super(obj), init] }; + let _: () = unsafe { msg_send![super(obj,), init] }; + let _: () = unsafe { msg_send![super(obj, superclass,), init] }; +} diff --git a/tests/ui/invalid_msg_send_super.stderr b/tests/ui/invalid_msg_send_super.stderr new file mode 100644 index 000000000..b00eb229a --- /dev/null +++ b/tests/ui/invalid_msg_send_super.stderr @@ -0,0 +1,29 @@ +error[E0433]: failed to resolve: there are too many leading `super` keywords + --> ui/invalid_msg_send_super.rs:9:36 + | +9 | let _: () = unsafe { msg_send![super, init] }; + | ^^^^^ there are too many leading `super` keywords + +error[E0433]: failed to resolve: there are too many leading `super` keywords + --> ui/invalid_msg_send_super.rs:10:36 + | +10 | let _: () = unsafe { msg_send![super(), init] }; + | ^^^^^ there are too many leading `super` keywords + +error[E0433]: failed to resolve: there are too many leading `super` keywords + --> ui/invalid_msg_send_super.rs:11:36 + | +11 | let _: () = unsafe { msg_send![super(obj), init] }; + | ^^^^^ there are too many leading `super` keywords + +error[E0433]: failed to resolve: there are too many leading `super` keywords + --> ui/invalid_msg_send_super.rs:12:36 + | +12 | let _: () = unsafe { msg_send![super(obj,), init] }; + | ^^^^^ there are too many leading `super` keywords + +error[E0433]: failed to resolve: there are too many leading `super` keywords + --> ui/invalid_msg_send_super.rs:13:36 + | +13 | let _: () = unsafe { msg_send![super(obj, superclass,), init] }; + | ^^^^^ there are too many leading `super` keywords diff --git a/tests/ui/invalid_sel.rs b/tests/ui/invalid_sel.rs new file mode 100644 index 000000000..7617215a4 --- /dev/null +++ b/tests/ui/invalid_sel.rs @@ -0,0 +1,8 @@ +//! Test invalid selector syntax +use objc2::sel; + +fn main() { + sel!(); + sel!(a: b); + sel!(a: b: c); +} diff --git a/tests/ui/invalid_sel.stderr b/tests/ui/invalid_sel.stderr new file mode 100644 index 000000000..33ed31d45 --- /dev/null +++ b/tests/ui/invalid_sel.stderr @@ -0,0 +1,17 @@ +error: unexpected end of macro invocation + --> ui/invalid_sel.rs:5:5 + | +5 | sel!(); + | ^^^^^^ missing tokens in macro arguments + +error: unexpected end of macro invocation + --> ui/invalid_sel.rs:6:14 + | +6 | sel!(a: b); + | ^ missing tokens in macro arguments + +error: unexpected end of macro invocation + --> ui/invalid_sel.rs:7:17 + | +7 | sel!(a: b: c); + | ^ missing tokens in macro arguments