Skip to content

Allow #[method_id] in declare_class! #354

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

Merged
merged 6 commits into from
Jan 18, 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 @@ -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 All @@ -19,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
* Allow empty structs in `declare_class!` macro.
* Allow using `extern_methods!` without the `ClassType` trait in scope.
* Fixed a few small issues with `declare_class!`.


## 0.3.0-beta.4 - 2022-12-24
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())
}
}
107 changes: 99 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 All @@ -924,4 +969,50 @@ mod tests {

let _cls = Custom::class();
}

// Proof-of-concept how we could make declare_class! accept generic.
#[test]
fn test_generic() {
struct GenericDeclareClass<T>(T);

unsafe impl<T> RefEncode for GenericDeclareClass<T> {
const ENCODING_REF: Encoding = Encoding::Object;
}
unsafe impl<T> Message for GenericDeclareClass<T> {}

unsafe impl<T> ClassType for GenericDeclareClass<T> {
type Super = NSObject;
const NAME: &'static str = "GenericDeclareClass";

#[inline]
fn as_super(&self) -> &Self::Super {
unimplemented!()
}

#[inline]
fn as_super_mut(&mut self) -> &mut Self::Super {
unimplemented!()
}

fn class() -> &'static Class {
let superclass = NSObject::class();
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();

unsafe {
builder.add_method(
sel!(generic),
<GenericDeclareClass<T>>::generic as unsafe extern "C" fn(_, _),
);
}

builder.register()
}
}

impl<T> GenericDeclareClass<T> {
extern "C" fn generic(&self, _cmd: Sel) {}
}

let _ = GenericDeclareClass::<()>::class();
}
}
Loading