Skip to content

Commit

Permalink
Allow #[method_id(...)] to be used in declare_class!
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jan 18, 2023
1 parent ab5b696 commit 4997924
Show file tree
Hide file tree
Showing 24 changed files with 1,122 additions and 112 deletions.
2 changes: 2 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Support `#[cfg(...)]` attributes in `extern_class!` macro.
* Added support for selectors with multiple colons like `abc::` in the `sel!`,
`extern_class!`, `extern_protocol!` and `declare_class!` macros.
* Added ability to use `#[method_id(mySelector:)]` inside `declare_class!`,
just like you would do in `extern_methods!`.

### Changed
* **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in
Expand Down
11 changes: 11 additions & 0 deletions crates/objc2/src/__macro_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub use core::{compile_error, concat, panic, stringify};
// TODO: Use `core::cell::LazyCell`
pub use std::sync::Once;

mod declare_class;

pub use self::declare_class::{MaybeOptionId, MessageRecieveId};

// Common selectors.
//
// These are put here to deduplicate the cached selector, and when using
Expand Down Expand Up @@ -850,6 +854,13 @@ mod tests {
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, methodReturningNull] };
}

#[test]
#[should_panic = "unexpected NULL returned from -[__RcTestObject aMethod:]"]
fn test_normal_with_param_and_null() {
let obj = Id::into_shared(__RcTestObject::new());
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, aMethod: false] };
}

#[test]
#[should_panic = "unexpected NULL description; receiver was NULL"]
#[cfg(not(debug_assertions))] // Does NULL receiver checks
Expand Down
113 changes: 113 additions & 0 deletions crates/objc2/src/__macro_helpers/declare_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use core::mem::ManuallyDrop;
use core::ptr;

use crate::declare::__IdReturnValue;
use crate::rc::{Allocated, Id, Ownership};
use crate::{Message, MessageReceiver};

use super::{CopyOrMutCopy, Init, MaybeUnwrap, New, Other};

// One could imagine a different design where we simply had a method like
// `fn convert_receiver()`, but that won't work in `declare_class!` since we
// can't actually modify the `self` argument (e.g. `let self = foo(self)` is
// not allowed).
//
// See `MsgSendId` and `RetainSemantics` for details on the retain semantics
// we're following here.
pub trait MessageRecieveId<Receiver, Ret> {
fn into_return(obj: Ret) -> __IdReturnValue;
}

// Receiver and return type have no correlation
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for New
where
Receiver: MessageReceiver,
Ret: MaybeOptionId,
{
#[inline]
fn into_return(obj: Ret) -> __IdReturnValue {
obj.consumed_return()
}
}

// Explicitly left unimplemented for now!
// impl MessageRecieveId<impl MessageReceiver, Option<Allocated<T>>> for Alloc {}

// Note: `MethodImplementation` allows for `Allocated` as the receiver, so we
// restrict it here to only be when the selector is `init`.
//
// Additionally, the receiver and return type must have the same generic
// generic parameter `T`.
impl<Ret, T, O> MessageRecieveId<Allocated<T>, Ret> for Init
where
T: Message,
O: Ownership,
Ret: MaybeOptionId<Input = Id<T, O>>,
{
#[inline]
fn into_return(obj: Ret) -> __IdReturnValue {
obj.consumed_return()
}
}

// Receiver and return type have no correlation
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for CopyOrMutCopy
where
Receiver: MessageReceiver,
Ret: MaybeOptionId,
{
#[inline]
fn into_return(obj: Ret) -> __IdReturnValue {
obj.consumed_return()
}
}

// Receiver and return type have no correlation
impl<Receiver, Ret> MessageRecieveId<Receiver, Ret> for Other
where
Receiver: MessageReceiver,
Ret: MaybeOptionId,
{
#[inline]
fn into_return(obj: Ret) -> __IdReturnValue {
obj.autorelease_return()
}
}

/// Helper trait for specifying an `Id<T, O>` or an `Option<Id<T, O>>`.
///
/// (Both of those are valid return types from declare_class! `#[method_id]`).
pub trait MaybeOptionId: MaybeUnwrap {
fn consumed_return(self) -> __IdReturnValue;
fn autorelease_return(self) -> __IdReturnValue;
}

impl<T: Message, O: Ownership> MaybeOptionId for Id<T, O> {
#[inline]
fn consumed_return(self) -> __IdReturnValue {
let ptr: *mut T = Id::consume_as_ptr(ManuallyDrop::new(self));
__IdReturnValue(ptr.cast())
}

#[inline]
fn autorelease_return(self) -> __IdReturnValue {
let ptr: *mut T = Id::autorelease_return(self);
__IdReturnValue(ptr.cast())
}
}

impl<T: Message, O: Ownership> MaybeOptionId for Option<Id<T, O>> {
#[inline]
fn consumed_return(self) -> __IdReturnValue {
let ptr: *mut T = self
.map(|this| Id::consume_as_ptr(ManuallyDrop::new(this)))
.unwrap_or_else(ptr::null_mut);
__IdReturnValue(ptr.cast())
}

#[inline]
fn autorelease_return(self) -> __IdReturnValue {
let ptr: *mut T = Id::autorelease_return_option(self);
__IdReturnValue(ptr.cast())
}
}
61 changes: 53 additions & 8 deletions crates/objc2/src/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ use std::ffi::CString;

