Skip to content

Commit dcac8ce

Browse files
committed
Allow #[method_id(...)] to be used in declare_class!
1 parent ab5b696 commit dcac8ce

24 files changed

+1124
-112
lines changed

crates/objc2/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
* Support `#[cfg(...)]` attributes in `extern_class!` macro.
1111
* Added support for selectors with multiple colons like `abc::` in the `sel!`,
1212
`extern_class!`, `extern_protocol!` and `declare_class!` macros.
13+
* Added ability to use `#[method_id(mySelector:)]` inside `declare_class!`,
14+
just like you would do in `extern_methods!`.
1315

1416
### Changed
1517
* **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in

crates/objc2/src/__macro_helpers.rs

+11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub use core::{compile_error, concat, panic, stringify};
3030
// TODO: Use `core::cell::LazyCell`
3131
pub use std::sync::Once;
3232

33+
mod declare_class;
34+
35+
pub use self::declare_class::{MaybeOptionId, MessageRecieveId};
36+
3337
// Common selectors.
3438
//
3539
// These are put here to deduplicate the cached selector, and when using
@@ -850,6 +854,13 @@ mod tests {
850854
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, methodReturningNull] };
851855
}
852856

857+
#[test]
858+
#[should_panic = "unexpected NULL returned from -[__RcTestObject aMethod:]"]
859+
fn test_normal_with_param_and_null() {
860+
let obj = Id::into_shared(__RcTestObject::new());
861+
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, aMethod: false] };
862+
}
863+
853864
#[test]
854865
#[should_panic = "unexpected NULL description; receiver was NULL"]
855866
#[cfg(not(debug_assertions))] // Does NULL receiver checks
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use core::mem::ManuallyDrop;
2+
use core::ptr;
3+
4+
use crate::declare::__IdReturnValue;
5+
use crate::rc::{Allocated, Id, Ownership};
6+
use crate::{Message, MessageReceiver};
7+
8+
use super::{CopyOrMutCopy, Init, MaybeUnwrap, New, Other};
9+
10+
// One could imagine a different design where we simply had a method like
11+
// `fn convert_receiver()`, but that won't work in `declare_class!` since we
12+
// can't actually modify the `self` argument (e.g. `let self = foo(self)` is
13+
// not allowed).
14+
//
15+
// See `MsgSendId` and `RetainSemantics` for details on the retain semantics
16+
// we're following here.
17+
pub trait MessageRecieveId<Receiver, Ret> {
18+
fn into_return(obj: Ret) -> __IdReturnValue;
19+
}
20+
21+
// Receiver and return type have no correlation
22+
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for New
23+
where
24+
Receiver: MessageReceiver,
25+
Ret: MaybeOptionId,
26+
{
27+
#[inline]
28+
fn into_return(obj: Ret) -> __IdReturnValue {
29+
obj.consumed_return()
30+
}
31+
}
32+
33+
// Explicitly left unimplemented for now!
34+
// impl MessageRecieveId<impl MessageReceiver, Option<Allocated<T>>> for Alloc {}
35+
36+
// Note: `MethodImplementation` allows for `Allocated` as the receiver, so we
37+
// restrict it here to only be when the selector is `init`.
38+
//
39+
// Additionally, the receiver and return type must have the same generic
40+
// generic parameter `T`.
41+
impl<Ret, T, O> MessageRecieveId<Allocated<T>, Ret> for Init
42+
where
43+
T: Message,
44+
O: Ownership,
45+
Ret: MaybeOptionId<Input = Id<T, O>>,
46+
{
47+
#[inline]
48+
fn into_return(obj: Ret) -> __IdReturnValue {
49+
obj.consumed_return()
50+
}
51+
}
52+
53+
// Receiver and return type have no correlation
54+
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for CopyOrMutCopy
55+
where
56+
Receiver: MessageReceiver,
57+
Ret: MaybeOptionId,
58+
{
59+
#[inline]
60+
fn into_return(obj: Ret) -> __IdReturnValue {
61+
obj.consumed_return()
62+
}
63+
}
64+
65+
// Receiver and return type have no correlation
66+
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for Other
67+
where
68+
Receiver: MessageReceiver,
69+
Ret: MaybeOptionId,
70+
{
71+
#[inline]
72+
fn into_return(obj: Ret) -> __IdReturnValue {
73+
obj.autorelease_return()
74+
}
75+
}
76+
77+
/// Helper trait for specifying an `Id<T, O>` or an `Option<Id<T, O>>`.
78+
///
79+
/// (Both of those are valid return types from declare_class! `#[method_id]`).
80+
pub trait MaybeOptionId: MaybeUnwrap {
81+
fn consumed_return(self) -> __IdReturnValue;
82+
fn autorelease_return(self) -> __IdReturnValue;
83+
}
84+
85+
impl<T: Message, O: Ownership> MaybeOptionId for Id<T, O> {
86+
#[inline]
87+
fn consumed_return(self) -> __IdReturnValue {
88+
let ptr: *mut T = Id::consume_as_ptr(ManuallyDrop::new(self));
89+
__IdReturnValue(ptr.cast())
90+
}
91+
92+
#[inline]
93+
fn autorelease_return(self) -> __IdReturnValue {
94+
let ptr: *mut T = Id::autorelease_return(self);
95+
__IdReturnValue(ptr.cast())
96+
}
97+
}
98+
99+
impl<T: Message, O: Ownership> MaybeOptionId for Option<Id<T, O>> {
100+
#[inline]
101+
fn consumed_return(self) -> __IdReturnValue {
102+
let ptr: *mut T = self
103+
.map(|this| Id::consume_as_ptr(ManuallyDrop::new(this)))
104+
.unwrap_or_else(ptr::null_mut);
105+
__IdReturnValue(ptr.cast())
106+
}
107+
108+
#[inline]
109+
fn autorelease_return(self) -> __IdReturnValue {
110+
let ptr: *mut T = Id::autorelease_return_option(self);
111+
__IdReturnValue(ptr.cast())
112+
}
113+
}

