Skip to content

Commit

Permalink
Add support for out parameters like &mut Id<_, _> in msg_send!
Browse files Browse the repository at this point in the history
Supported types:
- `&mut Id<_, _>`
- `&mut Option<Id<_, _>>`
- `Option<&mut Id<_, _>>`
- `Option<&mut Option<Id<_, _>>>`
  • Loading branch information
madsmtm committed Feb 1, 2023
1 parent 3458c9a commit 1a4dfe7
Show file tree
Hide file tree
Showing 42 changed files with 5,690 additions and 80 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ resolver = "2"
inherits = "release"
# Enable LTO to allow testing the `unstable-static-sel-inlined` feature
lto = true
# Don't emit unwind info; while important to get right, the control flow is
# very hard to glean from assembly output.
panic = "abort"
2 changes: 2 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* **BREAKING**: Moved the `objc2_encode` traits to `objc2::encode`.

This includes removing the `EncodeConvert` and `EncodeArguments` traits.
* Added support for out-parameters like `&mut Id<_, _>` in `msg_send!`,
`msg_send_id!` and `extern_methods!`.

### Changed
* **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in
Expand Down
10 changes: 5 additions & 5 deletions crates/objc2/src/__macro_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,15 @@ pub trait MsgSendId<T, U> {
if let Some(res) = res {
// In this case, the error is likely not created. If it is, it is
// autoreleased anyhow, so it would be a waste to retain and
// release it.
// release it here.
Ok(res)
} else {
// In this case, the error has very likely been created, but has
// been autoreleased (as is common for "out parameters"). Hence we
// need to retain it if we want it to live across autorelease
// pools.
// been autoreleased (as is common for "out parameters", see
// `src/rc/writeback.rs`). Hence we need to retain it if we want
// it to live across autorelease pools.
//
// SAFETY: The closure `f` is guaranteed to populate the error
// SAFETY: The message send is guaranteed to populate the error
// object, or leave it as NULL. The error is shared, and all
// holders of the error know this, so is safe to retain.
Err(unsafe { encountered_error(err) })
Expand Down
68 changes: 68 additions & 0 deletions crates/objc2/src/declare/declare_class_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,71 @@ fn test_subclass_duplicate_ivar() {
let ivar_dynamically = unsafe { obj.ivar::<i32>("ivar") };
assert_eq!(*ivar_dynamically, 3);
}

declare_class!(
struct OutParam;

unsafe impl ClassType for OutParam {
type Super = NSObject;
const NAME: &'static str = "OutParam";
}

unsafe impl OutParam {
#[method(unsupported1:)]
fn _unsupported1(_param: &mut Id<Self, Shared>) {}

#[method(unsupported2:)]
fn _unsupported2(_param: Option<&mut Id<Self, Shared>>) {}

#[method(unsupported3:)]
fn _unsupported3(_param: &mut Option<Id<Self, Shared>>) {}

#[method(unsupported4:)]
fn _unsupported4(_param: Option<&mut Option<Id<Self, Shared>>>) {}
}
);

extern_methods!(
unsafe impl OutParam {
#[method_id(new)]
fn new() -> Id<Self, Shared>;

#[method(unsupported1:)]
fn unsupported1(_param: &mut Id<Self, Shared>);

#[method(unsupported2:)]
fn unsupported2(_param: Option<&mut Id<Self, Shared>>);

#[method(unsupported3:)]
fn unsupported3(_param: &mut Option<Id<Self, Shared>>);

#[method(unsupported4:)]
fn unsupported4(_param: Option<&mut Option<Id<Self, Shared>>>);
}
);

#[test]
#[should_panic = "`&mut Id<_, _>` is not supported in `declare_class!` yet"]
fn out_param1() {
let mut param = OutParam::new();
OutParam::unsupported1(&mut param);
}

#[test]
#[should_panic = "`Option<&mut Id<_, _>>` is not supported in `declare_class!` yet"]
fn out_param2() {
OutParam::unsupported2(None);
}

#[test]
#[should_panic = "`&mut Option<Id<_, _>>` is not supported in `declare_class!` yet"]
fn out_param3() {
let mut param = Some(OutParam::new());
OutParam::unsupported3(&mut param);
}

#[test]
#[should_panic = "`Option<&mut Option<Id<_, _>>>` is not supported in `declare_class!` yet"]
fn out_param4() {
OutParam::unsupported4(None);
}
131 changes: 80 additions & 51 deletions crates/objc2/src/encode/__unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
)]

