From a7fac23fed38a6ce99def02889160dace20a474c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 8 Jul 2022 18:26:58 +0200 Subject: [PATCH 01/21] Add `declare::Ivar` struct --- objc2-foundation/examples/custom_class.rs | 29 +- objc2/CHANGELOG.md | 2 + objc2/examples/declare_ivar.rs | 86 ++++++ objc2/src/declare.rs | 5 + objc2/src/declare/ivar.rs | 241 +++++++++++++++ objc2/src/declare/ivar_forwarding_impls.rs | 337 +++++++++++++++++++++ 6 files changed, 684 insertions(+), 16 deletions(-) create mode 100644 objc2/examples/declare_ivar.rs create mode 100644 objc2/src/declare/ivar.rs create mode 100644 objc2/src/declare/ivar_forwarding_impls.rs diff --git a/objc2-foundation/examples/custom_class.rs b/objc2-foundation/examples/custom_class.rs index bb5319fdf..53ddf80a7 100644 --- a/objc2-foundation/examples/custom_class.rs +++ b/objc2-foundation/examples/custom_class.rs @@ -1,17 +1,22 @@ use std::sync::Once; -use objc2::declare::ClassBuilder; +use objc2::declare::{ClassBuilder, Ivar, IvarType}; use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, Object, Sel}; use objc2::{msg_send, msg_send_id, sel}; use objc2::{Encoding, Message, RefEncode}; use objc2_foundation::NSObject; -/// In the future this should be an `extern type`, if that gets stabilized, -/// see [RFC-1861](https://rust-lang.github.io/rfcs/1861-extern-types.html). +struct NumberIvar; +unsafe impl IvarType for NumberIvar { + type Type = u32; + const NAME: &'static str = "_number"; +} + #[repr(C)] pub struct MYObject { inner: Object, + number: Ivar, } unsafe impl RefEncode for MYObject { @@ -28,27 +33,19 @@ impl MYObject { unsafe { msg_send_id![cls, new].unwrap() } } - fn number(&self) -> u32 { - unsafe { *self.inner.ivar("_number") } - } - - fn set_number(&mut self, number: u32) { - unsafe { self.inner.set_ivar("_number", number) }; - } - fn class() -> &'static Class { MYOBJECT_REGISTER_CLASS.call_once(|| { let superclass = NSObject::class(); let mut builder = ClassBuilder::new("MYObject", superclass).unwrap(); - builder.add_ivar::("_number"); + builder.add_ivar::<::Type>(::NAME); // Add ObjC methods for getting and setting the number extern "C" fn my_object_set_number(this: &mut MYObject, _cmd: Sel, number: u32) { - this.set_number(number); + *this.number = number; } extern "C" fn my_object_get_number(this: &MYObject, _cmd: Sel) -> u32 { - this.number() + *this.number } unsafe { @@ -68,7 +65,7 @@ impl MYObject { fn main() { let mut obj = MYObject::new(); - obj.set_number(7); + *obj.number = 7; println!("Number: {}", unsafe { let number: u32 = msg_send![&obj, number]; number @@ -77,5 +74,5 @@ fn main() { unsafe { let _: () = msg_send![&mut obj, setNumber: 12u32]; } - println!("Number: {}", obj.number()); + println!("Number: {}", obj.number); } diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index a1508b823..e92519026 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -39,6 +39,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added `Class::responds_to`. * Added `exception::Exception` object to improve error messages from caught exceptions. +* Added `declare::Ivar` helper struct. This is useful for building safe + abstractions that access instance variables. ### Changed * **BREAKING**: `Sel` is now required to be non-null, which means that you diff --git a/objc2/examples/declare_ivar.rs b/objc2/examples/declare_ivar.rs new file mode 100644 index 000000000..a0028c81d --- /dev/null +++ b/objc2/examples/declare_ivar.rs @@ -0,0 +1,86 @@ +use std::mem::MaybeUninit; + +use objc2::declare::{ClassBuilder, Ivar, IvarType}; +use objc2::rc::{Id, Owned}; +use objc2::runtime::{Bool, Class, Object, Sel}; +use objc2::{class, msg_send, msg_send_id, sel, Encoding, Message, RefEncode}; + +// Helper types for the two instance variables + +struct CustomIvar1; +unsafe impl IvarType for CustomIvar1 { + type Type = i32; + const NAME: &'static str = "ivar1"; +} + +struct CustomIvar2; +unsafe impl IvarType for CustomIvar2 { + type Type = Bool; + const NAME: &'static str = "ivar2"; +} + +/// Struct that represents the desired object +#[repr(C)] +pub struct CustomObject { + inner: Object, + // SAFETY: The instance variables are used within an object, and they are + // correctly declared in `create_class`. + ivar1: Ivar, + ivar2: Ivar, +} + +// SAFETY: `Ivar` is zero-sized, so it can be ignored +unsafe impl RefEncode for CustomObject { + const ENCODING_REF: Encoding<'static> = Encoding::Object; +} +unsafe impl Message for CustomObject {} + +pub fn create_class() -> &'static Class { + let superclass = class!(NSObject); + let mut builder = ClassBuilder::new("CustomObject", superclass).unwrap(); + + builder.add_ivar::<::Type>(CustomIvar1::NAME); + builder.add_ivar::<::Type>(CustomIvar2::NAME); + + #[repr(C)] + pub struct PartialInit { + inner: Object, + ivar1: Ivar>, + ivar2: Ivar>, + } + unsafe impl RefEncode for PartialInit { + const ENCODING_REF: Encoding<'static> = Encoding::Object; + } + unsafe impl Message for PartialInit {} + + impl PartialInit { + extern "C" fn init(&mut self, _cmd: Sel) -> Option<&mut Self> { + let this: Option<&mut Self> = unsafe { msg_send![super(self, class!(NSObject)), init] }; + this.map(|this| { + this.ivar1.write(42); + this.ivar2.write(Bool::from(true)); + this + }) + } + } + + unsafe { + builder.add_method(sel!(init), PartialInit::init as extern "C" fn(_, _) -> _); + } + + builder.register() +} + +fn main() { + let cls = create_class(); + let mut obj: Id = unsafe { msg_send_id![cls, new].unwrap() }; + + println!("Ivar1: {:?}", obj.ivar1); + println!("Ivar2: {:?}", obj.ivar2); + + *obj.ivar1 += 1; + *obj.ivar2 = Bool::from(false); + + println!("Ivar1: {:?}", obj.ivar1); + println!("Ivar2: {:?}", obj.ivar2); +} diff --git a/objc2/src/declare.rs b/objc2/src/declare.rs index 51b096044..a20f6bbb0 100644 --- a/objc2/src/declare.rs +++ b/objc2/src/declare.rs @@ -37,6 +37,9 @@ //! decl.register(); //! ``` +mod ivar; +mod ivar_forwarding_impls; + use alloc::format; use alloc::string::ToString; use core::mem; @@ -48,6 +51,8 @@ use std::ffi::CString; use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel}; use crate::{ffi, Encode, EncodeArguments, Encoding, Message, RefEncode}; +pub use ivar::{Ivar, IvarType}; + pub(crate) mod private { pub trait Sealed {} } diff --git a/objc2/src/declare/ivar.rs b/objc2/src/declare/ivar.rs new file mode 100644 index 000000000..78e12401d --- /dev/null +++ b/objc2/src/declare/ivar.rs @@ -0,0 +1,241 @@ +use core::fmt; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +use crate::runtime::Object; +use crate::Encode; + +/// Helper trait for defining instance variables. +/// +/// This should be implemented for an empty marker type, which can then be +/// used within [`Ivar`] to refer to the instance variable. +/// +/// +/// # Safety +/// +/// Really, [`Ivar`] should be marked as `unsafe`, but since we can't do that +/// we'll mark this trait as `unsafe` instead. See [`Ivar`] for safety +/// requirements. +/// +/// +/// # Examples +/// +/// Create an instance variable `myCustomIvar` with type `i32`. +/// +/// ``` +/// use objc2::declare::IvarType; +/// +/// // Helper type +/// struct MyCustomIvar; +/// +/// unsafe impl IvarType for MyCustomIvar { +/// type Type = i32; +/// const NAME: &'static str = "myCustomIvar"; +/// } +/// +/// // `Ivar` can now be used +/// ``` +pub unsafe trait IvarType { + /// The type of the instance variable. + type Type: Encode; + /// The name of the instance variable. + const NAME: &'static str; +} + +unsafe impl IvarType for MaybeUninit { + type Type = MaybeUninit; + const NAME: &'static str = T::NAME; +} + +/// A wrapper type over a custom instance variable. +/// +/// This type is not meant to be constructed by itself, it must reside within +/// another struct meant to represent an Objective-C object. +/// +/// On [`Deref`] it then uses the [`IvarType::NAME`] string to access the ivar +/// of the containing object. +/// +/// Note that this is not ([currently][zst-hack]) allowed by [stacked +/// borrows][sb], but due to [`Object`] being a zero-sized type such that we +/// don't have provenance over the ivars anyhow, this should be just as sound +/// as normal instance variable access. +/// +/// [sb]: https://github.com/rust-lang/unsafe-code-guidelines/blob/e21202c60c7be03dd2ab016ada92fb5305d40438/wip/stacked-borrows.md +/// [zst-hack]: https://github.com/rust-lang/unsafe-code-guidelines/issues/305 +/// +/// +/// # Safety +/// +/// This must be used within a type that act as an Objective-C object. In +/// particular, this is never safe to have on the stack by itself. +/// +/// Additionally, the instance variable described by `T` must be available on +/// the specific instance, and be of the exact same type. +/// +/// Finally, two ivars with the same name must not be used on the same object. +/// +/// +/// # Examples +/// +/// ``` +/// use objc2::declare::{Ivar, IvarType}; +/// use objc2::runtime::Object; +/// # +/// # #[cfg(feature = "gnustep-1-7")] +/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; +/// +/// // Declare ivar with given type and name +/// struct MyCustomIvar; +/// unsafe impl IvarType for MyCustomIvar { +/// type Type = i32; +/// const NAME: &'static str = "myCustomIvar"; +/// } +/// +/// // Custom object +/// #[repr(C)] +/// pub struct MyObject { +/// inner: Object, +/// // SAFETY: The instance variable is used within an object, and it is +/// // properly declared below. +/// my_ivar: Ivar, +/// } +/// +/// # use objc2::class; +/// # use objc2::declare::ClassBuilder; +/// # let mut builder = ClassBuilder::new("MyObject", class!(NSObject)).unwrap(); +/// // Declare the class and add the instance variable to it +/// builder.add_ivar::<::Type>(MyCustomIvar::NAME); +/// # let _cls = builder.register(); +/// +/// let obj: MyObject; +/// // You can now access `obj.my_ivar` +/// ``` +/// +/// See also the `declare_ivar.rs` example. +#[repr(C)] +// Must not be `Copy` nor `Clone`! +pub struct Ivar { + /// Make this type allowed in `repr(C)` + inner: [u8; 0], + /// For proper variance and auto traits + item: PhantomData, +} + +impl Deref for Ivar { + type Target = T::Type; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: The user ensures that this is placed in a struct that can + // be reinterpreted as an `Object`. Since `Ivar` can never be + // constructed by itself (and is neither Copy nor Clone), we know that + // it is guaranteed to _stay_ in said struct. + // + // Even if the user were to do `mem::swap`, the `Ivar` has a unique + // type (and does not hold any data), so that wouldn't break anything. + // + // Note: We technically don't have provenance over the object, nor the + // ivar, but the object doesn't have provenance over the ivar either, + // so that is fine. + let ptr = NonNull::from(self).cast::(); + let obj = unsafe { ptr.as_ref() }; + + // SAFETY: User ensures that the `Ivar` is only used when the ivar + // exists and has the correct type + unsafe { obj.ivar::(T::NAME) } + } +} + +impl DerefMut for Ivar { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + let ptr = NonNull::from(self).cast::(); + // SAFETY: Same as `deref`. + // + // Note: We don't use `mut` because the user might have two mutable + // references to different ivars, as such: + // + // ``` + // #[repr(C)] + // struct X { + // inner: Object, + // ivar1: Ivar, + // ivar2: Ivar, + // } + // + // let mut x: X; + // let ivar1: &mut Ivar = &mut x.ivar1; + // let ivar2: &mut Ivar = &mut x.ivar2; + // ``` + // + // And using `mut` would create aliasing mutable reference to the + // object. + // + // TODO: Not entirely sure, it might be safe to just do `as_mut`, but + // this is definitely safe. + let obj = unsafe { ptr.as_ref() }; + + // SAFETY: Same as `deref` + // + // Safe as mutable because there is only one access to a particular + // ivar at a time (since we have `&mut self`). `Object` is + // `UnsafeCell`, so mutable access through `&Object` is allowed. + unsafe { obj.ivar_ptr::(T::NAME).as_mut().unwrap_unchecked() } + } +} + +/// Format as a pointer to the instance variable. +impl fmt::Pointer for Ivar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ptr: *const T::Type = &**self; + fmt::Pointer::fmt(&ptr, f) + } +} + +#[cfg(test)] +mod tests { + use core::mem; + use core::panic::{RefUnwindSafe, UnwindSafe}; + + use super::*; + use crate::{test_utils, MessageReceiver}; + + struct TestIvar; + + unsafe impl IvarType for TestIvar { + type Type = u32; + const NAME: &'static str = "_foo"; + } + + #[repr(C)] + struct IvarTestObject { + inner: Object, + foo: Ivar, + } + + #[test] + fn auto_traits() { + fn assert_auto_traits() {} + assert_auto_traits::>(); + + // Ensure that `Ivar` is zero-sized + assert_eq!(mem::size_of::>(), 0); + assert_eq!(mem::align_of::>(), 1); + } + + #[test] + fn access_ivar() { + let mut obj = test_utils::custom_object(); + let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] }; + + let obj = unsafe { + obj.__as_raw_receiver() + .cast::() + .as_ref() + .unwrap() + }; + assert_eq!(*obj.foo, 42); + } +} diff --git a/objc2/src/declare/ivar_forwarding_impls.rs b/objc2/src/declare/ivar_forwarding_impls.rs new file mode 100644 index 000000000..dd4dc0d7f --- /dev/null +++ b/objc2/src/declare/ivar_forwarding_impls.rs @@ -0,0 +1,337 @@ +//! Trivial forwarding impls on `Ivar`. +//! +//! Kept here to keep `ivar.rs` free from this boilerplate. +//! +//! `#[inline]` is used where the standard library `Box` uses it. + +#![forbid(unsafe_code)] + +// use alloc::borrow; +use alloc::string::String; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::fmt; +use core::future::Future; +use core::hash; +use core::iter::FusedIterator; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; +use std::error::Error; +use std::io; + +use super::{Ivar, IvarType}; + +impl PartialEq for Ivar +where + T::Type: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + (**self).eq(&**other) + } + + #[inline] + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, other: &Self) -> bool { + (**self).ne(&**other) + } +} + +impl Eq for Ivar where T::Type: Eq {} + +impl PartialOrd for Ivar +where + T::Type: PartialOrd, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } + #[inline] + fn lt(&self, other: &Self) -> bool { + (**self).lt(&**other) + } + #[inline] + fn le(&self, other: &Self) -> bool { + (**self).le(&**other) + } + #[inline] + fn ge(&self, other: &Self) -> bool { + (**self).ge(&**other) + } + #[inline] + fn gt(&self, other: &Self) -> bool { + (**self).gt(&**other) + } +} + +impl Ord for Ivar +where + T::Type: Ord, +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + (**self).cmp(&**other) + } +} + +impl hash::Hash for Ivar +where + T::Type: hash::Hash, +{ + fn hash(&self, state: &mut H) { + (**self).hash(state) + } +} + +impl hash::Hasher for Ivar +where + T::Type: hash::Hasher, +{ + fn finish(&self) -> u64 { + (**self).finish() + } + fn write(&mut self, bytes: &[u8]) { + (**self).write(bytes) + } + fn write_u8(&mut self, i: u8) { + (**self).write_u8(i) + } + fn write_u16(&mut self, i: u16) { + (**self).write_u16(i) + } + fn write_u32(&mut self, i: u32) { + (**self).write_u32(i) + } + fn write_u64(&mut self, i: u64) { + (**self).write_u64(i) + } + fn write_u128(&mut self, i: u128) { + (**self).write_u128(i) + } + fn write_usize(&mut self, i: usize) { + (**self).write_usize(i) + } + fn write_i8(&mut self, i: i8) { + (**self).write_i8(i) + } + fn write_i16(&mut self, i: i16) { + (**self).write_i16(i) + } + fn write_i32(&mut self, i: i32) { + (**self).write_i32(i) + } + fn write_i64(&mut self, i: i64) { + (**self).write_i64(i) + } + fn write_i128(&mut self, i: i128) { + (**self).write_i128(i) + } + fn write_isize(&mut self, i: isize) { + (**self).write_isize(i) + } +} + +impl fmt::Display for Ivar +where + T::Type: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Debug for Ivar +where + T::Type: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl Iterator for Ivar +where + I::Type: Iterator, +{ + type Item = ::Item; + fn next(&mut self) -> Option<::Item> { + (**self).next() + } + fn size_hint(&self) -> (usize, Option) { + (**self).size_hint() + } + fn nth(&mut self, n: usize) -> Option<::Item> { + (**self).nth(n) + } +} + +impl DoubleEndedIterator for Ivar +where + I::Type: DoubleEndedIterator, +{ + fn next_back(&mut self) -> Option<::Item> { + (**self).next_back() + } + fn nth_back(&mut self, n: usize) -> Option<::Item> { + (**self).nth_back(n) + } +} + +impl ExactSizeIterator for Ivar +where + I::Type: ExactSizeIterator, +{ + fn len(&self) -> usize { + (**self).len() + } +} + +impl FusedIterator for Ivar where I::Type: FusedIterator {} + +// impl borrow::Borrow for Ivar { +// fn borrow(&self) -> &T::Type { +// Deref::deref(self) +// } +// } +// +// impl borrow::BorrowMut for Ivar { +// fn borrow_mut(&mut self) -> &mut T::Type { +// DerefMut::deref_mut(self) +// } +// } + +impl AsRef for Ivar { + fn as_ref(&self) -> &T::Type { + Deref::deref(self) + } +} + +impl AsMut for Ivar { + fn as_mut(&mut self) -> &mut T::Type { + DerefMut::deref_mut(self) + } +} + +impl Error for Ivar +where + T::Type: Error, +{ + fn source(&self) -> Option<&(dyn Error + 'static)> { + (**self).source() + } +} + +impl io::Read for Ivar +where + T::Type: io::Read, +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + + #[inline] + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + (**self).read_vectored(bufs) + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + (**self).read_to_end(buf) + } + + #[inline] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + (**self).read_to_string(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + (**self).read_exact(buf) + } +} + +impl io::Write for Ivar +where + T::Type: io::Write, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} + +impl io::Seek for Ivar +where + T::Type: io::Seek, +{ + #[inline] + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + (**self).seek(pos) + } + + #[inline] + fn stream_position(&mut self) -> io::Result { + (**self).stream_position() + } +} + +impl io::BufRead for Ivar +where + T::Type: io::BufRead, +{ + #[inline] + fn fill_buf(&mut self) -> io::Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } + + #[inline] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + (**self).read_until(byte, buf) + } + + #[inline] + fn read_line(&mut self, buf: &mut String) -> io::Result { + (**self).read_line(buf) + } +} + +impl Future for Ivar +where + T::Type: Future + Unpin, +{ + type Output = ::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + ::poll(Pin::new(&mut *self), cx) + } +} + +// TODO: impl Fn traits, CoerceUnsized, Stream and so on when stabilized From 7b1f246a4afa1c3340c76ad4ad91fc0b230b8d13 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 11 Jul 2022 00:06:58 +0200 Subject: [PATCH 02/21] Add initial declare_class! macro --- objc2-foundation/examples/declaration.rs | 76 ++++++++++++++++++++++++ objc2-foundation/src/declare_macro.rs | 76 ++++++++++++++++++++++++ objc2-foundation/src/lib.rs | 2 + objc2-foundation/src/macros.rs | 6 +- 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 objc2-foundation/examples/declaration.rs create mode 100644 objc2-foundation/src/declare_macro.rs diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs new file mode 100644 index 000000000..e9e9579b1 --- /dev/null +++ b/objc2-foundation/examples/declaration.rs @@ -0,0 +1,76 @@ +use objc2::{ + msg_send, msg_send_id, + rc::{Id, Shared}, + runtime::{Bool, Object}, +}; +use objc2_foundation::{declare_class, extern_class, NSObject}; + +#[cfg(all(feature = "apple", target_os = "macos"))] +#[link(name = "AppKit", kind = "framework")] +extern "C" {} + +extern_class! { + unsafe struct NSResponder: NSObject; +} + +declare_class! { + // TODO: Protocol NSApplicationDelegate + unsafe struct CustomAppDelegate: NSResponder, NSObject { + pub ivar: u8, + another_ivar: Bool, + } + + unsafe impl { + // #[selector(initWith:another:)] + fn init_with( + this: &mut Self, + ivar: u8, + another_ivar: Bool, + ) -> *mut Self { + let this: *mut Self = unsafe { + msg_send![super(this, NSResponder::class()), init] + }; + if let Some(this) = unsafe { this.as_mut() } { + // TODO: Allow initialization through MaybeUninit + *this.ivar = ivar; + *this.another_ivar = another_ivar; + } + this + } + + // #[selector(applicationDidFinishLaunching:)] + #[allow(unused)] // TMP + fn did_finish_launching(&self, _sender: *mut Object) { + println!("Did finish launching!"); + } + + // #[selector(applicationWillTerminate:)] + #[allow(unused)] // TMP + fn will_terminate(&self, _sender: *mut Object) { + println!("Will terminate!"); + } + } +} + +impl CustomAppDelegate { + pub fn new(ivar: u8, another_ivar: bool) -> Id { + let cls = Self::class(); + unsafe { + msg_send_id![ + msg_send_id![cls, alloc], + initWith: ivar, + another: Bool::from(another_ivar), + ] + .unwrap() + } + } +} + +fn main() { + let _cls = CustomAppDelegate::create_class(); // TMP + + let delegate = CustomAppDelegate::new(42, true); + + println!("{}", delegate.ivar); + println!("{}", delegate.another_ivar.as_bool()); +} diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs new file mode 100644 index 000000000..fad4a4f42 --- /dev/null +++ b/objc2-foundation/src/declare_macro.rs @@ -0,0 +1,76 @@ +/// TODO +#[macro_export] +macro_rules! declare_class { + { + $(#[$m:meta])* + unsafe $v:vis struct $name:ident: $inherits:ty $(, $inheritance_rest:ty)* { + $($ivar_v:vis $ivar:ident: $ivar_ty:ty,)* + } + + $(#[$impl_m:meta])* + unsafe impl { + $( + $s:item + )* + } + } => { + $( + #[allow(non_camel_case_types)] + $ivar_v struct $ivar { + __priv: (), + } + + unsafe impl $crate::objc2::declare::IvarType for $ivar { + type Type = $ivar_ty; + const NAME: &'static str = stringify!($ivar); + } + )* + + $crate::__inner_extern_class! { + @__inner + $(#[$m])* + unsafe $v struct $name<>: $inherits, $($inheritance_rest,)* $crate::objc2::runtime::Object { + $($ivar_v $ivar: $crate::objc2::declare::Ivar<$ivar>,)* + } + } + + // Creation + impl $name { + fn create_class() -> &'static $crate::objc2::runtime::Class { + let superclass = <$inherits>::class(); + let mut builder = $crate::objc2::declare::ClassBuilder::new(stringify!($name), superclass).unwrap(); + + $( + builder.add_ivar::<$ivar_ty>(stringify!($ivar)); + )* + + // TODO: Fix shim + extern "C" fn init_with( + this: &mut CustomAppDelegate, + _cmd: $crate::objc2::runtime::Sel, + ivar: u8, + another_ivar: Bool, + ) -> *mut CustomAppDelegate { + CustomAppDelegate::init_with(this, ivar, another_ivar) + } + + unsafe { + builder.add_method( + $crate::objc2::sel!(initWith:another:), + init_with as unsafe extern "C" fn(_, _, _, _) -> _, + ); + } + + builder.register() + } + } + + // Methods + $(#[$impl_m])* + impl $name { + $( + $s + )* + } + }; +} diff --git a/objc2-foundation/src/lib.rs b/objc2-foundation/src/lib.rs index 174fb91fd..e090e15b9 100644 --- a/objc2-foundation/src/lib.rs +++ b/objc2-foundation/src/lib.rs @@ -87,6 +87,8 @@ extern "C" {} #[macro_use] mod macros; +#[macro_use] +mod declare_macro; mod array; mod attributed_string; diff --git a/objc2-foundation/src/macros.rs b/objc2-foundation/src/macros.rs index e5fefb014..936684e41 100644 --- a/objc2-foundation/src/macros.rs +++ b/objc2-foundation/src/macros.rs @@ -166,7 +166,7 @@ macro_rules! __inner_extern_class { @__inner $(#[$m:meta])* unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident)?),*>: $inherits:ty $(, $inheritance_rest:ty)* { - $($p:ident: $pty:ty,)* + $($p_v:vis $p:ident: $pty:ty,)* } ) => { $(#[$m])* @@ -174,8 +174,8 @@ macro_rules! __inner_extern_class { #[repr(C)] $v struct $name<$($t $(: $b)?),*> { __inner: $inherits, - // Additional fields (should only be zero-sized PhantomData). - $($p: $pty),* + // Additional fields (should only be zero-sized PhantomData or ivars). + $($p_v $p: $pty),* } unsafe impl<$($t $(: $b)?),*> $crate::objc2::Message for $name<$($t),*> { } From a202d32452c0e246be43f0aff4c90626342644a7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 7 Jul 2022 01:23:52 +0200 Subject: [PATCH 03/21] Add ability to implement protocols in the `declare_class!` macro --- objc2-foundation/examples/declaration.rs | 6 +++++- objc2-foundation/src/declare_macro.rs | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index e9e9579b1..95472168d 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -14,7 +14,11 @@ extern_class! { } declare_class! { - // TODO: Protocol NSApplicationDelegate + // For some reason, `NSApplicationDelegate` is not a "real" protocol we + // can retrieve using `objc_getProtocol` - it seems it is created by + // `clang` only when used in Objective-C... + // + // TODO: Investigate this! unsafe struct CustomAppDelegate: NSResponder, NSObject { pub ivar: u8, another_ivar: Bool, diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index fad4a4f42..08ea6ff5e 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -3,7 +3,7 @@ macro_rules! declare_class { { $(#[$m:meta])* - unsafe $v:vis struct $name:ident: $inherits:ty $(, $inheritance_rest:ty)* { + unsafe $v:vis struct $name:ident: $inherits:ident $(, $inheritance_rest:ident)* $(<$($protocols:ident),+ $(,)?>)? { $($ivar_v:vis $ivar:ident: $ivar_ty:ty,)* } @@ -40,6 +40,13 @@ macro_rules! declare_class { let superclass = <$inherits>::class(); let mut builder = $crate::objc2::declare::ClassBuilder::new(stringify!($name), superclass).unwrap(); + // Implement protocols + $( + $( + builder.add_protocol($crate::objc2::runtime::Protocol::get(stringify!($protocols)).unwrap()); + )+ + )? + $( builder.add_ivar::<$ivar_ty>(stringify!($ivar)); )* From 493247f844e0d1335daa3d0fa4b51613662d79a8 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 16:07:43 +0200 Subject: [PATCH 04/21] Rewrite methods in declare_class! --- objc2-foundation/examples/declaration.rs | 10 +- objc2-foundation/src/declare_macro.rs | 154 ++++++++++++++++++++--- 2 files changed, 145 insertions(+), 19 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index 95472168d..14f7755af 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -27,12 +27,12 @@ declare_class! { unsafe impl { // #[selector(initWith:another:)] fn init_with( - this: &mut Self, + self: &mut Self, ivar: u8, another_ivar: Bool, ) -> *mut Self { let this: *mut Self = unsafe { - msg_send![super(this, NSResponder::class()), init] + msg_send![super(self, NSResponder::class()), init] }; if let Some(this) = unsafe { this.as_mut() } { // TODO: Allow initialization through MaybeUninit @@ -53,6 +53,12 @@ declare_class! { fn will_terminate(&self, _sender: *mut Object) { println!("Will terminate!"); } + + // #[selector(myClassMethod)] + #[allow(unused)] // TMP + fn my_class_method() { + println!("A class method!"); + } } } diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 08ea6ff5e..2bb552ae2 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -1,3 +1,133 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! __inner_declare_class { + {@rewrite_methods} => {}; + { + @rewrite_methods + + $(#[$m:meta])* + $v:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block + + $($rest:tt)* + } => { + $crate::__inner_declare_class! { + @rewrite_methods_inner + + // Split args out so that we can match on `self`, while still use + // it as a function argument + ($($args)*) + + $(#[$m])* + $v fn $name($($args)*) $(-> $ret)? $body + } + + $crate::__inner_declare_class! { + @rewrite_methods + + $($rest)* + } + }; + + // Instance method + { + @rewrite_methods_inner + + (&mut self $($__rest_args:tt)*) + + $(#[$m:meta])* + $v:vis fn $name:ident( + &mut $self:ident + $($rest_args:tt)* + ) $(-> $ret:ty)? $body:block + } => { + $(#[$m])* + $v extern "C" fn $name( + &mut $self, + _cmd: $crate::objc2::runtime::Sel + $($rest_args)* + ) $(-> $ret)? $body + }; + { + @rewrite_methods_inner + + (&self $($__rest_args:tt)*) + + $(#[$m:meta])* + $v:vis fn $name:ident( + &$self:ident + $($rest_args:tt)* + ) $(-> $ret:ty)? $body:block + } => { + $(#[$m])* + $v extern "C" fn $name( + &$self, + _cmd: $crate::objc2::runtime::Sel + $($rest_args)* + ) $(-> $ret)? $body + }; + { + @rewrite_methods_inner + + ( + mut self: $__self_ty:ty + $(, $($__rest_args:tt)*)? + ) + + $(#[$m:meta])* + $v:vis fn $name:ident( + mut $self:ident: $self_ty:ty + $(, $($rest_args:tt)*)? + ) $(-> $ret:ty)? $body:block + } => { + $(#[$m])* + $v extern "C" fn $name( + mut $self: $self_ty, + _cmd: $crate::objc2::runtime::Sel + $(, $($rest_args)*)? + ) $(-> $ret)? $body + }; + { + @rewrite_methods_inner + + ( + self: $__self_ty:ty + $(, $($__rest_args:tt)*)? + ) + + $(#[$m:meta])* + $v:vis fn $name:ident( + $self:ident: $self_ty:ty + $(, $($rest_args:tt)*)? + ) $(-> $ret:ty)? $body:block + } => { + $(#[$m])* + $v extern "C" fn $name( + $self: $self_ty, + _cmd: $crate::objc2::runtime::Sel + $(, $($rest_args)*)? + ) $(-> $ret)? $body + }; + + // Class method + { + @rewrite_methods_inner + + ($($__args:tt)*) + + $(#[$m:meta])* + $v:vis fn $name:ident( + $($args:tt)* + ) $(-> $ret:ty)? $body:block + } => { + $(#[$m])* + $v extern "C" fn $name( + _cls: &$crate::objc2::runtime::Class, + _cmd: $crate::objc2::runtime::Sel, + $($args)* + ) $(-> $ret)? $body + }; +} + /// TODO #[macro_export] macro_rules! declare_class { @@ -9,9 +139,7 @@ macro_rules! declare_class { $(#[$impl_m:meta])* unsafe impl { - $( - $s:item - )* + $($methods:tt)* } } => { $( @@ -51,20 +179,10 @@ macro_rules! declare_class { builder.add_ivar::<$ivar_ty>(stringify!($ivar)); )* - // TODO: Fix shim - extern "C" fn init_with( - this: &mut CustomAppDelegate, - _cmd: $crate::objc2::runtime::Sel, - ivar: u8, - another_ivar: Bool, - ) -> *mut CustomAppDelegate { - CustomAppDelegate::init_with(this, ivar, another_ivar) - } - unsafe { builder.add_method( $crate::objc2::sel!(initWith:another:), - init_with as unsafe extern "C" fn(_, _, _, _) -> _, + $name::init_with as unsafe extern "C" fn(_, _, _, _) -> _, ); } @@ -75,9 +193,11 @@ macro_rules! declare_class { // Methods $(#[$impl_m])* impl $name { - $( - $s - )* + $crate::__inner_declare_class! { + @rewrite_methods + + $($methods)* + } } }; } From 0e4f44e2bee4b61980db023b05a9f4832a7a2e44 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 16:08:01 +0200 Subject: [PATCH 05/21] Allow specifying selector in declare_class! --- objc2-foundation/examples/declaration.rs | 8 ++++---- objc2-foundation/src/declare_macro.rs | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index 14f7755af..ea80bf689 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -25,7 +25,7 @@ declare_class! { } unsafe impl { - // #[selector(initWith:another:)] + @sel(initWith:another:) fn init_with( self: &mut Self, ivar: u8, @@ -42,20 +42,20 @@ declare_class! { this } - // #[selector(applicationDidFinishLaunching:)] #[allow(unused)] // TMP + @sel(applicationDidFinishLaunching:) fn did_finish_launching(&self, _sender: *mut Object) { println!("Did finish launching!"); } - // #[selector(applicationWillTerminate:)] #[allow(unused)] // TMP + @sel(applicationWillTerminate:) fn will_terminate(&self, _sender: *mut Object) { println!("Will terminate!"); } - // #[selector(myClassMethod)] #[allow(unused)] // TMP + @sel(myClassMethod) fn my_class_method() { println!("A class method!"); } diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 2bb552ae2..b960d616b 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -6,6 +6,7 @@ macro_rules! __inner_declare_class { @rewrite_methods $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block $($rest:tt)* @@ -18,6 +19,7 @@ macro_rules! __inner_declare_class { ($($args)*) $(#[$m])* + @sel($($sel)+) $v fn $name($($args)*) $(-> $ret)? $body } @@ -35,6 +37,7 @@ macro_rules! __inner_declare_class { (&mut self $($__rest_args:tt)*) $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident( &mut $self:ident $($rest_args:tt)* @@ -53,6 +56,7 @@ macro_rules! __inner_declare_class { (&self $($__rest_args:tt)*) $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident( &$self:ident $($rest_args:tt)* @@ -74,6 +78,7 @@ macro_rules! __inner_declare_class { ) $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident( mut $self:ident: $self_ty:ty $(, $($rest_args:tt)*)? @@ -95,6 +100,7 @@ macro_rules! __inner_declare_class { ) $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident( $self:ident: $self_ty:ty $(, $($rest_args:tt)*)? @@ -115,6 +121,7 @@ macro_rules! __inner_declare_class { ($($__args:tt)*) $(#[$m:meta])* + @sel($($sel:tt)+) $v:vis fn $name:ident( $($args:tt)* ) $(-> $ret:ty)? $body:block From 0d1ba182b61306bdcbb824b59b81a105b808d2f0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 16:19:55 +0200 Subject: [PATCH 06/21] Internal declare_class! refactor To allow extra processing step after @rewrite_methods --- objc2-foundation/src/declare_macro.rs | 105 +++++++++++++++++--------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index b960d616b..56b112403 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -1,9 +1,10 @@ #[doc(hidden)] #[macro_export] macro_rules! __inner_declare_class { - {@rewrite_methods} => {}; + {@rewrite_methods @$output_type:ident} => {}; { @rewrite_methods + @$output_type:ident $(#[$m:meta])* @sel($($sel:tt)+) @@ -13,7 +14,7 @@ macro_rules! __inner_declare_class { } => { $crate::__inner_declare_class! { @rewrite_methods_inner - + @$output_type // Split args out so that we can match on `self`, while still use // it as a function argument ($($args)*) @@ -25,6 +26,7 @@ macro_rules! __inner_declare_class { $crate::__inner_declare_class! { @rewrite_methods + @$output_type $($rest)* } @@ -33,7 +35,7 @@ macro_rules! __inner_declare_class { // Instance method { @rewrite_methods_inner - + @$output_type:ident (&mut self $($__rest_args:tt)*) $(#[$m:meta])* @@ -43,16 +45,20 @@ macro_rules! __inner_declare_class { $($rest_args:tt)* ) $(-> $ret:ty)? $body:block } => { - $(#[$m])* - $v extern "C" fn $name( - &mut $self, - _cmd: $crate::objc2::runtime::Sel - $($rest_args)* - ) $(-> $ret)? $body + $crate::__inner_declare_class! { + @$output_type + + $(#[$m])* + $v extern "C" fn $name( + &mut $self, + _cmd: $crate::objc2::runtime::Sel + $($rest_args)* + ) $(-> $ret)? $body + } }; { @rewrite_methods_inner - + @$output_type:ident (&self $($__rest_args:tt)*) $(#[$m:meta])* @@ -62,16 +68,20 @@ macro_rules! __inner_declare_class { $($rest_args:tt)* ) $(-> $ret:ty)? $body:block } => { - $(#[$m])* - $v extern "C" fn $name( - &$self, - _cmd: $crate::objc2::runtime::Sel - $($rest_args)* - ) $(-> $ret)? $body + $crate::__inner_declare_class! { + @$output_type + + $(#[$m])* + $v extern "C" fn $name( + &$self, + _cmd: $crate::objc2::runtime::Sel + $($rest_args)* + ) $(-> $ret)? $body + } }; { @rewrite_methods_inner - + @$output_type:ident ( mut self: $__self_ty:ty $(, $($__rest_args:tt)*)? @@ -84,16 +94,20 @@ macro_rules! __inner_declare_class { $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block } => { - $(#[$m])* - $v extern "C" fn $name( - mut $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel - $(, $($rest_args)*)? - ) $(-> $ret)? $body + $crate::__inner_declare_class! { + @$output_type + + $(#[$m])* + $v extern "C" fn $name( + mut $self: $self_ty, + _cmd: $crate::objc2::runtime::Sel + $(, $($rest_args)*)? + ) $(-> $ret)? $body + } }; { @rewrite_methods_inner - + @$output_type:ident ( self: $__self_ty:ty $(, $($__rest_args:tt)*)? @@ -106,18 +120,22 @@ macro_rules! __inner_declare_class { $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block } => { - $(#[$m])* - $v extern "C" fn $name( - $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel - $(, $($rest_args)*)? - ) $(-> $ret)? $body + $crate::__inner_declare_class! { + @$output_type + + $(#[$m])* + $v extern "C" fn $name( + $self: $self_ty, + _cmd: $crate::objc2::runtime::Sel + $(, $($rest_args)*)? + ) $(-> $ret)? $body + } }; // Class method { @rewrite_methods_inner - + @$output_type:ident ($($__args:tt)*) $(#[$m:meta])* @@ -126,12 +144,24 @@ macro_rules! __inner_declare_class { $($args:tt)* ) $(-> $ret:ty)? $body:block } => { - $(#[$m])* - $v extern "C" fn $name( - _cls: &$crate::objc2::runtime::Class, - _cmd: $crate::objc2::runtime::Sel, - $($args)* - ) $(-> $ret)? $body + $crate::__inner_declare_class! { + @$output_type + + $(#[$m])* + $v extern "C" fn $name( + _cls: &$crate::objc2::runtime::Class, + _cmd: $crate::objc2::runtime::Sel, + $($args)* + ) $(-> $ret)? $body + } + }; + + { + @method_out + + $($method:item)* + } => { + $($method)* }; } @@ -202,6 +232,7 @@ macro_rules! declare_class { impl $name { $crate::__inner_declare_class! { @rewrite_methods + @method_out $($methods)* } From 34468b6f0472d810b1b3915af7a61602f531703c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 16:46:49 +0200 Subject: [PATCH 07/21] Properly support methods in declare_class! --- objc2-foundation/examples/declaration.rs | 3 - objc2-foundation/src/declare_macro.rs | 288 +++++++++++++++++++++-- 2 files changed, 271 insertions(+), 20 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index ea80bf689..4feaa2a8a 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -42,19 +42,16 @@ declare_class! { this } - #[allow(unused)] // TMP @sel(applicationDidFinishLaunching:) fn did_finish_launching(&self, _sender: *mut Object) { println!("Did finish launching!"); } - #[allow(unused)] // TMP @sel(applicationWillTerminate:) fn will_terminate(&self, _sender: *mut Object) { println!("Will terminate!"); } - #[allow(unused)] // TMP @sel(myClassMethod) fn my_class_method() { println!("A class method!"); diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 56b112403..8c7866668 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -1,10 +1,11 @@ #[doc(hidden)] #[macro_export] macro_rules! __inner_declare_class { - {@rewrite_methods @$output_type:ident} => {}; + {@rewrite_methods @$output_type:ident @$builder:ident} => {}; { @rewrite_methods @$output_type:ident + @$builder:ident $(#[$m:meta])* @sel($($sel:tt)+) @@ -15,6 +16,7 @@ macro_rules! __inner_declare_class { $crate::__inner_declare_class! { @rewrite_methods_inner @$output_type + @$builder // Split args out so that we can match on `self`, while still use // it as a function argument ($($args)*) @@ -27,6 +29,7 @@ macro_rules! __inner_declare_class { $crate::__inner_declare_class! { @rewrite_methods @$output_type + @$builder $($rest)* } @@ -36,52 +39,65 @@ macro_rules! __inner_declare_class { { @rewrite_methods_inner @$output_type:ident + @$builder:ident (&mut self $($__rest_args:tt)*) $(#[$m:meta])* @sel($($sel:tt)+) $v:vis fn $name:ident( &mut $self:ident - $($rest_args:tt)* + $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block } => { $crate::__inner_declare_class! { @$output_type + @instance_method + @sel($($sel)*) + @$name + @$builder + @($($($rest_args)*)?) $(#[$m])* $v extern "C" fn $name( &mut $self, - _cmd: $crate::objc2::runtime::Sel - $($rest_args)* + _cmd: $crate::objc2::runtime::Sel, + $($($rest_args)*)? ) $(-> $ret)? $body } }; { @rewrite_methods_inner @$output_type:ident + @$builder:ident (&self $($__rest_args:tt)*) $(#[$m:meta])* @sel($($sel:tt)+) $v:vis fn $name:ident( &$self:ident - $($rest_args:tt)* + $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block } => { $crate::__inner_declare_class! { @$output_type + @instance_method + @sel($($sel)*) + @$name + @$builder + @($($($rest_args)*)?) $(#[$m])* $v extern "C" fn $name( &$self, - _cmd: $crate::objc2::runtime::Sel - $($rest_args)* + _cmd: $crate::objc2::runtime::Sel, + $($($rest_args)*)? ) $(-> $ret)? $body } }; { @rewrite_methods_inner @$output_type:ident + @$builder:ident ( mut self: $__self_ty:ty $(, $($__rest_args:tt)*)? @@ -96,18 +112,24 @@ macro_rules! __inner_declare_class { } => { $crate::__inner_declare_class! { @$output_type + @instance_method + @sel($($sel)*) + @$name + @$builder + @($($($rest_args)*)?) $(#[$m])* $v extern "C" fn $name( mut $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel - $(, $($rest_args)*)? + _cmd: $crate::objc2::runtime::Sel, + $($($rest_args)*)? ) $(-> $ret)? $body } }; { @rewrite_methods_inner @$output_type:ident + @$builder:ident ( self: $__self_ty:ty $(, $($__rest_args:tt)*)? @@ -122,12 +144,17 @@ macro_rules! __inner_declare_class { } => { $crate::__inner_declare_class! { @$output_type + @instance_method + @sel($($sel)*) + @$name + @$builder + @($($($rest_args)*)?) $(#[$m])* $v extern "C" fn $name( $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel - $(, $($rest_args)*)? + _cmd: $crate::objc2::runtime::Sel, + $($($rest_args)*)? ) $(-> $ret)? $body } }; @@ -136,6 +163,7 @@ macro_rules! __inner_declare_class { { @rewrite_methods_inner @$output_type:ident + @$builder:ident ($($__args:tt)*) $(#[$m:meta])* @@ -146,6 +174,11 @@ macro_rules! __inner_declare_class { } => { $crate::__inner_declare_class! { @$output_type + @class_method + @sel($($sel)*) + @$name + @$builder + @($($args)*) $(#[$m])* $v extern "C" fn $name( @@ -158,10 +191,227 @@ macro_rules! __inner_declare_class { { @method_out + @$method_type:ident + @sel($($sel:tt)*) + @$name:ident + @$builder:ident + @($($builder_args:tt)*) + + $method:item + } => { + $method + }; + { + @register_out + @class_method + @sel($($sel:tt)*) + @$name:ident + @$builder:ident + @($($builder_args:tt)*) + + $method:item + } => { + $builder.add_class_method( + $crate::objc2::sel!($($sel)*), + $crate::__inner_declare_class! { + @cast_extern_fn + @$name + $($builder_args)* + }, + ); + }; + { + @register_out + @instance_method + @sel($($sel:tt)*) + @$name:ident + @$builder:ident + @($($builder_args:tt)*) + + $method:item + } => { + $builder.add_method( + $crate::objc2::sel!($($sel)*), + $crate::__inner_declare_class! { + @cast_extern_fn + @$name + $($builder_args)* + }, + ); + }; + + // Create the `as extern "C" fn(...) -> _` cast + // + // TODO: Investigate if there's a better way of doing this + { + @cast_extern_fn + @$name:ident + + $(,)? + } => { + Self::$name as extern "C" fn(_, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident - $($method:item)* + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty, + $param8:ident: $param8_ty:ty $(,)? } => { - $($method)* + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty, + $param8:ident: $param8_ty:ty, + $param9:ident: $param9_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty, + $param8:ident: $param8_ty:ty, + $param9:ident: $param9_ty:ty, + $param10:ident: $param10_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty, + $param8:ident: $param8_ty:ty, + $param9:ident: $param9_ty:ty, + $param10:ident: $param10_ty:ty, + $param11:ident: $param11_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _, _) -> _ + }; + { + @cast_extern_fn + @$name:ident + + $param1:ident: $param1_ty:ty, + $param2:ident: $param2_ty:ty, + $param3:ident: $param3_ty:ty, + $param4:ident: $param4_ty:ty, + $param5:ident: $param5_ty:ty, + $param6:ident: $param6_ty:ty, + $param7:ident: $param7_ty:ty, + $param8:ident: $param8_ty:ty, + $param9:ident: $param9_ty:ty, + $param10:ident: $param10_ty:ty, + $param11:ident: $param11_ty:ty, + $param12:ident: $param12_ty:ty $(,)? + } => { + Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _, _, _) -> _ }; } @@ -217,10 +467,13 @@ macro_rules! declare_class { )* unsafe { - builder.add_method( - $crate::objc2::sel!(initWith:another:), - $name::init_with as unsafe extern "C" fn(_, _, _, _) -> _, - ); + $crate::__inner_declare_class! { + @rewrite_methods + @register_out + @builder + + $($methods)* + } } builder.register() @@ -233,6 +486,7 @@ macro_rules! declare_class { $crate::__inner_declare_class! { @rewrite_methods @method_out + @__builder $($methods)* } From c4aaa196c2fe71ab553bbd34d6d0a0e7bcbc5654 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 20:55:02 +0200 Subject: [PATCH 08/21] Remove support for publicity specifiers on methods We don't actually want people to call these, and it'd be an easy way for libraries to accidentally introduce unsoundness --- objc2-foundation/src/declare_macro.rs | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 8c7866668..d5dbf107b 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -9,7 +9,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block + fn $name:ident($($args:tt)*) $(-> $ret:ty)? $body:block $($rest:tt)* } => { @@ -23,7 +23,7 @@ macro_rules! __inner_declare_class { $(#[$m])* @sel($($sel)+) - $v fn $name($($args)*) $(-> $ret)? $body + fn $name($($args)*) $(-> $ret)? $body } $crate::__inner_declare_class! { @@ -44,7 +44,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident( + fn $name:ident( &mut $self:ident $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block @@ -58,7 +58,7 @@ macro_rules! __inner_declare_class { @($($($rest_args)*)?) $(#[$m])* - $v extern "C" fn $name( + extern "C" fn $name( &mut $self, _cmd: $crate::objc2::runtime::Sel, $($($rest_args)*)? @@ -73,7 +73,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident( + fn $name:ident( &$self:ident $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block @@ -87,7 +87,7 @@ macro_rules! __inner_declare_class { @($($($rest_args)*)?) $(#[$m])* - $v extern "C" fn $name( + extern "C" fn $name( &$self, _cmd: $crate::objc2::runtime::Sel, $($($rest_args)*)? @@ -105,7 +105,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident( + fn $name:ident( mut $self:ident: $self_ty:ty $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block @@ -119,7 +119,7 @@ macro_rules! __inner_declare_class { @($($($rest_args)*)?) $(#[$m])* - $v extern "C" fn $name( + extern "C" fn $name( mut $self: $self_ty, _cmd: $crate::objc2::runtime::Sel, $($($rest_args)*)? @@ -137,7 +137,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident( + fn $name:ident( $self:ident: $self_ty:ty $(, $($rest_args:tt)*)? ) $(-> $ret:ty)? $body:block @@ -151,7 +151,7 @@ macro_rules! __inner_declare_class { @($($($rest_args)*)?) $(#[$m])* - $v extern "C" fn $name( + extern "C" fn $name( $self: $self_ty, _cmd: $crate::objc2::runtime::Sel, $($($rest_args)*)? @@ -168,7 +168,7 @@ macro_rules! __inner_declare_class { $(#[$m:meta])* @sel($($sel:tt)+) - $v:vis fn $name:ident( + fn $name:ident( $($args:tt)* ) $(-> $ret:ty)? $body:block } => { @@ -181,7 +181,7 @@ macro_rules! __inner_declare_class { @($($args)*) $(#[$m])* - $v extern "C" fn $name( + extern "C" fn $name( _cls: &$crate::objc2::runtime::Class, _cmd: $crate::objc2::runtime::Sel, $($args)* @@ -416,6 +416,12 @@ macro_rules! __inner_declare_class { } /// TODO +/// +/// This macro is limited in a few spots, in particular: +/// - A transformation step is performed on the methods, and hence they should +/// not be called manually, only through a `msg_send!` (they can't be marked +/// `pub` nor `unsafe` for the same reason). +/// - ... #[macro_export] macro_rules! declare_class { { From e304d66bc89e93d830d085dc18eddee62265f40d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 21:51:36 +0200 Subject: [PATCH 09/21] Automatically declare class on first access --- objc2-foundation/examples/declaration.rs | 2 - objc2-foundation/src/declare_macro.rs | 49 +++++++++++++++--------- objc2-foundation/src/lib.rs | 3 ++ objc2-foundation/src/macros.rs | 18 ++++++--- objc2-foundation/src/object.rs | 8 +++- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index 4feaa2a8a..3f555d5a5 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -74,8 +74,6 @@ impl CustomAppDelegate { } fn main() { - let _cls = CustomAppDelegate::create_class(); // TMP - let delegate = CustomAppDelegate::new(42, true); println!("{}", delegate.ivar); diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index d5dbf107b..17443ee69 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -457,32 +457,43 @@ macro_rules! declare_class { // Creation impl $name { - fn create_class() -> &'static $crate::objc2::runtime::Class { - let superclass = <$inherits>::class(); - let mut builder = $crate::objc2::declare::ClassBuilder::new(stringify!($name), superclass).unwrap(); + fn class() -> &'static $crate::objc2::runtime::Class { + // TODO: Use `core::cell::LazyCell` + use $crate::__std::sync::Once; - // Implement protocols - $( + use $crate::objc2::declare::ClassBuilder; + use $crate::objc2::runtime::Protocol; + static REGISTER_CLASS: Once = Once::new(); + + REGISTER_CLASS.call_once(|| { + let superclass = <$inherits>::class(); + let mut builder = ClassBuilder::new(stringify!($name), superclass).unwrap(); + + // Implement protocols $( - builder.add_protocol($crate::objc2::runtime::Protocol::get(stringify!($protocols)).unwrap()); - )+ - )? + $( + builder.add_protocol(Protocol::get(stringify!($protocols)).unwrap()); + )+ + )? - $( - builder.add_ivar::<$ivar_ty>(stringify!($ivar)); - )* + $( + builder.add_ivar::<$ivar_ty>(stringify!($ivar)); + )* - unsafe { - $crate::__inner_declare_class! { - @rewrite_methods - @register_out - @builder + unsafe { + $crate::__inner_declare_class! { + @rewrite_methods + @register_out + @builder - $($methods)* + $($methods)* + } } - } - builder.register() + let _cls = builder.register(); + }); + + $crate::objc2::class!($name) } } diff --git a/objc2-foundation/src/lib.rs b/objc2-foundation/src/lib.rs index e090e15b9..e73d56ec4 100644 --- a/objc2-foundation/src/lib.rs +++ b/objc2-foundation/src/lib.rs @@ -71,8 +71,11 @@ pub use self::value::NSValue; #[doc(no_inline)] pub use objc2::ffi::{NSInteger, NSUInteger}; +// For macros #[doc(hidden)] pub use core as __core; +#[doc(hidden)] +pub extern crate std as __std; // Expose the version of objc2 that this crate uses pub use objc2; diff --git a/objc2-foundation/src/macros.rs b/objc2-foundation/src/macros.rs index 936684e41..dffbb3b8f 100644 --- a/objc2-foundation/src/macros.rs +++ b/objc2-foundation/src/macros.rs @@ -94,6 +94,12 @@ macro_rules! extern_class { $(#[$m])* unsafe $v struct $name<>: $($inheritance_chain,)+ $crate::objc2::runtime::Object {} } + + impl $name { + $v fn class() -> &'static $crate::objc2::runtime::Class { + $crate::objc2::class!($name) + } + } }; } @@ -161,6 +167,12 @@ macro_rules! __inner_extern_class { $($p: $pty,)* } } + + impl<$($t $(: $b)?),*> $name<$($t),*> { + $v fn class() -> &'static $crate::objc2::runtime::Class { + $crate::objc2::class!($name) + } + } }; ( @__inner @@ -185,12 +197,6 @@ macro_rules! __inner_extern_class { = <$inherits as $crate::objc2::RefEncode>::ENCODING_REF; } - impl<$($t $(: $b)?),*> $name<$($t),*> { - $v fn class() -> &'static $crate::objc2::runtime::Class { - $crate::objc2::class!($name) - } - } - // SAFETY: An instance can always be _used_ in exactly the same way as // it's superclasses (though not necessarily _constructed_ in the same // way, but `Deref` doesn't allow this). diff --git a/objc2-foundation/src/object.rs b/objc2-foundation/src/object.rs index b8dfbe274..974a68c80 100644 --- a/objc2-foundation/src/object.rs +++ b/objc2-foundation/src/object.rs @@ -3,7 +3,7 @@ use core::hash; use objc2::rc::{DefaultId, Id, Owned, Shared}; use objc2::runtime::{Class, Object}; -use objc2::{msg_send, msg_send_bool, msg_send_id}; +use objc2::{class, msg_send, msg_send_bool, msg_send_id}; use super::NSString; @@ -12,6 +12,12 @@ __inner_extern_class! { unsafe pub struct NSObject<>: Object {} } +impl NSObject { + pub fn class() -> &'static Class { + class!(NSObject) + } +} + impl NSObject { unsafe_def_fn!(pub fn new -> Owned); From d3a73d966d3a0998404dec7a719880cec1fdcfe7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 22:37:59 +0200 Subject: [PATCH 10/21] Document declare_class! macro --- objc2-foundation/src/declare_macro.rs | 202 +++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 7 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 17443ee69..f486357a0 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -415,13 +415,192 @@ macro_rules! __inner_declare_class { }; } -/// TODO +/// Declare a new Objective-C class. /// -/// This macro is limited in a few spots, in particular: -/// - A transformation step is performed on the methods, and hence they should -/// not be called manually, only through a `msg_send!` (they can't be marked -/// `pub` nor `unsafe` for the same reason). -/// - ... +/// This is mostly just a convenience macro on top of [`extern_class!`] and +/// the functionality in the [`objc2::declare`] module, but it can really help +/// with cutting down on boilerplate, in particular when defining delegate +/// classes! +/// +/// +/// # Specification +/// +/// This macro consists of two parts; the class definition, and the method +/// definition. +/// +/// +/// ## Class and ivar definition +/// +/// The class definition works a lot like [`extern_class!`], with the added +/// functionality that you can define custom instance variables on your class, +/// which are then wrapped in a [`objc2::runtime::Ivar`] and made accessible +/// through the class. (E.g. you can use `self.my_ivar` as if it was a normal +/// Rust struct). +/// +/// You can also specify the protocols that the class is expected to implement +/// +/// Note that the class name should be unique across the entire application! +/// As a tip, you can declare the class with the desired unique name like +/// `MyCrateCustomObject` using this macro, and then expose a renamed type +/// alias like `pub type CustomObject = MyCrateCustomObject;` instead. +/// +/// The class is guaranteed to have been created and registered with the +/// Objective-C runtime after the associated function `class` has been called. +/// +/// +/// ## Method definition +/// +/// Within the `impl` block you can define two types of functions; +/// ["associated functions"] and ["methods"]. These are then mapped to the +/// Objective-C equivalents "class methods" and "instance methods". In +/// particular, if you use `self` your method will be registered as an +/// instance method, and if you don't it will be registered as a class method. +/// +/// The desired selector can be specified using a special `@sel(my:selector:)` +/// directive directly before the function definition. +/// +/// A transformation step is performed on the functions (to make them have the +/// correct ABI) and hence they shouldn't really be called manually. (You +/// can't mark them as `pub` for the same reason). Instead, define a new +/// function that calls it via. [`objc2::msg_send!`]. +/// +/// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods +/// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods +/// +/// +/// # Safety +/// +/// Using this macro requires writing two `unsafe` markers: +/// +/// The one for the class definition has the following safety requirements: +/// - Same as [`extern_class!`] (the inheritance chain has to be correct). +/// - Any instance variables you specify must either be able to be created +/// using [`MaybeUninit::zeroed`], or be properly initialized in an `init` +/// method. +/// - All requirements (e.g. required methods) of the specified protocols must +/// be upheld. +/// +/// The one for the method implementation asserts that the types match those +/// that are expected when the method is invoked from Objective-C. Note that +/// there are no safe-guards here; you can easily write `i8`, but if +/// Objective-C thinks it's an `u32`, it will cause UB when called! +/// +/// [`MaybeUninit::zeroed`]: core::mem::MaybeUninit::zeroed +/// +/// +/// # Examples +/// +/// Declare a class `MyCustomObject` with a few instance variables and +/// methods. +/// +/// ``` +/// use std::os::raw::c_int; +/// use objc2::{msg_send, msg_send_bool, msg_send_id}; +/// use objc2::rc::{Id, Owned}; +/// use objc2::runtime::Bool; +/// use objc2_foundation::{declare_class, NSObject}; +/// # +/// # #[cfg(feature = "gnustep-1-7")] +/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; +/// +/// declare_class! { +/// unsafe struct MyCustomObject: NSObject { +/// foo: u8, +/// pub bar: c_int, +/// } +/// +/// unsafe impl { +/// @sel(initWithFoo:) +/// fn init_with(&mut self, foo: u8) -> Option<&mut Self> { +/// let this: Option<&mut Self> = unsafe { +/// msg_send![super(self, NSObject::class()), init] +/// }; +/// this.map(|this| { +/// // TODO: Initialization through MaybeUninit +/// // (The below is only safe because these variables are +/// // safe to initialize with `MaybeUninit::zeroed`). +/// *this.foo = foo; +/// *this.bar = 42; +/// this +/// }) +/// } +/// +/// @sel(foo) +/// fn __get_foo(&self) -> u8 { +/// *self.foo +/// } +/// +/// @sel(myClassMethod) +/// fn __my_class_method() -> Bool { +/// Bool::YES +/// } +/// } +/// } +/// +/// impl MyCustomObject { +/// pub fn new(foo: u8) -> Id { +/// let cls = Self::class(); +/// unsafe { msg_send_id![msg_send_id![cls, alloc], initWithFoo: foo,].unwrap() } +/// } +/// +/// pub fn get_foo(&self) -> u8 { +/// unsafe { msg_send![self, foo] } +/// } +/// +/// pub fn my_class_method() -> bool { +/// unsafe { msg_send_bool![Self::class(), myClassMethod] } +/// } +/// } +/// +/// fn main() { +/// let obj = MyCustomObject::new(3); +/// assert_eq!(*obj.foo, 3); +/// assert_eq!(*obj.bar, 42); +/// assert_eq!(obj.get_foo(), 3); +/// assert!(MyCustomObject::my_class_method()); +/// } +/// ``` +/// +/// Approximately equivalent to the following Objective-C code. +/// +/// ```text +/// #import +/// +/// @interface MyCustomObject: NSObject { +/// int bar; +/// } +/// +/// - (instancetype)initWithFoo:(uint8_t)foo; +/// - (uint8_t)foo; +/// + (BOOL)myClassMethod; +/// +/// @end +/// +/// +/// @implementation MyCustomObject { +/// // Private +/// uint8_t foo; +/// } +/// +/// - (instancetype)initWithFoo:(uint8_t)foo_arg { +/// self = [super init]; +/// if (self) { +/// self->foo = foo_arg; +/// self->bar = 42; +/// } +/// return self; +/// } +/// +/// - (uint8_t)foo { +/// return self->foo; // Or just `foo` +/// } +/// +/// + (BOOL)myClassMethod { +/// return YES; +/// } +/// +/// @end +/// ``` #[macro_export] macro_rules! declare_class { { @@ -450,7 +629,13 @@ macro_rules! declare_class { $crate::__inner_extern_class! { @__inner $(#[$m])* + // SAFETY: Upheld by caller unsafe $v struct $name<>: $inherits, $($inheritance_rest,)* $crate::objc2::runtime::Object { + // SAFETY: + // - The ivars are in a type used as an Objective-C object + // - The instance variable is defined in the exact same manner + // in `create_class`. + // - Rust prevents having two fields with the same name. $($ivar_v $ivar: $crate::objc2::declare::Ivar<$ivar>,)* } } @@ -477,9 +662,12 @@ macro_rules! declare_class { )? $( - builder.add_ivar::<$ivar_ty>(stringify!($ivar)); + builder.add_ivar::<<$ivar as $crate::objc2::declare::IvarType>::Type>( + <$ivar as $crate::objc2::declare::IvarType>::NAME + ); )* + // SAFETY: Upheld by caller unsafe { $crate::__inner_declare_class! { @rewrite_methods From 6d8dd0f973393292f7a64677efd46207e1285083 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 23:46:38 +0200 Subject: [PATCH 11/21] Remove support for accessing `_cmd` in declared methods For now at least, since this will allow us to easier change implementation details later on. May have to re-evaluate this decision at some point. --- objc2-foundation/src/declare_macro.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index f486357a0..3fd10d9c0 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -60,7 +60,7 @@ macro_rules! __inner_declare_class { $(#[$m])* extern "C" fn $name( &mut $self, - _cmd: $crate::objc2::runtime::Sel, + _: $crate::objc2::runtime::Sel, $($($rest_args)*)? ) $(-> $ret)? $body } @@ -89,7 +89,7 @@ macro_rules! __inner_declare_class { $(#[$m])* extern "C" fn $name( &$self, - _cmd: $crate::objc2::runtime::Sel, + _: $crate::objc2::runtime::Sel, $($($rest_args)*)? ) $(-> $ret)? $body } @@ -121,7 +121,7 @@ macro_rules! __inner_declare_class { $(#[$m])* extern "C" fn $name( mut $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel, + _: $crate::objc2::runtime::Sel, $($($rest_args)*)? ) $(-> $ret)? $body } @@ -153,7 +153,7 @@ macro_rules! __inner_declare_class { $(#[$m])* extern "C" fn $name( $self: $self_ty, - _cmd: $crate::objc2::runtime::Sel, + _: $crate::objc2::runtime::Sel, $($($rest_args)*)? ) $(-> $ret)? $body } @@ -182,8 +182,8 @@ macro_rules! __inner_declare_class { $(#[$m])* extern "C" fn $name( - _cls: &$crate::objc2::runtime::Class, - _cmd: $crate::objc2::runtime::Sel, + _: &$crate::objc2::runtime::Class, + _: $crate::objc2::runtime::Sel, $($args)* ) $(-> $ret)? $body } From a12ae7556946de9f74e60eaf19c09e1f1127e94e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 9 Jul 2022 23:55:41 +0200 Subject: [PATCH 12/21] Add NSZone --- objc2-foundation/CHANGELOG.md | 1 + objc2-foundation/src/lib.rs | 2 ++ objc2-foundation/src/zone.rs | 61 +++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 objc2-foundation/src/zone.rs diff --git a/objc2-foundation/CHANGELOG.md b/objc2-foundation/CHANGELOG.md index 33b08fa74..3836046f0 100644 --- a/objc2-foundation/CHANGELOG.md +++ b/objc2-foundation/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added `NSException` object. * Added `extern_class!` macro to help with defining other classes. * Expose the `objc2` version that this uses in the crate root. +* Added `NSZone`. ### Changed * Changed a few `Debug` impls. diff --git a/objc2-foundation/src/lib.rs b/objc2-foundation/src/lib.rs index e73d56ec4..c396bc5b7 100644 --- a/objc2-foundation/src/lib.rs +++ b/objc2-foundation/src/lib.rs @@ -65,6 +65,7 @@ pub use self::string::NSString; pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker, NSThread}; pub use self::uuid::NSUUID; pub use self::value::NSValue; +pub use self::zone::NSZone; // Available under Foundation, so makes sense here as well: // https://developer.apple.com/documentation/foundation/numbers_data_and_basic_values?language=objc @@ -111,3 +112,4 @@ mod string; mod thread; mod uuid; mod value; +mod zone; diff --git a/objc2-foundation/src/zone.rs b/objc2-foundation/src/zone.rs new file mode 100644 index 000000000..ad478c815 --- /dev/null +++ b/objc2-foundation/src/zone.rs @@ -0,0 +1,61 @@ +#[cfg(feature = "gnustep-1-7")] +use objc2::Encode; +use objc2::{Encoding, RefEncode}; + +/// A type used to identify and manage memory zones. +/// +/// Zones are ignored on all newer platforms, you should very rarely need to +/// use this. +/// +/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nszone?language=objc). +#[derive(Debug)] +pub struct NSZone { + _inner: [u8; 0], +} + +unsafe impl RefEncode for NSZone { + #[cfg(feature = "apple")] + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Encoding::Struct("_NSZone", &[])); + #[cfg(feature = "gnustep-1-7")] + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Encoding::Struct( + "_NSZone", + &[ + // Functions + Encoding::Pointer(&Encoding::Unknown), + Encoding::Pointer(&Encoding::Unknown), + Encoding::Pointer(&Encoding::Unknown), + Encoding::Pointer(&Encoding::Unknown), + Encoding::Pointer(&Encoding::Unknown), + Encoding::Pointer(&Encoding::Unknown), + // Stats + Encoding::Pointer(&Encoding::Unknown), + // Zone granularity + usize::ENCODING, + // Name of zone + Encoding::Object, + // Next zone + Encoding::Pointer(&Encoding::Struct("_NSZone", &[])), + ], + )); +} + +#[cfg(test)] +mod tests { + use crate::NSObject; + use core::ptr; + use objc2::msg_send_id; + use objc2::rc::{Allocated, Id, Owned}; + + use super::*; + + #[test] + #[cfg_attr( + feature = "gnustep-1-7", + ignore = "The encoding is not really correct yet!" + )] + fn alloc_with_zone() { + let zone: *const NSZone = ptr::null(); + let _obj: Id, Owned> = + unsafe { msg_send_id![NSObject::class(), allocWithZone: zone].unwrap() }; + } +} From 8382036764ddd3fab338520ac26419c2f54192f3 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 10 Jul 2022 00:34:15 +0200 Subject: [PATCH 13/21] Change protocol implementation syntax This allows using types in the inheritance chain again --- objc2-foundation/examples/declaration.rs | 11 +-- objc2-foundation/src/declare_macro.rs | 101 ++++++++++++++++++----- objc2-foundation/src/zone.rs | 3 +- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index 3f555d5a5..60e96307a 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -14,11 +14,6 @@ extern_class! { } declare_class! { - // For some reason, `NSApplicationDelegate` is not a "real" protocol we - // can retrieve using `objc_getProtocol` - it seems it is created by - // `clang` only when used in Objective-C... - // - // TODO: Investigate this! unsafe struct CustomAppDelegate: NSResponder, NSObject { pub ivar: u8, another_ivar: Bool, @@ -57,6 +52,12 @@ declare_class! { println!("A class method!"); } } + + // For some reason, `NSApplicationDelegate` is not a "real" protocol we + // can retrieve using `objc_getProtocol` - it seems it is created by + // `clang` only when used in Objective-C... + // + // TODO: Investigate this! } impl CustomAppDelegate { diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 3fd10d9c0..8e92dd8ca 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -425,8 +425,8 @@ macro_rules! __inner_declare_class { /// /// # Specification /// -/// This macro consists of two parts; the class definition, and the method -/// definition. +/// This macro consists of three parts; the class definition, the method +/// definition, and the protocol definition. /// /// /// ## Class and ivar definition @@ -437,8 +437,6 @@ macro_rules! __inner_declare_class { /// through the class. (E.g. you can use `self.my_ivar` as if it was a normal /// Rust struct). /// -/// You can also specify the protocols that the class is expected to implement -/// /// Note that the class name should be unique across the entire application! /// As a tip, you can declare the class with the desired unique name like /// `MyCrateCustomObject` using this macro, and then expose a renamed type @@ -468,37 +466,49 @@ macro_rules! __inner_declare_class { /// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods /// /// +/// ## Protocol definition +/// +/// You can specify the protocols that the class should implement, along with +/// any required methods for said protocols. +/// +/// The methods work exactly as above, they're only put here to make things +/// easier to read. +/// +/// /// # Safety /// -/// Using this macro requires writing two `unsafe` markers: +/// Using this macro requires writing a few `unsafe` markers: /// /// The one for the class definition has the following safety requirements: /// - Same as [`extern_class!`] (the inheritance chain has to be correct). /// - Any instance variables you specify must either be able to be created /// using [`MaybeUninit::zeroed`], or be properly initialized in an `init` /// method. -/// - All requirements (e.g. required methods) of the specified protocols must -/// be upheld. /// /// The one for the method implementation asserts that the types match those /// that are expected when the method is invoked from Objective-C. Note that /// there are no safe-guards here; you can easily write `i8`, but if /// Objective-C thinks it's an `u32`, it will cause UB when called! /// +/// The one for the protocol definition requires that all required methods of +/// the specified protocol is implemented, and that any extra requirements +/// (implicit or explicit) that the protocol has are upheld. The methods in +/// this definition has the same safety requirements as above. +/// /// [`MaybeUninit::zeroed`]: core::mem::MaybeUninit::zeroed /// /// /// # Examples /// -/// Declare a class `MyCustomObject` with a few instance variables and -/// methods. +/// Declare a class `MyCustomObject` that inherits `NSObject`, has a few +/// instance variables and methods, and implements the `NSCopying` protocol. /// /// ``` /// use std::os::raw::c_int; /// use objc2::{msg_send, msg_send_bool, msg_send_id}; /// use objc2::rc::{Id, Owned}; /// use objc2::runtime::Bool; -/// use objc2_foundation::{declare_class, NSObject}; +/// use objc2_foundation::{declare_class, NSCopying, NSObject, NSZone}; /// # /// # #[cfg(feature = "gnustep-1-7")] /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; @@ -535,6 +545,15 @@ macro_rules! __inner_declare_class { /// Bool::YES /// } /// } +/// +/// unsafe impl protocol NSCopying { +/// @sel(copyWithZone:) +/// fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self { +/// let mut obj = Self::new(*self.foo); +/// *obj.bar = *self.bar; +/// obj.autorelease_return() +/// } +/// } /// } /// /// impl MyCustomObject { @@ -552,11 +571,19 @@ macro_rules! __inner_declare_class { /// } /// } /// +/// unsafe impl NSCopying for MyCustomObject { +/// type Ownership = Owned; +/// type Output = Self; +/// } +/// /// fn main() { /// let obj = MyCustomObject::new(3); /// assert_eq!(*obj.foo, 3); /// assert_eq!(*obj.bar, 42); +/// +/// let obj = obj.copy(); /// assert_eq!(obj.get_foo(), 3); +/// /// assert!(MyCustomObject::my_class_method()); /// } /// ``` @@ -566,7 +593,7 @@ macro_rules! __inner_declare_class { /// ```text /// #import /// -/// @interface MyCustomObject: NSObject { +/// @interface MyCustomObject: NSObject { /// int bar; /// } /// @@ -599,13 +626,19 @@ macro_rules! __inner_declare_class { /// return YES; /// } /// +/// - (id)copyWithZone:(NSZone *)_zone { +/// MyCustomObject* obj = [[MyCustomObject alloc] initWithFoo: self->foo]; +/// obj->bar = self->bar; +/// return obj; +/// } +/// /// @end /// ``` #[macro_export] macro_rules! declare_class { { $(#[$m:meta])* - unsafe $v:vis struct $name:ident: $inherits:ident $(, $inheritance_rest:ident)* $(<$($protocols:ident),+ $(,)?>)? { + unsafe $v:vis struct $name:ident: $inherits:ty $(, $inheritance_rest:ty)* { $($ivar_v:vis $ivar:ident: $ivar_ty:ty,)* } @@ -613,6 +646,13 @@ macro_rules! declare_class { unsafe impl { $($methods:tt)* } + + $( + $(#[$impl_protocol_m:meta])* + unsafe impl protocol $protocols:ident { + $($protocol_methods:tt)* + } + ),* } => { $( #[allow(non_camel_case_types)] @@ -654,13 +694,6 @@ macro_rules! declare_class { let superclass = <$inherits>::class(); let mut builder = ClassBuilder::new(stringify!($name), superclass).unwrap(); - // Implement protocols - $( - $( - builder.add_protocol(Protocol::get(stringify!($protocols)).unwrap()); - )+ - )? - $( builder.add_ivar::<<$ivar as $crate::objc2::declare::IvarType>::Type>( <$ivar as $crate::objc2::declare::IvarType>::NAME @@ -678,6 +711,22 @@ macro_rules! declare_class { } } + // Implement protocols + $( + builder.add_protocol(Protocol::get(stringify!($protocols)).unwrap()); + + // SAFETY: Upheld by caller + unsafe { + $crate::__inner_declare_class! { + @rewrite_methods + @register_out + @builder + + $($protocol_methods)* + } + } + )* + let _cls = builder.register(); }); @@ -696,5 +745,19 @@ macro_rules! declare_class { $($methods)* } } + + // Protocol methods + $( + $(#[$impl_protocol_m])* + impl $name { + $crate::__inner_declare_class! { + @rewrite_methods + @method_out + @__builder + + $($protocol_methods)* + } + } + )* }; } diff --git a/objc2-foundation/src/zone.rs b/objc2-foundation/src/zone.rs index ad478c815..465c1a395 100644 --- a/objc2-foundation/src/zone.rs +++ b/objc2-foundation/src/zone.rs @@ -5,7 +5,8 @@ use objc2::{Encoding, RefEncode}; /// A type used to identify and manage memory zones. /// /// Zones are ignored on all newer platforms, you should very rarely need to -/// use this. +/// use this, but may be useful if you need to implement `copyWithZone:` or +/// `allocWithZone:`. /// /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nszone?language=objc). #[derive(Debug)] From 02177d4eea46470bd90cc90745505c22871b350b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 10 Jul 2022 00:09:59 +0200 Subject: [PATCH 14/21] Add CHANGELOG entry --- objc2-foundation/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/objc2-foundation/CHANGELOG.md b/objc2-foundation/CHANGELOG.md index 3836046f0..6f23ac412 100644 --- a/objc2-foundation/CHANGELOG.md +++ b/objc2-foundation/CHANGELOG.md @@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added `MainThreadMarker` to help with designing APIs where a method is only safe to call on the main thread. * Added `NSException` object. -* Added `extern_class!` macro to help with defining other classes. +* Added `extern_class!` macro to help with defining interfaces to classes. +* Added `declare_class!` macro to help with declaring custom classes. * Expose the `objc2` version that this uses in the crate root. * Added `NSZone`. From 0ea044cceb49426d1a3f0dbaaf92ea2708528b22 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 10 Jul 2022 00:58:26 +0200 Subject: [PATCH 15/21] Fix visibility of function `class` in declare_class! macro --- objc2-foundation/src/declare_macro.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 8e92dd8ca..e347072b6 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -682,7 +682,7 @@ macro_rules! declare_class { // Creation impl $name { - fn class() -> &'static $crate::objc2::runtime::Class { + $v fn class() -> &'static $crate::objc2::runtime::Class { // TODO: Use `core::cell::LazyCell` use $crate::__std::sync::Once; From 8635d20e97a348b3ffc68fc351762b184ad18174 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 10 Jul 2022 01:36:55 +0200 Subject: [PATCH 16/21] Support `_` in argument names --- objc2-foundation/examples/declaration.rs | 2 +- objc2-foundation/src/declare_macro.rs | 156 +++++++++++------------ 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index 60e96307a..a19c1e9c3 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -43,7 +43,7 @@ declare_class! { } @sel(applicationWillTerminate:) - fn will_terminate(&self, _sender: *mut Object) { + fn will_terminate(&self, _: *mut Object) { println!("Will terminate!"); } diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index e347072b6..85a763350 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -255,7 +255,7 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _) -> _ }; @@ -263,8 +263,8 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _) -> _ }; @@ -272,9 +272,9 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _) -> _ }; @@ -282,10 +282,10 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _) -> _ }; @@ -293,11 +293,11 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _) -> _ }; @@ -305,12 +305,12 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _) -> _ }; @@ -318,13 +318,13 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _) -> _ }; @@ -332,14 +332,14 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty, - $param8:ident: $param8_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty, + $($param8:ident)? $(_)?: $param8_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _) -> _ }; @@ -347,15 +347,15 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty, - $param8:ident: $param8_ty:ty, - $param9:ident: $param9_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty, + $($param8:ident)? $(_)?: $param8_ty:ty, + $($param9:ident)? $(_)?: $param9_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _) -> _ }; @@ -363,16 +363,16 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty, - $param8:ident: $param8_ty:ty, - $param9:ident: $param9_ty:ty, - $param10:ident: $param10_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty, + $($param8:ident)? $(_)?: $param8_ty:ty, + $($param9:ident)? $(_)?: $param9_ty:ty, + $($param10:ident)? $(_)?: $param10_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _) -> _ }; @@ -380,17 +380,17 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty, - $param8:ident: $param8_ty:ty, - $param9:ident: $param9_ty:ty, - $param10:ident: $param10_ty:ty, - $param11:ident: $param11_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty, + $($param8:ident)? $(_)?: $param8_ty:ty, + $($param9:ident)? $(_)?: $param9_ty:ty, + $($param10:ident)? $(_)?: $param10_ty:ty, + $($param11:ident)? $(_)?: $param11_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _, _) -> _ }; @@ -398,18 +398,18 @@ macro_rules! __inner_declare_class { @cast_extern_fn @$name:ident - $param1:ident: $param1_ty:ty, - $param2:ident: $param2_ty:ty, - $param3:ident: $param3_ty:ty, - $param4:ident: $param4_ty:ty, - $param5:ident: $param5_ty:ty, - $param6:ident: $param6_ty:ty, - $param7:ident: $param7_ty:ty, - $param8:ident: $param8_ty:ty, - $param9:ident: $param9_ty:ty, - $param10:ident: $param10_ty:ty, - $param11:ident: $param11_ty:ty, - $param12:ident: $param12_ty:ty $(,)? + $($param1:ident)? $(_)?: $param1_ty:ty, + $($param2:ident)? $(_)?: $param2_ty:ty, + $($param3:ident)? $(_)?: $param3_ty:ty, + $($param4:ident)? $(_)?: $param4_ty:ty, + $($param5:ident)? $(_)?: $param5_ty:ty, + $($param6:ident)? $(_)?: $param6_ty:ty, + $($param7:ident)? $(_)?: $param7_ty:ty, + $($param8:ident)? $(_)?: $param8_ty:ty, + $($param9:ident)? $(_)?: $param9_ty:ty, + $($param10:ident)? $(_)?: $param10_ty:ty, + $($param11:ident)? $(_)?: $param11_ty:ty, + $($param12:ident)? $(_)?: $param12_ty:ty $(,)? } => { Self::$name as extern "C" fn(_, _, _, _, _, _, _, _, _, _, _, _, _, _) -> _ }; From 63d3d0fc56e1781ae1a9ca964cfc761b5e3a5aaf Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 10 Jul 2022 02:19:40 +0200 Subject: [PATCH 17/21] Print selector name in declare assertion --- objc2/src/declare.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/objc2/src/declare.rs b/objc2/src/declare.rs index a20f6bbb0..f189abe9a 100644 --- a/objc2/src/declare.rs +++ b/objc2/src/declare.rs @@ -265,7 +265,8 @@ impl ClassBuilder { assert_eq!( sel_args, encs.len(), - "Selector accepts {} arguments, but function accepts {}", + "Selector {:?} accepts {} arguments, but function accepts {}", + sel, sel_args, encs.len(), ); @@ -306,7 +307,8 @@ impl ClassBuilder { assert_eq!( sel_args, encs.len(), - "Selector accepts {} arguments, but function accepts {}", + "Selector {:?} accepts {} arguments, but function accepts {}", + sel, sel_args, encs.len(), ); @@ -417,7 +419,8 @@ impl ProtocolBuilder { assert_eq!( sel_args, encs.len(), - "Selector accepts {} arguments, but function accepts {}", + "Selector {:?} accepts {} arguments, but function accepts {}", + sel, sel_args, encs.len(), ); From 7bede3ff434ecd41fb98f596b138fa61cf7c684a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 11 Jul 2022 00:56:50 +0200 Subject: [PATCH 18/21] Improve error messages in declare_class! macro --- objc2-foundation/src/declare_macro.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 85a763350..c7c0697cd 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -692,7 +692,12 @@ macro_rules! declare_class { REGISTER_CLASS.call_once(|| { let superclass = <$inherits>::class(); - let mut builder = ClassBuilder::new(stringify!($name), superclass).unwrap(); + let err_str = concat!( + "could not create new class ", + stringify!($name), + ". Perhaps a class with that name already exists?", + ); + let mut builder = ClassBuilder::new(stringify!($name), superclass).expect(err_str); $( builder.add_ivar::<<$ivar as $crate::objc2::declare::IvarType>::Type>( @@ -713,7 +718,8 @@ macro_rules! declare_class { // Implement protocols $( - builder.add_protocol(Protocol::get(stringify!($protocols)).unwrap()); + let err_str = concat!("could not find protocol ", stringify!($protocols)); + builder.add_protocol(Protocol::get(stringify!($protocols)).expect(err_str)); // SAFETY: Upheld by caller unsafe { From f19719b8febf5e972837fc21b8115fbe1a9dbd80 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 17 Jul 2022 00:53:47 +0200 Subject: [PATCH 19/21] Provide more flexibility for impl blocks --- objc2-foundation/examples/declaration.rs | 21 ++++---- objc2-foundation/src/declare_macro.rs | 66 +++++++----------------- 2 files changed, 31 insertions(+), 56 deletions(-) diff --git a/objc2-foundation/examples/declaration.rs b/objc2-foundation/examples/declaration.rs index a19c1e9c3..2f1ebdbf9 100644 --- a/objc2-foundation/examples/declaration.rs +++ b/objc2-foundation/examples/declaration.rs @@ -37,16 +37,6 @@ declare_class! { this } - @sel(applicationDidFinishLaunching:) - fn did_finish_launching(&self, _sender: *mut Object) { - println!("Did finish launching!"); - } - - @sel(applicationWillTerminate:) - fn will_terminate(&self, _: *mut Object) { - println!("Will terminate!"); - } - @sel(myClassMethod) fn my_class_method() { println!("A class method!"); @@ -58,6 +48,17 @@ declare_class! { // `clang` only when used in Objective-C... // // TODO: Investigate this! + unsafe impl { + @sel(applicationDidFinishLaunching:) + fn did_finish_launching(&self, _sender: *mut Object) { + println!("Did finish launching!"); + } + + @sel(applicationWillTerminate:) + fn will_terminate(&self, _: *mut Object) { + println!("Will terminate!"); + } + } } impl CustomAppDelegate { diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index c7c0697cd..db23ac153 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -471,26 +471,26 @@ macro_rules! __inner_declare_class { /// You can specify the protocols that the class should implement, along with /// any required methods for said protocols. /// -/// The methods work exactly as above, they're only put here to make things -/// easier to read. +/// The methods work exactly as normal, they're only put "under" the protocol +/// definition to make things easier to read. /// /// /// # Safety /// /// Using this macro requires writing a few `unsafe` markers: /// -/// The one for the class definition has the following safety requirements: +/// `unsafe struct ...` has the following safety requirements: /// - Same as [`extern_class!`] (the inheritance chain has to be correct). /// - Any instance variables you specify must either be able to be created /// using [`MaybeUninit::zeroed`], or be properly initialized in an `init` /// method. /// -/// The one for the method implementation asserts that the types match those -/// that are expected when the method is invoked from Objective-C. Note that -/// there are no safe-guards here; you can easily write `i8`, but if -/// Objective-C thinks it's an `u32`, it will cause UB when called! +/// `unsafe impl { ... }` asserts that the types match those that are expected +/// when the method is invoked from Objective-C. Note that there are no +/// safe-guards here; you can easily write `i8`, but if Objective-C thinks +/// it's an `u32`, it will cause UB when called! /// -/// The one for the protocol definition requires that all required methods of +/// `unsafe impl protocol ... { ... }` requires that all required methods of /// the specified protocol is implemented, and that any extra requirements /// (implicit or explicit) that the protocol has are upheld. The methods in /// this definition has the same safety requirements as above. @@ -642,17 +642,12 @@ macro_rules! declare_class { $($ivar_v:vis $ivar:ident: $ivar_ty:ty,)* } - $(#[$impl_m:meta])* - unsafe impl { - $($methods:tt)* - } - $( - $(#[$impl_protocol_m:meta])* - unsafe impl protocol $protocols:ident { - $($protocol_methods:tt)* + $(#[$impl_m:meta])* + unsafe impl $(protocol $protocol:ident)? { + $($methods:tt)* } - ),* + )* } => { $( #[allow(non_camel_case_types)] @@ -705,21 +700,12 @@ macro_rules! declare_class { ); )* - // SAFETY: Upheld by caller - unsafe { - $crate::__inner_declare_class! { - @rewrite_methods - @register_out - @builder - - $($methods)* - } - } - - // Implement protocols $( - let err_str = concat!("could not find protocol ", stringify!($protocols)); - builder.add_protocol(Protocol::get(stringify!($protocols)).expect(err_str)); + // Implement protocols + $( + let err_str = concat!("could not find protocol ", stringify!($protocol)); + builder.add_protocol(Protocol::get(stringify!($protocol)).expect(err_str)); + )? // SAFETY: Upheld by caller unsafe { @@ -728,7 +714,7 @@ macro_rules! declare_class { @register_out @builder - $($protocol_methods)* + $($methods)* } } )* @@ -741,27 +727,15 @@ macro_rules! declare_class { } // Methods - $(#[$impl_m])* - impl $name { - $crate::__inner_declare_class! { - @rewrite_methods - @method_out - @__builder - - $($methods)* - } - } - - // Protocol methods $( - $(#[$impl_protocol_m])* + $(#[$impl_m])* impl $name { $crate::__inner_declare_class! { @rewrite_methods @method_out @__builder - $($protocol_methods)* + $($methods)* } } )* From ac378d4275e92c42916583839fa66168a77e8091 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 17 Jul 2022 01:16:41 +0200 Subject: [PATCH 20/21] Comment cleanup --- objc2-foundation/src/declare_macro.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index db23ac153..6daaa0a3f 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -559,7 +559,7 @@ macro_rules! __inner_declare_class { /// impl MyCustomObject { /// pub fn new(foo: u8) -> Id { /// let cls = Self::class(); -/// unsafe { msg_send_id![msg_send_id![cls, alloc], initWithFoo: foo,].unwrap() } +/// unsafe { msg_send_id![msg_send_id![cls, alloc], initWithFoo: foo].unwrap() } /// } /// /// pub fn get_foo(&self) -> u8 { @@ -594,6 +594,7 @@ macro_rules! __inner_declare_class { /// #import /// /// @interface MyCustomObject: NSObject { +/// // Public ivar /// int bar; /// } /// @@ -605,7 +606,7 @@ macro_rules! __inner_declare_class { /// /// /// @implementation MyCustomObject { -/// // Private +/// // Private ivar /// uint8_t foo; /// } /// @@ -626,6 +627,8 @@ macro_rules! __inner_declare_class { /// return YES; /// } /// +/// // NSCopying +/// /// - (id)copyWithZone:(NSZone *)_zone { /// MyCustomObject* obj = [[MyCustomObject alloc] initWithFoo: self->foo]; /// obj->bar = self->bar; @@ -667,10 +670,11 @@ macro_rules! declare_class { // SAFETY: Upheld by caller unsafe $v struct $name<>: $inherits, $($inheritance_rest,)* $crate::objc2::runtime::Object { // SAFETY: - // - The ivars are in a type used as an Objective-C object + // - The ivars are in a type used as an Objective-C object. // - The instance variable is defined in the exact same manner - // in `create_class`. + // in `class` below. // - Rust prevents having two fields with the same name. + // - Caller upholds that the ivars are properly initialized. $($ivar_v $ivar: $crate::objc2::declare::Ivar<$ivar>,)* } } @@ -701,12 +705,13 @@ macro_rules! declare_class { )* $( - // Implement protocols + // Implement protocol if any specified $( let err_str = concat!("could not find protocol ", stringify!($protocol)); builder.add_protocol(Protocol::get(stringify!($protocol)).expect(err_str)); )? + // Implement methods // SAFETY: Upheld by caller unsafe { $crate::__inner_declare_class! { From 1ac9617318bd66905c7a563c8fb028392c83611f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 17 Jul 2022 01:40:00 +0200 Subject: [PATCH 21/21] Document the associated function `class` --- objc2-foundation/src/declare_macro.rs | 8 ++++++++ objc2-foundation/src/macros.rs | 18 ++++++++++++++++++ objc2-foundation/src/object.rs | 2 ++ 3 files changed, 28 insertions(+) diff --git a/objc2-foundation/src/declare_macro.rs b/objc2-foundation/src/declare_macro.rs index 6daaa0a3f..d3c9beb97 100644 --- a/objc2-foundation/src/declare_macro.rs +++ b/objc2-foundation/src/declare_macro.rs @@ -681,6 +681,14 @@ macro_rules! declare_class { // Creation impl $name { + #[doc = concat!( + "Get a reference to the Objective-C class `", + stringify!($name), + "`.", + "\n\n", + "May register the class if it wasn't already.", + )] + // TODO: Allow users to configure this? $v fn class() -> &'static $crate::objc2::runtime::Class { // TODO: Use `core::cell::LazyCell` use $crate::__std::sync::Once; diff --git a/objc2-foundation/src/macros.rs b/objc2-foundation/src/macros.rs index dffbb3b8f..aa424d015 100644 --- a/objc2-foundation/src/macros.rs +++ b/objc2-foundation/src/macros.rs @@ -13,6 +13,10 @@ /// The traits [`objc2::RefEncode`] and [`objc2::Message`] are implemented to /// allow sending messages to the object and using it in [`objc2::rc::Id`]. /// +/// An associated function `class` is created on the object as a convenient +/// shorthand so that you can do `MyObject::class()` instead of +/// `class!(MyObject)`. +/// /// [`Deref`] and [`DerefMut`] are implemented and delegate to the first /// superclass (direct parent). Auto traits are inherited from this superclass /// as well (this macro effectively just creates a newtype wrapper around the @@ -96,6 +100,13 @@ macro_rules! extern_class { } impl $name { + #[doc = concat!( + "Get a reference to the Objective-C class `", + stringify!($name), + "`.", + )] + #[inline] + // TODO: Allow users to configure this? $v fn class() -> &'static $crate::objc2::runtime::Class { $crate::objc2::class!($name) } @@ -169,6 +180,13 @@ macro_rules! __inner_extern_class { } impl<$($t $(: $b)?),*> $name<$($t),*> { + #[doc = concat!( + "Get a reference to the Objective-C class `", + stringify!($name), + "`.", + )] + #[inline] + // TODO: Allow users to configure this? $v fn class() -> &'static $crate::objc2::runtime::Class { $crate::objc2::class!($name) } diff --git a/objc2-foundation/src/object.rs b/objc2-foundation/src/object.rs index 974a68c80..2c8484ce7 100644 --- a/objc2-foundation/src/object.rs +++ b/objc2-foundation/src/object.rs @@ -13,6 +13,8 @@ __inner_extern_class! { } impl NSObject { + /// Get a reference to the Objective-C class `NSObject`. + #[inline] pub fn class() -> &'static Class { class!(NSObject) }