Skip to content

Commit

Permalink
Merge pull request #52 from madsmtm/more-encode
Browse files Browse the repository at this point in the history
More `Encode` and `RefEncode` implementations
  • Loading branch information
madsmtm authored Oct 30, 2021
2 parents 4060608 + 6112808 commit 1ddd898
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 16 deletions.
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.
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

0 comments on commit 1ddd898

Please sign in to comment.