crates/objc2/src/declare.rs

+53-8
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ use std::ffi::CString;
124124

125125
use crate::encode::{Encode, EncodeArguments, Encoding, RefEncode};
126126
use crate::ffi;
127+
use crate::rc::Allocated;
127128
use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel};
128129
use crate::sel;
129130
use crate::Message;
@@ -196,6 +197,37 @@ macro_rules! method_decl_impl {
196197
}
197198
}
198199
};
200+
(@<> Allocated<T>, $f:ty, $($t:ident),*) => {
201+
#[doc(hidden)]
202+
impl<T, $($t),*> private::Sealed for $f
203+
where
204+
T: Message + ?Sized,
205+
$($t: Encode,)*
206+
{}
207+
208+
#[doc(hidden)]
209+
impl<T, $($t),*> MethodImplementation for $f
210+
where
211+
T: Message + ?Sized,
212+
$($t: Encode,)*
213+
{
214+
type Callee = T;
215+
type Ret = __IdReturnValue;
216+
type Args = ($($t,)*);
217+
218+
fn __imp(self) -> Imp {
219+
// SAFETY: `Allocated<T>` is the same as `NonNull<T>`, except
220+
// with the assumption of a +1 calling convention.
221+
//
222+
// The calling convention is ensured to be upheld by having
223+
// `__IdReturnValue` in the type, since that type is private
224+
// and hence only internal macros like `#[method_id]` will be
225+
// able to produce it (and that, in turn, only allows it if
226+
// the selector is `init` as checked by `MessageRecieveId`).
227+
unsafe { mem::transmute(self) }
228+
}
229+
}
230+
};
199231
(# $abi:literal; $($t:ident),*) => {
200232
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
201233
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
@@ -207,6 +239,9 @@ macro_rules! method_decl_impl {
207239
method_decl_impl!(@<'a> Class, R, extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
208240
method_decl_impl!(@<> Class, R, unsafe extern $abi fn(*const Class, Sel $(, $t)*) -> R, $($t),*);
209241
method_decl_impl!(@<'a> Class, R, unsafe extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
242+
243+
method_decl_impl!(@<> Allocated<T>, extern $abi fn(Allocated<T>, Sel $(, $t)*) -> __IdReturnValue, $($t),*);
244+
method_decl_impl!(@<> Allocated<T>, unsafe extern $abi fn(Allocated<T>, Sel $(, $t)*) -> __IdReturnValue, $($t),*);
210245
};
211246
($($t:ident),*) => {
212247
method_decl_impl!(# "C"; $($t),*);
@@ -229,6 +264,17 @@ method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
229264
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
230265
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);
231266

267+
/// Helper type for implementing `MethodImplementation` with a receiver of
268+
/// `Allocated<T>`, without exposing that implementation to users.
269+
#[doc(hidden)]
270+
#[repr(transparent)]
271+
pub struct __IdReturnValue(pub(crate) *mut Object);
272+
273+
// SAFETY: `__IdReturnValue` is `#[repr(transparent)]`
274+
unsafe impl Encode for __IdReturnValue {
275+
const ENCODING: Encoding = <*mut Object>::ENCODING;
276+
}
277+
232278
fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
233279
// First two arguments are always self and the selector
234280
let mut types = format!("{ret}{}{}", <*mut Object>::ENCODING, Sel::ENCODING);
@@ -630,6 +676,7 @@ impl ProtocolBuilder {
630676
#[cfg(test)]
631677
mod tests {
632678
use super::*;
679+
use crate::rc::{Id, Shared};
633680
use crate::runtime::{NSObject, NSZone};
634681
use crate::test_utils;
635682
use crate::{declare_class, extern_protocol, msg_send, ClassType, ConformsTo, ProtocolType};
@@ -641,8 +688,8 @@ mod tests {
641688
const NAME: &'static str = "NSCopying";
642689

643690
#[allow(unused)]
644-
#[method(copyWithZone:)]
645-
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self;
691+
#[method_id(copyWithZone:)]
692+
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared>;
646693
}
647694
);
648695

@@ -814,9 +861,8 @@ mod tests {
814861
}
815862

816863
unsafe impl ConformsTo<NSCopyingObject> for Custom {
817-
#[method(copyWithZone:)]
818-
#[allow(unreachable_code)]
819-
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
864+
#[method_id(copyWithZone:)]
865+
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared> {
820866
unimplemented!()
821867
}
822868
}
@@ -910,9 +956,8 @@ mod tests {
910956
}
911957

912958
unsafe impl ConformsTo<NSCopyingObject> for Custom {
913-
#[method(copyWithZone:)]
914-
#[allow(unreachable_code)]
915-
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
959+
#[method_id(copyWithZone:)]
960+
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared> {
916961
unimplemented!()
917962
}
918963

crates/objc2/src/declare/declare_class_tests.rs

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![deny(deprecated)]
1+
#![deny(deprecated, unreachable_code)]
22
use core::ptr;
33

44
use crate::rc::{Id, Owned, Shared};
@@ -199,15 +199,15 @@ declare_class!(
199199
true
200200
}
201201

202-
#[method(test:::withObject:)]
202+
#[method_id(test:::withObject:)]
203203
fn _test_object(
204204
&self,
205205
_arg1: i32,
206206
_arg2: i32,
207207
_arg3: i32,
208208
_obj: *const Self,
209-
) -> *const Self {
210-
ptr::null()
209+
) -> Option<Id<Self, Owned>> {
210+
None
211211
}
212212
}
213213
);
@@ -290,6 +290,16 @@ declare_class!(
290290
fn takes_returns_bool_instance(&self, b: bool) -> bool {
291291
b
292292
}
293+
294+
#[method_id(idTakesBool:)]
295+
fn id_takes_bool(_b: bool) -> Option<Id<Self, Owned>> {
296+
None
297+
}
298+
299+
#[method_id(idTakesBoolInstance:)]
300+
fn id_takes_bool_instance(&self, _b: bool) -> Option<Id<Self, Owned>> {
301+
None
302+
}
293303
}
294304
);
295305

@@ -326,6 +336,16 @@ declare_class!(
326336
fn unreachable_class_void() {
327337
unreachable!()
328338
}
339+
340+
#[method_id(unreachableId)]
341+
fn unreachable_id(&self) -> Id<Self, Owned> {
342+
unreachable!()
343+
}
344+
345+
#[method_id(unreachableClassId)]
346+
fn unreachable_class_id() -> Id<Self, Owned> {
347+
unreachable!()
348+
}
329349
}
330350
);
331351

crates/objc2/src/macros.rs

+9
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@ macro_rules! __sel_helper {
245245
} => ({
246246
$crate::__sel_data!($($parsed_sel)*)
247247
});
248+
// Single identifier
249+
{
250+
@()
251+
$ident:ident
252+
} => {
253+
$crate::__sel_helper! {
254+
@($ident)
255+
}
256+
};
248257
// Parse identitifer + colon token
249258
{
250259
@($($parsed_sel:tt)*)

0 commit comments

Comments
 (0)