Skip to content

Commit c5fd147

Browse files
authored
Merge pull request #354 from madsmtm/declare-class-method-id
Allow `#[method_id]` in `declare_class!`
2 parents 443aaf7 + e5e22d7 commit c5fd147

32 files changed

+2452
-772
lines changed

crates/objc2/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
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
@@ -19,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1921
### Fixed
2022
* Allow empty structs in `declare_class!` macro.
2123
* Allow using `extern_methods!` without the `ClassType` trait in scope.
24+
* Fixed a few small issues with `declare_class!`.
2225

2326

2427
## 0.3.0-beta.4 - 2022-12-24

crates/objc2/src/__macro_helpers.rs

Lines changed: 11 additions & 0 deletions
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
Lines changed: 113 additions & 0 deletions
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

Lines changed: 99 additions & 8 deletions
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

@@ -924,4 +969,50 @@ mod tests {
924969

925970
let _cls = Custom::class();
926971
}
972+
973+
// Proof-of-concept how we could make declare_class! accept generic.
974+
#[test]
975+
fn test_generic() {
976+
struct GenericDeclareClass<T>(T);
977+
978+
unsafe impl<T> RefEncode for GenericDeclareClass<T> {
979+
const ENCODING_REF: Encoding = Encoding::Object;
980+
}
981+
unsafe impl<T> Message for GenericDeclareClass<T> {}
982+
983+
unsafe impl<T> ClassType for GenericDeclareClass<T> {
984+
type Super = NSObject;
985+
const NAME: &'static str = "GenericDeclareClass";
986+
987+
#[inline]
988+
fn as_super(&self) -> &Self::Super {
989+
unimplemented!()
990+
}
991+
992+
#[inline]
993+
fn as_super_mut(&mut self) -> &mut Self::Super {
994+
unimplemented!()
995+
}
996+
997+
fn class() -> &'static Class {
998+
let superclass = NSObject::class();
999+
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();
1000+
1001+
unsafe {
1002+
builder.add_method(
1003+
sel!(generic),
1004+
<GenericDeclareClass<T>>::generic as unsafe extern "C" fn(_, _),
1005+
);
1006+
}
1007+
1008+
builder.register()
1009+
}
1010+
}
1011+
1012+
impl<T> GenericDeclareClass<T> {
1013+
extern "C" fn generic(&self, _cmd: Sel) {}
1014+
}
1015+
1016+
let _ = GenericDeclareClass::<()>::class();
1017+
}
9271018
}

0 commit comments

Comments
 (0)