Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using MainThreadMarker in extern_methods! #416

Merged
merged 1 commit into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
50 changes: 50 additions & 0 deletions crates/objc2/src/macros/__method_msg_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions crates/objc2/src/macros/extern_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 79 additions & 0 deletions crates/objc2/tests/macros_mainthreadmarker.rs
Original file line number Diff line number Diff line change
@@ -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<Self>;
}

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<Self> {
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<Self>;

#[method(myMethod:)]
fn method(mtm: MainThreadMarker, arg: i32, mtm2: MainThreadMarker) -> i32;

#[method_id(myMethodId:)]
fn method_id(mtm: MainThreadMarker, arg: &Self, mtm2: MainThreadMarker) -> Id<Self>;
}
);

#[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);
}
8 changes: 8 additions & 0 deletions crates/test-ui/ui/extern_methods_invalid_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use objc2::runtime::NSObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
Expand Down Expand Up @@ -46,4 +47,11 @@ extern_methods!(
}
);

extern_methods!(
unsafe impl MyObject {
#[method(mainThreadMarkerAsReturn)]
fn main_thread_marker_as_return() -> MainThreadMarker;
}
);

fn main() {}
33 changes: 33 additions & 0 deletions crates/test-ui/ui/extern_methods_invalid_type.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,36 @@ note: required by a bound in `send_message_id`
| unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = U>>(
| ^^^^^^^^^^^^^^^^^^^^^^ 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<A, R>(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)
8 changes: 8 additions & 0 deletions crates/test-ui/ui/extern_methods_wrong_arguments_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use objc2::runtime::NSObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
Expand Down Expand Up @@ -39,4 +40,11 @@ extern_methods!(
}
);

extern_methods!(
unsafe impl MyObject {
#[method(tooFew:withMtm:_)]
fn too_few_with_mtm(&self, mtm: MainThreadMarker) -> Result<(), Id<NSObject>>;
}
);

fn main() {}
13 changes: 13 additions & 0 deletions crates/test-ui/ui/extern_methods_wrong_arguments_error.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,16 @@ error: number of arguments in function and selector did not match
| |_^
|
= note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info)

error: number of arguments in function and selector did not match
--> ui/extern_methods_wrong_arguments_error.rs
|
| / extern_methods!(
| | unsafe impl MyObject {
| | #[method(tooFew:withMtm:_)]
| | fn too_few_with_mtm(&self, mtm: MainThreadMarker) -> Result<(), Id<NSObject>>;
| | }
| | );
| |_^
|
= note: this error originates in the macro `$crate::__method_msg_send` which comes from the expansion of the macro `extern_methods` (in Nightly builds, run with -Z macro-backtrace for more info)