diff --git a/objc2/README.md b/objc2/README.md index 90ced8f3f..354b4fed8 100644 --- a/objc2/README.md +++ b/objc2/README.md @@ -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]; } diff --git a/objc2/src/bool.rs b/objc2/src/bool.rs index 34065b277..d98bb79be 100644 --- a/objc2/src/bool.rs +++ b/objc2/src/bool.rs @@ -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); } diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs index 50352b4aa..271c94148 100644 --- a/objc2/src/lib.rs +++ b/objc2/src/lib.rs @@ -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]; # } diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index 9003b74e3..eb16d5d49 100644 --- a/objc2/src/message/mod.rs +++ b/objc2/src/message/mod.rs @@ -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(&self, sel: Sel) -> Result<(), MessageError> diff --git a/objc2_encode/src/encode.rs b/objc2_encode/src/encode.rs index 8a371c642..a062cf084 100644 --- a/objc2_encode/src/encode.rs +++ b/objc2_encode/src/encode.rs @@ -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; @@ -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; } @@ -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 { + 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; + } )*); } @@ -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 Encode for [T; LENGTH] { const ENCODING: Encoding<'static> = Encoding::Array(LENGTH, &T::ENCODING); } @@ -265,6 +314,16 @@ unsafe impl RefEncode for Pin { const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; } +// SAFETY: `Wrapping` is `repr(transparent)`. +unsafe impl Encode for Wrapping { + const ENCODING: Encoding<'static> = T::ENCODING; +} + +// SAFETY: `Wrapping` is `repr(transparent)`. +unsafe impl RefEncode for Wrapping { + const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; +} + /// Helper for implementing `Encode`/`RefEncode` for pointers to types that /// implement `RefEncode`. /// @@ -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!(::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!( + ::ENCODING, + Encoding::Pointer(&Encoding::Unknown) + ); + assert_eq!( + ()>::ENCODING, + Encoding::Pointer(&Encoding::Unknown) + ); + } +} diff --git a/objc2_sys/src/types.rs b/objc2_sys/src/types.rs index 5a44e5ddb..c2d8c0e3d 100644 --- a/objc2_sys/src/types.rs +++ b/objc2_sys/src/types.rs @@ -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.