From d90209d01227cd78665309088a84747eb477ceb0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 1 Feb 2023 12:16:48 +0100 Subject: [PATCH] Allow using `MainThreadMarker` in `extern_methods!` --- crates/objc2/CHANGELOG.md | 3 + crates/objc2/src/macros/__method_msg_send.rs | 50 ++++++++++++ crates/objc2/src/macros/extern_methods.rs | 6 ++ crates/objc2/tests/macros_mainthreadmarker.rs | 79 +++++++++++++++++++ .../test-ui/ui/extern_methods_invalid_type.rs | 8 ++ .../ui/extern_methods_invalid_type.stderr | 33 ++++++++ 6 files changed, 179 insertions(+) create mode 100644 crates/objc2/tests/macros_mainthreadmarker.rs diff --git a/crates/objc2/CHANGELOG.md b/crates/objc2/CHANGELOG.md index 769de3801..8606553af 100644 --- a/crates/objc2/CHANGELOG.md +++ b/crates/objc2/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD +### Added +* Allow using `MainThreadMarker` in `extern_methods!`. + ### Changed * Renamed `runtime` types: - `Object` to `AnyObject`. diff --git a/crates/objc2/src/macros/__method_msg_send.rs b/crates/objc2/src/macros/__method_msg_send.rs index 9182e9d92..be56c91b9 100644 --- a/crates/objc2/src/macros/__method_msg_send.rs +++ b/crates/objc2/src/macros/__method_msg_send.rs @@ -22,6 +22,30 @@ macro_rules! __method_msg_send { } }; + // Skip using `MainThreadMarker` in the message send. + // + // This is a purely textual match, and using e.g. + // `Foundation::MainThreadMarker` would fail - but that would just be + // detected as giving a wrong number of arguments, so it's fine for now. + ( + ($receiver:expr) + ($($sel_rest:tt)*) + ($arg:ident: MainThreadMarker $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ) => ({ + let _ = $arg; + $crate::__method_msg_send! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)*) + ($($arg_parsed)*) + } + }); + // Parse each argument-selector pair ( ($receiver:expr) @@ -173,6 +197,32 @@ macro_rules! __method_msg_send_id { } }; + // Skip using `MainThreadMarker` in the message send. + // + // This is a purely textual match, and using e.g. + // `Foundation::MainThreadMarker` would fail - but that would just be + // detected as giving a wrong number of arguments, so it's fine for now. + ( + ($receiver:expr) + ($($sel_rest:tt)*) + ($arg:ident: MainThreadMarker $(, $($args_rest:tt)*)?) + + ($($sel_parsed:tt)*) + ($($arg_parsed:tt)*) + ($($retain_semantics:ident)?) + ) => ({ + let _ = $arg; + $crate::__method_msg_send_id! { + ($receiver) + ($($sel_rest)*) + ($($($args_rest)*)?) + + ($($sel_parsed)*) + ($($arg_parsed)*) + ($($retain_semantics)?) + } + }); + // Parse each argument-selector pair ( ($receiver:expr) diff --git a/crates/objc2/src/macros/extern_methods.rs b/crates/objc2/src/macros/extern_methods.rs index 035e1fddf..dfea4bf56 100644 --- a/crates/objc2/src/macros/extern_methods.rs +++ b/crates/objc2/src/macros/extern_methods.rs @@ -26,6 +26,12 @@ /// [`Result`]. See the error section in [`msg_send!`] and [`msg_send_id!`] /// for details. /// +/// If you use `icrate::Foundation::MainThreadMarker` as a parameter type, the +/// macro will ignore it, allowing you to neatly specify "this method must be +/// run on the main thread". Note that due to type-system limitations, this is +/// currently a textual match on `MainThreadMarker`; so you must use that +/// exact identifier. +/// /// Putting other attributes on the method such as `cfg`, `allow`, `doc`, /// `deprecated` and so on is supported. However, note that `cfg_attr` may not /// work correctly, due to implementation difficulty - if you have a concrete diff --git a/crates/objc2/tests/macros_mainthreadmarker.rs b/crates/objc2/tests/macros_mainthreadmarker.rs new file mode 100644 index 000000000..546079dd0 --- /dev/null +++ b/crates/objc2/tests/macros_mainthreadmarker.rs @@ -0,0 +1,79 @@ +use objc2::rc::Id; +use objc2::runtime::{NSObject, NSObjectProtocol}; +use objc2::{declare_class, extern_methods, extern_protocol, mutability, ClassType, ProtocolType}; + +extern_protocol!( + #[allow(clippy::missing_safety_doc)] + unsafe trait Proto: NSObjectProtocol { + #[method(myMethod:)] + fn protocol_method(mtm: MainThreadMarker, arg: i32) -> i32; + + #[method_id(myMethodId:)] + fn protocol_method_id(mtm: MainThreadMarker, arg: &Self) -> Id; + } + + unsafe impl ProtocolType for dyn Proto { + const NAME: &'static str = "MainThreadMarkerTestProtocol"; + } +); + +declare_class!( + #[derive(PartialEq, Eq, Hash, Debug)] + struct Cls; + + unsafe impl ClassType for Cls { + type Super = NSObject; + type Mutability = mutability::InteriorMutable; + const NAME: &'static str = "MainThreadMarkerTest"; + } + + unsafe impl Proto for Cls { + #[method(myMethod:)] + fn _my_mainthreadonly_method(arg: i32) -> i32 { + arg + 1 + } + + #[method_id(myMethodId:)] + fn _my_mainthreadonly_method_id(arg: &Self) -> Id { + unsafe { Id::retain(arg as *const Self as *mut Self).unwrap() } + } + } +); + +unsafe impl NSObjectProtocol for Cls {} + +// The macro does a textual match; but when users actually use +// `icrate::Foundation::MainThreadMarker` to ensure soundness, they will not +// do this! +#[derive(Clone, Copy)] +struct MainThreadMarker(bool); + +extern_methods!( + unsafe impl Cls { + #[method_id(new)] + fn new() -> Id; + + #[method(myMethod:)] + fn method(mtm: MainThreadMarker, arg: i32, mtm2: MainThreadMarker) -> i32; + + #[method_id(myMethodId:)] + fn method_id(mtm: MainThreadMarker, arg: &Self, mtm2: MainThreadMarker) -> Id; + } +); + +#[test] +fn call() { + let obj1 = Cls::new(); + let mtm = MainThreadMarker(true); + + let res = Cls::method(mtm, 2, mtm); + assert_eq!(res, 3); + let res = Cls::protocol_method(mtm, 3); + assert_eq!(res, 4); + + let obj2 = Cls::method_id(mtm, &obj1, mtm); + assert_eq!(obj1, obj2); + + let obj2 = Cls::protocol_method_id(mtm, &obj1); + assert_eq!(obj1, obj2); +} diff --git a/crates/test-ui/ui/extern_methods_invalid_type.rs b/crates/test-ui/ui/extern_methods_invalid_type.rs index a6554d87f..70ea5a87f 100644 --- a/crates/test-ui/ui/extern_methods_invalid_type.rs +++ b/crates/test-ui/ui/extern_methods_invalid_type.rs @@ -1,3 +1,4 @@ +use icrate::Foundation::MainThreadMarker; use objc2::rc::Id; use objc2::runtime::NSObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; @@ -46,4 +47,11 @@ extern_methods!( } ); +extern_methods!( + unsafe impl MyObject { + #[method(mainThreadMarkerAsReturn)] + fn main_thread_marker_as_return() -> MainThreadMarker; + } +); + fn main() {} diff --git a/crates/test-ui/ui/extern_methods_invalid_type.stderr b/crates/test-ui/ui/extern_methods_invalid_type.stderr index 9d3008b49..befca79d5 100644 --- a/crates/test-ui/ui/extern_methods_invalid_type.stderr +++ b/crates/test-ui/ui/extern_methods_invalid_type.stderr @@ -132,3 +132,36 @@ note: required by a bound in `send_message_id` | unsafe fn send_message_id>( | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MsgSendId::send_message_id` = note: this error originates in the macro `$crate::__msg_send_id_helper` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `MainThreadMarker: Encode` is not satisfied + --> ui/extern_methods_invalid_type.rs + | + | / extern_methods!( + | | unsafe impl MyObject { + | | #[method(mainThreadMarkerAsReturn)] + | | fn main_thread_marker_as_return() -> MainThreadMarker; + | | } + | | ); + | |_^ the trait `Encode` is not implemented for `MainThreadMarker` + | + = help: the following other types implement trait `Encode`: + &'a T + &'a mut T + *const T + *const c_void + *mut T + *mut c_void + AtomicI16 + AtomicI32 + and $N others + = note: required for `MainThreadMarker` to implement `EncodeReturn` + = note: required for `MainThreadMarker` to implement `EncodeConvertReturn` +note: required by a bound in `send_message` + --> $WORKSPACE/crates/objc2/src/message/mod.rs + | + | unsafe fn send_message(self, sel: Sel, args: A) -> R + | ------------ required by a bound in this associated function +... + | R: EncodeConvertReturn, + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `MessageReceiver::send_message` + = note: this error originates in the macro `$crate::__msg_send_helper` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info)