Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Encode and RefEncode implementations #52

Merged
merged 4 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions objc2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ Objective-C objects can be messaged using the `msg_send!` macro:

```rust , no_run
use objc2::{class, msg_send};
use objc2::runtime::{BOOL, Object};
use objc2::runtime::{Bool, Object};

let cls = class!(NSObject);
unsafe {
let obj: *mut Object = msg_send![cls, new];
let hash: usize = msg_send![obj, hash];
let is_kind: BOOL = msg_send![obj, isKindOfClass: cls];
let is_kind: Bool = msg_send![obj, isKindOfClass: cls];
// Even void methods must have their return type annotated
let _: () = msg_send![obj, release];
}
Expand Down
11 changes: 11 additions & 0 deletions objc2/src/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,21 @@ impl fmt::Debug for Bool {
}
}

// SAFETY: `Bool` is `repr(transparent)`.
unsafe impl Encode for Bool {
const ENCODING: Encoding<'static> = objc2_sys::BOOL::ENCODING;
}

// Note that we shouldn't delegate to `BOOL`'s `ENCODING_REF` since `BOOL` is
// sometimes `i8`/`u8`, and their `ENCODING_REF`s are `Encoding::String`,
// which is incorrect for `BOOL`:
//
// ```objc
// @encode(BOOL); // -> "c", "C" or "B"
// @encode(BOOL*); // -> "^c", "^C" or "^B"
// @encode(char); // -> "c" or "C"
// @encode(char*); // -> "*"
// ```
unsafe impl RefEncode for Bool {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}
Expand Down
4 changes: 2 additions & 2 deletions objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Objective-C objects can be messaged using the [`msg_send!`](macro.msg_send!.html

``` no_run
# use objc2::{class, msg_send};
# use objc2::runtime::{BOOL, Class, Object};
# use objc2::runtime::{Bool, Class, Object};
# unsafe {
let cls = class!(NSObject);
let obj: *mut Object = msg_send![cls, new];
let hash: usize = msg_send![obj, hash];
let is_kind: BOOL = msg_send![obj, isKindOfClass: cls];
let is_kind: Bool = msg_send![obj, isKindOfClass: cls];
// Even void methods must have their return type annotated
let _: () = msg_send![obj, release];
# }
Expand Down
4 changes: 2 additions & 2 deletions objc2/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ pub unsafe trait MessageReceiver: private::Sealed {
/// # Example
/// ``` no_run
/// # use objc2::{class, msg_send, sel};
/// # use objc2::runtime::{BOOL, Class, Object};
/// # use objc2::runtime::{Bool, Class, Object};
/// # use objc2::MessageReceiver;
/// let obj: &Object;
/// # obj = unsafe { msg_send![class!(NSObject), new] };
/// let sel = sel!(isKindOfClass:);
/// // Verify isKindOfClass: takes one Class and returns a BOOL
/// let result = obj.verify_message::<(&Class,), BOOL>(sel);
/// let result = obj.verify_message::<(&Class,), Bool>(sel);
/// assert!(result.is_ok());
/// ```
fn verify_message<A, R>(&self, sel: Sel) -> Result<(), MessageError>
Expand Down
142 changes: 132 additions & 10 deletions objc2_encode/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use core::{ffi::c_void, mem::ManuallyDrop, pin::Pin, ptr::NonNull};
use core::ffi::c_void;
use core::mem::ManuallyDrop;
use core::num::{
NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32,
NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
};
use core::pin::Pin;
use core::ptr::NonNull;

use crate::Encoding;

Expand Down Expand Up @@ -160,17 +167,22 @@ encode_impls!(
u64 => ULongLong,
f32 => Float,
f64 => Double,
() => Void,
*mut i8 => String,
*const i8 => String,
*mut u8 => String,
*const u8 => String,
);

/// To allow usage as the return type of generic functions.
///
/// You should not rely on this encoding to exist for any other purpose (since
/// `()` is not FFI-safe)!
///
/// TODO: Figure out a way to remove this.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling attention to this. Possible solution would be to change encode_fn_pointer_impl! and similar macros to handle both the case where R: Encode and where R = ()

unsafe impl Encode for () {
const ENCODING: Encoding<'static> = Encoding::Void;
}

/// Using this directly is heavily discouraged, since the type of BOOL differs
/// across platforms.
///
/// Use `objc2_sys::BOOL::ENCODING` or `objc2::runtime::Bool` instead.
/// Use `objc2::runtime::Bool::ENCODING` instead.
unsafe impl Encode for bool {
const ENCODING: Encoding<'static> = Encoding::Bool;
}
Expand All @@ -194,16 +206,45 @@ encode_impls_size!(
usize => (u16, u32, u64),
);

/// Simple helper for implementing [`Encode`] for integer types.
/// Simple helper for implementing [`RefEncode`].
macro_rules! pointer_refencode_impl {
($($t:ty),*) => ($(
unsafe impl RefEncode for $t {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}
)*);
}

pointer_refencode_impl!(bool, i16, i32, i64, isize, u16, u32, u64, usize, f32, f64);

/// Pointers to [`i8`] use the special [`Encoding::String`] encoding.
unsafe impl RefEncode for i8 {
const ENCODING_REF: Encoding<'static> = Encoding::String;
}

/// Pointers to [`u8`] use the special [`Encoding::String`] encoding.
unsafe impl RefEncode for u8 {
const ENCODING_REF: Encoding<'static> = Encoding::String;
}

/// Simple helper for implementing [`Encode`] for nonzero integer types.
macro_rules! encode_impls_nonzero {
($($nonzero:ident => $type:ty,)*) => ($(
unsafe impl Encode for core::num::$nonzero {
unsafe impl Encode for $nonzero {
const ENCODING: Encoding<'static> = <$type>::ENCODING;
}

unsafe impl Encode for Option<core::num::$nonzero> {
unsafe impl Encode for Option<$nonzero> {
const ENCODING: Encoding<'static> = <$type>::ENCODING;
}

unsafe impl RefEncode for $nonzero {
const ENCODING_REF: Encoding<'static> = <$type>::ENCODING_REF;
}

unsafe impl RefEncode for Option<$nonzero> {
const ENCODING_REF: Encoding<'static> = <$type>::ENCODING_REF;
}
)*);
}

Expand All @@ -229,12 +270,20 @@ unsafe impl Encode for *const c_void {
const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Void);
}

unsafe impl RefEncode for *const c_void {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}

/// [`Encode`] is implemented manually for `*mut c_void`, instead of
/// implementing [`RefEncode`], to discourage creating `&mut c_void`.
unsafe impl Encode for *mut c_void {
const ENCODING: Encoding<'static> = Encoding::Pointer(&Encoding::Void);
}

unsafe impl RefEncode for *mut c_void {
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
}

unsafe impl<T: Encode, const LENGTH: usize> Encode for [T; LENGTH] {
const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING);
}
Expand Down Expand Up @@ -265,6 +314,16 @@ unsafe impl<T: RefEncode> RefEncode for Pin<T> {
const ENCODING_REF: Encoding<'static> = T::ENCODING_REF;
}

// SAFETY: `Wrapping` is `repr(transparent)`.
unsafe impl<T: Encode> Encode for Wrapping<T> {
const ENCODING: Encoding<'static> = T::ENCODING;
}

// SAFETY: `Wrapping` is `repr(transparent)`.
unsafe impl<T: RefEncode> RefEncode for Wrapping<T> {
const ENCODING_REF: Encoding<'static> = T::ENCODING_REF;
}

/// Helper for implementing `Encode`/`RefEncode` for pointers to types that
/// implement `RefEncode`.
///
Expand Down Expand Up @@ -423,3 +482,66 @@ encode_args_impl!(A, B, C, D, E, F, G, H, I);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K);
encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L);

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_c_string() {
assert_eq!(i8::ENCODING, Encoding::Char);
assert_eq!(u8::ENCODING, Encoding::UChar);

assert_eq!(<*const i8>::ENCODING, Encoding::String);
assert_eq!(<&u8>::ENCODING, Encoding::String);
assert_eq!(i8::ENCODING_REF, Encoding::String);
assert_eq!(i8::ENCODING_REF, Encoding::String);

assert_eq!(
<*const *const i8>::ENCODING,
Encoding::Pointer(&Encoding::String)
);
assert_eq!(<&&u8>::ENCODING, Encoding::Pointer(&Encoding::String));
}