use crate::encode::{Encode, EncodeArguments, Encoding, RefEncode};
use crate::ffi;
use crate::rc::Allocated;
use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel};
use crate::sel;
use crate::Message;
Expand Down Expand Up @@ -196,6 +197,37 @@ macro_rules! method_decl_impl {
}
}
};
(@<> Allocated<T>, $f:ty, $($t:ident),*) => {
#[doc(hidden)]
impl<T, $($t),*> private::Sealed for $f
where
T: Message + ?Sized,
$($t: Encode,)*
{}

#[doc(hidden)]
impl<T, $($t),*> MethodImplementation for $f
where
T: Message + ?Sized,
$($t: Encode,)*
{
type Callee = T;
type Ret = __IdReturnValue;
type Args = ($($t,)*);

fn __imp(self) -> Imp {
// SAFETY: `Allocated<T>` is the same as `NonNull<T>`, except
// with the assumption of a +1 calling convention.
//
// The calling convention is ensured to be upheld by having
// `__IdReturnValue` in the type, since that type is private
// and hence only internal macros like `#[method_id]` will be
// able to produce it (and that, in turn, only allows it if
// the selector is `init` as checked by `MessageRecieveId`).
unsafe { mem::transmute(self) }
}
}
};
(# $abi:literal; $($t:ident),*) => {
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> T, R, extern $abi fn(&'a mut T, Sel $(, $t)*) -> R, $($t),*);
Expand All @@ -207,6 +239,9 @@ macro_rules! method_decl_impl {
method_decl_impl!(@<'a> Class, R, extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<> Class, R, unsafe extern $abi fn(*const Class, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(@<'a> Class, R, unsafe extern $abi fn(&'a Class, Sel $(, $t)*) -> R, $($t),*);

method_decl_impl!(@<> Allocated<T>, extern $abi fn(Allocated<T>, Sel $(, $t)*) -> __IdReturnValue, $($t),*);
method_decl_impl!(@<> Allocated<T>, unsafe extern $abi fn(Allocated<T>, Sel $(, $t)*) -> __IdReturnValue, $($t),*);
};
($($t:ident),*) => {
method_decl_impl!(# "C"; $($t),*);
Expand All @@ -229,6 +264,17 @@ method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);

/// Helper type for implementing `MethodImplementation` with a receiver of
/// `Allocated<T>`, without exposing that implementation to users.
#[doc(hidden)]
#[repr(transparent)]
pub struct __IdReturnValue(pub(crate) *mut Object);

// SAFETY: `__IdReturnValue` is `#[repr(transparent)]`
unsafe impl Encode for __IdReturnValue {
const ENCODING: Encoding = <*mut Object>::ENCODING;
}

fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
// First two arguments are always self and the selector
let mut types = format!("{ret}{}{}", <*mut Object>::ENCODING, Sel::ENCODING);
Expand Down Expand Up @@ -630,6 +676,7 @@ impl ProtocolBuilder {
#[cfg(test)]
mod tests {
use super::*;
use crate::rc::{Id, Shared};
use crate::runtime::{NSObject, NSZone};
use crate::test_utils;
use crate::{declare_class, extern_protocol, msg_send, ClassType, ConformsTo, ProtocolType};
Expand All @@ -641,8 +688,8 @@ mod tests {
const NAME: &'static str = "NSCopying";

#[allow(unused)]
#[method(copyWithZone:)]
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self;
#[method_id(copyWithZone:)]
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared>;
}
);

Expand Down Expand Up @@ -814,9 +861,8 @@ mod tests {
}

unsafe impl ConformsTo<NSCopyingObject> for Custom {
#[method(copyWithZone:)]
#[allow(unreachable_code)]
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
#[method_id(copyWithZone:)]
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared> {
unimplemented!()
}
}
Expand Down Expand Up @@ -910,9 +956,8 @@ mod tests {
}

unsafe impl ConformsTo<NSCopyingObject> for Custom {
#[method(copyWithZone:)]
#[allow(unreachable_code)]
fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
#[method_id(copyWithZone:)]
fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Shared> {
unimplemented!()
}

Expand Down
28 changes: 24 additions & 4 deletions crates/objc2/src/declare/declare_class_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![deny(deprecated)]
#![deny(deprecated, unreachable_code)]
use core::ptr;

use crate::rc::{Id, Owned, Shared};
Expand Down Expand Up @@ -199,15 +199,15 @@ declare_class!(
true
}

#[method(test:::withObject:)]
#[method_id(test:::withObject:)]
fn _test_object(
&self,
_arg1: i32,
_arg2: i32,
_arg3: i32,
_obj: *const Self,
) -> *const Self {
ptr::null()
) -> Option<Id<Self, Owned>> {
None
}
}
);
Expand Down Expand Up @@ -290,6 +290,16 @@ declare_class!(
fn takes_returns_bool_instance(&self, b: bool) -> bool {
b
}

#[method_id(idTakesBool:)]
fn id_takes_bool(_b: bool) -> Option<Id<Self, Owned>> {
None
}

#[method_id(idTakesBoolInstance:)]
fn id_takes_bool_instance(&self, _b: bool) -> Option<Id<Self, Owned>> {
None
}
}
);

Expand Down Expand Up @@ -326,6 +336,16 @@ declare_class!(
fn unreachable_class_void() {
unreachable!()
}

#[method_id(unreachableId)]
fn unreachable_id(&self) -> Id<Self, Owned> {
unreachable!()
}

#[method_id(unreachableClassId)]
fn unreachable_class_id() -> Id<Self, Owned> {
unreachable!()
}
}
);

Expand Down
9 changes: 9 additions & 0 deletions crates/objc2/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ macro_rules! __sel_helper {
} => ({
$crate::__sel_data!($($parsed_sel)*)
});
// Single identifier
{
@()
$ident:ident
} => {
$crate::__sel_helper! {
@($ident)
}
};
// Parse identitifer + colon token
{
@($($parsed_sel:tt)*)
Expand Down
Loading

0 comments on commit 4997924

Please sign in to comment.