use crate::encode::{Encode, Encoding};
use crate::rc::{Id, Ownership};
use crate::runtime::Bool;
use crate::Message;

mod return_private {
pub trait Sealed {}
Expand All @@ -28,18 +30,18 @@ mod return_private {
///
/// We currently don't need a similar `EncodeArgument` trait, but we might in
/// the future.
pub unsafe trait EncodeReturn: return_private::Sealed {
pub trait EncodeReturn: return_private::Sealed {
/// The Objective-C type-encoding for this type.
const ENCODING_RETURN: Encoding;
}

impl return_private::Sealed for () {}
unsafe impl EncodeReturn for () {
impl EncodeReturn for () {
const ENCODING_RETURN: Encoding = Encoding::Void;
}

impl<T: Encode> return_private::Sealed for T {}
unsafe impl<T: Encode> EncodeReturn for T {
impl<T: Encode> EncodeReturn for T {
const ENCODING_RETURN: Encoding = T::ENCODING;
}

Expand All @@ -50,89 +52,109 @@ mod convert_private {
impl<T: EncodeReturn> convert_private::Sealed for T {}
impl convert_private::Sealed for bool {}

// Implemented in rc/writeback.rs
impl<T: Message, O: Ownership> convert_private::Sealed for &mut Id<T, O> {}
impl<T: Message, O: Ownership> convert_private::Sealed for Option<&mut Id<T, O>> {}
impl<T: Message, O: Ownership> convert_private::Sealed for &mut Option<Id<T, O>> {}
impl<T: Message, O: Ownership> convert_private::Sealed for Option<&mut Option<Id<T, O>>> {}

/// Represents types that can easily be converted to/from an [`Encode`] type.
///
/// This is implemented specially for [`bool`] to allow using that as
/// Objective-C `BOOL`, where it would otherwise not be allowed (since they
/// are not ABI compatible).
///
/// This is also done specially for `&mut Id<_, _>`-like arguments, to allow
/// using those as "out" parameters.
pub trait EncodeConvertArgument: convert_private::Sealed {
/// The inner type that this can be converted to and from.
#[doc(hidden)]
type __Inner: Encode;

/// A helper type for out parameters.
#[doc(hidden)]
type __StoredBeforeMessage: Sized;

#[doc(hidden)]
fn __from_inner(inner: Self::__Inner) -> Self;
fn __from_declared_param(inner: Self::__Inner) -> Self;

#[doc(hidden)]
fn __into_inner(self) -> Self::__Inner;
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage);

#[doc(hidden)]
unsafe fn __process_after_message_send(_stored: Self::__StoredBeforeMessage) {}
}

/// Same as [`EncodeConvertArgument`], but for return types.
pub trait EncodeConvertReturn: convert_private::Sealed {
/// The inner type that this can be converted to and from.
#[doc(hidden)]
type __Inner: EncodeReturn;

#[doc(hidden)]
fn __into_declared_return(self) -> Self::__Inner;

#[doc(hidden)]
fn __from_return(inner: Self::__Inner) -> Self;
}

impl<T: Encode> EncodeConvertArgument for T {
type __Inner = Self;

type __StoredBeforeMessage = ();

#[inline]
fn __from_inner(inner: Self::__Inner) -> Self {
fn __from_declared_param(inner: Self::__Inner) -> Self {
inner
}

#[inline]
fn __into_inner(self) -> Self::__Inner {
self
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
(self, ())
}
}

impl EncodeConvertArgument for bool {
type __Inner = Bool;
impl<T: EncodeReturn> EncodeConvertReturn for T {
type __Inner = Self;

#[inline]
fn __from_inner(inner: Self::__Inner) -> Self {
inner.as_bool()
fn __into_declared_return(self) -> Self::__Inner {
self
}

#[inline]
fn __into_inner(self) -> Self::__Inner {
Bool::new(self)
fn __from_return(inner: Self::__Inner) -> Self {
inner
}
}

/// Same as [`EncodeConvertArgument`], but for return types.
pub trait EncodeConvertReturn: convert_private::Sealed {
/// The inner type that this can be converted to and from.
#[doc(hidden)]
type __Inner: EncodeReturn;

#[doc(hidden)]
fn __from_inner(inner: Self::__Inner) -> Self;

#[doc(hidden)]
fn __into_inner(self) -> Self::__Inner;
}
impl EncodeConvertArgument for bool {
type __Inner = Bool;

impl<T: EncodeReturn> EncodeConvertReturn for T {
type __Inner = Self;
type __StoredBeforeMessage = ();

#[inline]
fn __from_inner(inner: Self::__Inner) -> Self {
inner
fn __from_declared_param(inner: Self::__Inner) -> Self {
inner.as_bool()
}

#[inline]
fn __into_inner(self) -> Self::__Inner {
self
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
(Bool::new(self), ())
}
}

impl EncodeConvertReturn for bool {
type __Inner = Bool;

#[inline]
fn __from_inner(inner: Self::__Inner) -> Self {
inner.as_bool()
fn __into_declared_return(self) -> Self::__Inner {
Bool::new(self)
}

#[inline]
fn __into_inner(self) -> Self::__Inner {
Bool::new(self)
fn __from_return(inner: Self::__Inner) -> Self {
inner.as_bool()
}
}

Expand All @@ -149,7 +171,7 @@ mod args_private {
///
/// Note that tuples themselves don't implement [`Encode`] directly, because
/// they're not FFI-safe!
pub unsafe trait EncodeArguments: args_private::Sealed {
pub trait EncodeArguments: args_private::Sealed {
/// The encodings for the arguments.
const ENCODINGS: &'static [Encoding];
}
Expand All @@ -158,7 +180,7 @@ macro_rules! encode_args_impl {
($($Arg: ident),*) => {
impl<$($Arg: EncodeConvertArgument),*> args_private::Sealed for ($($Arg,)*) {}

unsafe impl<$($Arg: EncodeConvertArgument),*> EncodeArguments for ($($Arg,)*) {
impl<$($Arg: EncodeConvertArgument),*> EncodeArguments for ($($Arg,)*) {
const ENCODINGS: &'static [Encoding] = &[
// T::__Inner::ENCODING => T::ENCODING
// bool::__Inner::ENCODING => Bool::ENCODING
Expand Down Expand Up @@ -204,8 +226,11 @@ mod tests {
TypeId::of::<<i32 as EncodeConvertArgument>::__Inner>(),
TypeId::of::<i32>()
);
assert_eq!(<i32 as EncodeConvertArgument>::__from_inner(42), 42);
assert_eq!(EncodeConvertArgument::__into_inner(42i32), 42);
assert_eq!(
<i32 as EncodeConvertArgument>::__from_declared_param(42),
42
);
assert_eq!(EncodeConvertArgument::__into_argument(42i32).0, 42);
}

#[test]
Expand All @@ -214,21 +239,25 @@ mod tests {
TypeId::of::<<i8 as EncodeConvertArgument>::__Inner>(),
TypeId::of::<i8>()
);
assert_eq!(<i8 as EncodeConvertArgument>::__from_inner(-3), -3);
assert_eq!(EncodeConvertArgument::__into_inner(-3i32), -3);
assert_eq!(<i8 as EncodeConvertArgument>::__from_declared_param(-3), -3);
assert_eq!(EncodeConvertArgument::__into_argument(-3i32).0, -3);
}

#[test]
fn convert_bool() {
assert!(!<bool as EncodeConvertArgument>::__from_inner(Bool::NO));
assert!(<bool as EncodeConvertArgument>::__from_inner(Bool::YES));
assert!(!<bool as EncodeConvertReturn>::__from_inner(Bool::NO));
assert!(<bool as EncodeConvertReturn>::__from_inner(Bool::YES));

assert!(!EncodeConvertArgument::__into_inner(false).as_bool());
assert!(EncodeConvertArgument::__into_inner(true).as_bool());
assert!(!EncodeConvertReturn::__into_inner(false).as_bool());
assert!(EncodeConvertReturn::__into_inner(true).as_bool());
assert!(!<bool as EncodeConvertArgument>::__from_declared_param(
Bool::NO
));
assert!(<bool as EncodeConvertArgument>::__from_declared_param(
Bool::YES
));
assert!(!<bool as EncodeConvertReturn>::__from_return(Bool::NO));
assert!(<bool as EncodeConvertReturn>::__from_return(Bool::YES));

assert!(!EncodeConvertArgument::__into_argument(false).0.as_bool());
assert!(EncodeConvertArgument::__into_argument(true).0.as_bool());
assert!(!EncodeConvertReturn::__into_declared_return(false).as_bool());
assert!(EncodeConvertReturn::__into_declared_return(true).as_bool());

#[cfg(all(feature = "apple", target_os = "macos", target_arch = "x86_64"))]
assert_eq!(
Expand Down
Loading

0 comments on commit 1a4dfe7

Please sign in to comment.