#[test]
fn test_i32() {
assert_eq!(i32::ENCODING, Encoding::Int);
assert_eq!(<&i32>::ENCODING, Encoding::Pointer(&Encoding::Int));
assert_eq!(
<&&i32>::ENCODING,
Encoding::Pointer(&Encoding::Pointer(&Encoding::Int))
);
}

#[test]
fn test_void() {
// TODO: Remove this
assert_eq!(<()>::ENCODING, Encoding::Void);
assert_eq!(
<*const c_void>::ENCODING,
Encoding::Pointer(&Encoding::Void)
);
assert_eq!(
<&*const c_void>::ENCODING,
Encoding::Pointer(&Encoding::Pointer(&Encoding::Void))
);

// Shouldn't compile
// assert_eq!(<c_void>::ENCODING, Encoding::Void);
// assert_eq!(<*const ()>::ENCODING, Encoding::Pointer(&Encoding::Void));
// assert_eq!(<&c_void>::ENCODING, Encoding::Pointer(&Encoding::Void));
}

#[test]
fn test_extern_fn_pointer() {
assert_eq!(
<extern "C" fn()>::ENCODING,
Encoding::Pointer(&Encoding::Unknown)
);
assert_eq!(
<extern "C" fn(x: ()) -> ()>::ENCODING,
Encoding::Pointer(&Encoding::Unknown)
);
}
}
5 changes: 5 additions & 0 deletions objc2_sys/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ mod inner {
///
/// The type of this varies across platforms, so to convert an it into a Rust
/// [`bool`], always compare it with [`YES`][`crate::YES`] or [`NO`][`crate::NO`].
///
/// Note that this type implements `objc2_encode::Encode` and
/// `objc2_encode::RefEncode`, but the `RefEncode` implementation is wrong
/// on some platforms! You should only use this on FFI boundaries, otherwise
/// prefer `objc2::runtime::Bool`.
pub type BOOL = inner::BOOL;

/// An immutable pointer to a selector.
Expand Down