diff --git a/objc2-encode/CHANGELOG.md b/objc2-encode/CHANGELOG.md index 90a054dea..262552bde 100644 --- a/objc2-encode/CHANGELOG.md +++ b/objc2-encode/CHANGELOG.md @@ -7,9 +7,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - YYYY-MM-DD ### Added -* Added `Encoding::LONG` and `Encoding::U_LONG` to help with platform +* Added `Encoding::C_LONG` and `Encoding::C_U_LONG` to help with platform compatibility; use these instead of `c_long::ENCODING` and `c_ulong::ENCODING`. +* Implement `Encode` and `RefEncode` for `MaybeUninit`, where `T` is + properly bound. + +### Changed +* **BREAKING**: Sealed the `EncodeArguments` trait. +* **BREAKING**: Add type argument to `Encoding::BitField`. + +### Removed +* **BREAKING**: Removed `PartialEq` impl between `str` and `Encoding` since it + was incorrect (it violated the trait requirements). +* **BREAKING**: Removed `Encode` and `RefEncode` implementations for `Pin` + since it may not be sound. ## 2.0.0-beta.2 - 2022-01-03 diff --git a/objc2-encode/src/encode.rs b/objc2-encode/src/encode.rs index 80521e0b4..bec6a89ad 100644 --- a/objc2-encode/src/encode.rs +++ b/objc2-encode/src/encode.rs @@ -1,10 +1,9 @@ use core::ffi::c_void; -use core::mem::ManuallyDrop; +use core::mem::{ManuallyDrop, MaybeUninit}; 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; @@ -22,8 +21,8 @@ use crate::Encoding; /// `repr(u8)`, `repr(transparent)` where the inner types are C-compatible, /// and so on). See the [nomicon on other `repr`s][reprs]. /// -/// Objective-C will make assumptions about the type (like its size and -/// alignment) from its encoding, so the implementer must verify that the +/// Objective-C will make assumptions about the type (like its size, alignment +/// and ABI) from its encoding, so the implementer must verify that the /// encoding is accurate. /// /// Concretely, [`Self::ENCODING`] must match the result of running `@encode` @@ -101,8 +100,8 @@ pub unsafe trait Encode { /// information on how to represent objects that you don't know the layout of /// (or use `extern type` ([RFC-1861]) if you're using nightly). /// -/// Objective-C will make assumptions about the type (like its size and -/// alignment) from its encoding, so the implementer must verify that the +/// Objective-C will make assumptions about the type (like its size, alignment +/// and ABI) from its encoding, so the implementer must verify that the /// encoding is accurate. /// /// Concretely, [`Self::ENCODING_REF`] must match the result of running @@ -177,12 +176,33 @@ encode_impls!( /// /// 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. +// TODO: Figure out a way to remove this - maybe with a `EncodeReturn` trait? unsafe impl Encode for () { const ENCODING: Encoding<'static> = Encoding::Void; } +// UI tests of this is too brittle. +#[cfg(doctest)] +/// ``` +/// use objc2_encode::Encode; +/// <()>::ENCODING; // TODO: Make this fail as well +/// ``` +/// ```should_fail +/// use core::ffi::c_void; +/// use objc2_encode::Encode; +/// ::ENCODING; +/// ``` +/// ```should_fail +/// use objc2_encode::Encode; +/// <*const ()>::ENCODING; +/// ``` +/// ```should_fail +/// use core::ffi::c_void; +/// use objc2_encode::Encode; +/// <&c_void>::ENCODING; +/// ``` +extern "C" {} + /// Using this directly is heavily discouraged, since the type of BOOL differs /// across platforms. /// @@ -296,96 +316,89 @@ unsafe impl RefEncode for [T; LENGTH] { const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); } -// SAFETY: `ManuallyDrop` is `repr(transparent)`. -unsafe impl Encode for ManuallyDrop { - const ENCODING: Encoding<'static> = T::ENCODING; -} - -// With specialization: `impl Encode for ManuallyDrop>` - -// SAFETY: `ManuallyDrop` is `repr(transparent)`. -unsafe impl RefEncode for ManuallyDrop { - const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; -} - -// TODO: Consider UnsafeCell -// It is #[repr(transparent)], but might not be safe given that UnsafeCell -// also has #[repr(no_niche)] - -// TODO: Consider Cell -// It is #[repr(transparent)] of UnsafeCell, so figure that out first -// `Cell` might also have other requirements that would disourage this impl? - -// TODO: Types that need to be made repr(transparent) first: -// core::cell::Ref? -// core::cell::RefCell? -// core::cell::RefMut? -// core::panic::AssertUnwindSafe - -// SAFETY: `Pin` is `repr(transparent)`. -unsafe impl Encode for Pin { - const ENCODING: Encoding<'static> = T::ENCODING; -} - -// SAFETY: `Pin` is `repr(transparent)`. -unsafe impl RefEncode for Pin { - const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; -} - -// TODO: MaybeUninit +macro_rules! encode_impls_transparent { + ($($t:ident,)*) => ($( + unsafe impl Encode for $t { + const ENCODING: Encoding<'static> = T::ENCODING; + } -// SAFETY: `Wrapping` is `repr(transparent)`. -unsafe impl Encode for Wrapping { - const ENCODING: Encoding<'static> = T::ENCODING; + unsafe impl RefEncode for $t { + const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; + } + )*); } -// SAFETY: `Wrapping` is `repr(transparent)`. -unsafe impl RefEncode for Wrapping { - const ENCODING_REF: Encoding<'static> = T::ENCODING_REF; +encode_impls_transparent! { + // SAFETY: Guaranteed to have the same layout as `T`, and is subject to + // the same layout optimizations as `T`. + // TODO: With specialization: `impl Encode for ManuallyDrop>` + ManuallyDrop, + + // The fact that this has `repr(no_niche)` has no effect on us, since we + // don't implement `Encode` generically over `Option`. + // (e.g. an `Option>` impl is not available). + // The inner field is not public, so may not be stable. + // TODO: UnsafeCell, + + // The inner field is not public, so may not be safe. + // TODO: Pin, + + // SAFETY: Guaranteed to have the same size, alignment, and ABI as `T`. + MaybeUninit, + + // SAFETY: Guaranteed to have the same layout and ABI as `T`. + Wrapping, + + // It might have requirements that would disourage this impl? + // TODO: Cell + + // TODO: Types that need to be made repr(transparent) first: + // - core::cell::Ref? + // - core::cell::RefCell? + // - core::cell::RefMut? + // - core::panic::AssertUnwindSafe + // TODO: core::num::Saturating when that is stabilized + // TODO: core::cmp::Reverse? } -// TODO: core::num::Saturating when that is stabilized - -// TODO: core::cmp::Reverse? - /// Helper for implementing `Encode`/`RefEncode` for pointers to types that /// implement `RefEncode`. /// /// Using `?Sized` is safe here because we delegate to other implementations /// (which will verify that the implementation is safe for the unsized type). macro_rules! encode_pointer_impls { - (unsafe impl $x:ident for &$t:ident { + (unsafe impl $x:ident for Pointer { const $c:ident = $e:expr; }) => ( - unsafe impl<$t: RefEncode + ?Sized> $x for *const $t { + unsafe impl $x for *const T { const $c: Encoding<'static> = $e; } - unsafe impl<$t: RefEncode + ?Sized> $x for *mut $t { + unsafe impl $x for *mut T { const $c: Encoding<'static> = $e; } - unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a $t { + unsafe impl<'a, T: RefEncode + ?Sized> $x for &'a T { const $c: Encoding<'static> = $e; } - unsafe impl<'a, $t: RefEncode + ?Sized> $x for &'a mut $t { + unsafe impl<'a, T: RefEncode + ?Sized> $x for &'a mut T { const $c: Encoding<'static> = $e; } - unsafe impl $x for NonNull<$t> { + unsafe impl $x for NonNull { const $c: Encoding<'static> = $e; } - unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a $t> { + unsafe impl<'a, T: RefEncode + ?Sized> $x for Option<&'a T> { const $c: Encoding<'static> = $e; } - unsafe impl<'a, $t: RefEncode + ?Sized> $x for Option<&'a mut $t> { + unsafe impl<'a, T: RefEncode + ?Sized> $x for Option<&'a mut T> { const $c: Encoding<'static> = $e; } - unsafe impl $x for Option> { + unsafe impl $x for Option> { const $c: Encoding<'static> = $e; } ); @@ -397,7 +410,7 @@ macro_rules! encode_pointer_impls { // specific encoding as a pointer, instead of having to implement it for each // pointer-like type in turn. encode_pointer_impls!( - unsafe impl Encode for &T { + unsafe impl Encode for Pointer { const ENCODING = T::ENCODING_REF; } ); @@ -407,7 +420,7 @@ encode_pointer_impls!( // This implements `Encode` for pointers to pointers (to pointers, and so on), // which would otherwise be very cumbersome to do manually. encode_pointer_impls!( - unsafe impl RefEncode for &T { + unsafe impl RefEncode for Pointer { const ENCODING_REF = Encoding::Pointer(&T::ENCODING_REF); } ); @@ -472,25 +485,32 @@ encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J); encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J, K); encode_fn_pointer_impl!(A, B, C, D, E, F, G, H, I, J, K, L); +mod private { + pub trait Sealed {} +} + /// Types that represent an ordered group of function arguments, where each /// argument has an Objective-C type-encoding. /// -/// This is implemented for tuples, and is used to make generic code easier. +/// This is implemented for tuples of up to 12 arguments, where each argument +/// implements [`Encode`]. It is primarily used to make generic code easier. /// /// Note that tuples themselves don't implement [`Encode`] directly because -/// they're not FFI-safe. +/// they're not FFI-safe! /// /// # Safety /// -/// You should not need to implement this. Open an issue if you know a -/// use-case where this restrition should be lifted! -pub unsafe trait EncodeArguments { +/// This is a sealed trait, and should not need to be implemented. Open an +/// issue if you know a use-case where this restrition should be lifted! +pub unsafe trait EncodeArguments: private::Sealed { /// The encodings for the arguments. const ENCODINGS: &'static [Encoding<'static>]; } macro_rules! encode_args_impl { ($($Arg: ident),*) => { + impl<$($Arg: Encode),*> private::Sealed for ($($Arg,)*) {} + unsafe impl<$($Arg: Encode),*> EncodeArguments for ($($Arg,)*) { const ENCODINGS: &'static [Encoding<'static>] = &[ $($Arg::ENCODING),* @@ -556,11 +576,22 @@ mod tests { <&*const c_void>::ENCODING, Encoding::Pointer(&Encoding::Pointer(&Encoding::Void)) ); + } + + #[test] + fn test_transparent() { + assert_eq!(>::ENCODING, u8::ENCODING); + assert_eq!(>::ENCODING, u8::ENCODING_REF); + assert_eq!(>>::ENCODING, u8::ENCODING_REF); + assert_eq!(<&ManuallyDrop>>::ENCODING, <&&u8>::ENCODING); + + // assert_eq!(>::ENCODING, u8::ENCODING); + // assert_eq!(>::ENCODING, u8::ENCODING); + assert_eq!(>::ENCODING, u8::ENCODING); + assert_eq!(>::ENCODING, u8::ENCODING); // 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)); + // assert_eq!(>>::ENCODING, <&u8>::ENCODING); } #[test] @@ -578,4 +609,11 @@ mod tests { Encoding::Pointer(&Encoding::Unknown) ); } + + #[test] + fn test_encode_arguments() { + assert!(<()>::ENCODINGS.is_empty()); + assert_eq!(<(i8,)>::ENCODINGS, &[i8::ENCODING]); + assert_eq!(<(i8, u32)>::ENCODINGS, &[i8::ENCODING, u32::ENCODING]); + } } diff --git a/objc2-encode/src/encoding.rs b/objc2-encode/src/encoding.rs index 87fdf6094..fa5b99d3e 100644 --- a/objc2-encode/src/encoding.rs +++ b/objc2-encode/src/encoding.rs @@ -89,10 +89,13 @@ pub enum Encoding<'a> { /// /// This is usually used to encode functions. Unknown, - /// A bitfield with the given number of bits. + /// A bitfield with the given number of bits, and the given type. + /// + /// The type is not currently used, but may be in the future for better + /// compatibility with Objective-C runtimes. /// /// Corresponds to the `b`num code. - BitField(u8), + BitField(u8, &'a Encoding<'a>), /// A pointer to the given type. /// /// Corresponds to the `^`type code. @@ -141,8 +144,8 @@ impl Encoding<'_> { /// then `c_long::ENCODING` would just work. /// /// Unfortunately, `long` have a different encoding than `int` when it is - /// 32 bits wide; the 'l'/'L' encoding. - pub const LONG: Self = { + /// 32 bits wide; the [`l`][`Encoding::Long`] encoding. + pub const C_LONG: Self = { // Alternative: `mem::size_of::() == 4` // That would exactly match what `clang` does: // https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/AST/ASTContext.cpp#L7245 @@ -157,8 +160,8 @@ impl Encoding<'_> { /// The encoding of [`c_ulong`](`std::os::raw::c_ulong`). /// - /// See [`Encoding::LONG`] for explanation. - pub const U_LONG: Self = { + /// See [`Encoding::C_LONG`] for explanation. + pub const C_U_LONG: Self = { if cfg!(any(target_pointer_width = "32", windows)) { // @encode(unsigned long) = 'L' Encoding::ULong @@ -169,12 +172,24 @@ impl Encoding<'_> { }; /// Check if one encoding is equivalent to another. + /// + /// Currently, equivalence testing requires that the encodings are equal, + /// except for qualifiers. This may be changed in the future to e.g. + /// ignore struct names, to allow structs behind multiple pointers to be + /// considered equivalent, or similar changes that may be required because + /// of limitations in Objective-C compiler implementations. + /// + /// For example, you should not rely on two equivalent encodings to have + /// the same size or ABI - that is provided on a best-effort basis. pub fn equivalent_to(&self, other: &Self) -> bool { // For now, because we don't allow representing qualifiers self == other } /// Check if an encoding is equivalent to the given string representation. + /// + /// See [`Encoding::equivalent_to`] for details about the meaning of + /// "equivalence". pub fn equivalent_to_str(&self, s: &str) -> bool { // if the given encoding can be successfully removed from the start // and an empty string remains, they were fully equivalent! @@ -190,6 +205,9 @@ impl Encoding<'_> { /// /// If it is equivalent, the remaining part of the string is returned. /// Otherwise this returns [`None`]. + /// + /// See [`Encoding::equivalent_to`] for details about the meaning of + /// "equivalence". pub fn equivalent_to_start_of_str<'a>(&self, s: &'a str) -> Option<&'a str> { // strip leading qualifiers let s = s.trim_start_matches(parse::QUALIFIERS); @@ -200,6 +218,12 @@ impl Encoding<'_> { } } +/// Formats this [`Encoding`] in a similar way that the `@encode` directive +/// would ordinarily do. +/// +/// You should not rely on the output of this to be stable across versions. It +/// may change if found to be required to be compatible with exisiting +/// Objective-C compilers. impl fmt::Display for Encoding<'_> { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { use Encoding::*; @@ -228,7 +252,8 @@ impl fmt::Display for Encoding<'_> { Class => "#", Sel => ":", Unknown => "?", - BitField(b) => { + BitField(b, _type) => { + // TODO: Use the type on GNUStep return write!(formatter, "b{}", b); } Pointer(t) => { @@ -256,45 +281,18 @@ impl fmt::Display for Encoding<'_> { } } -// TODO: Deprecate and remove these PartialEq impls - -/// Partial equality between an [`Encoding`] and a [`str`]. -/// -/// Using this is heavily discouraged, since it is not transitive; use -/// [`Encoding::equivalent_to_str`] instead for more correct semantics. -impl PartialEq for Encoding<'_> { - /// Using this is discouraged. - fn eq(&self, other: &str) -> bool { - self.equivalent_to_str(other) - } - - /// Using this is discouraged. - fn ne(&self, other: &str) -> bool { - !self.eq(other) - } -} - -/// Partial equality between an [`Encoding`] and a [`str`]. -/// -/// Using this is heavily discouraged, since it is not transitive; use -/// [`Encoding::equivalent_to_str`] instead for more correct semantics. -impl PartialEq> for str { - /// Using this is discouraged. - fn eq(&self, other: &Encoding<'_>) -> bool { - other.equivalent_to_str(self) - } - - /// Using this is discouraged. - fn ne(&self, other: &Encoding<'_>) -> bool { - !self.eq(other) - } -} - #[cfg(test)] mod tests { use super::Encoding; use alloc::string::ToString; + fn send_sync() {} + + #[test] + fn test_send_sync() { + send_sync::>(); + } + #[test] fn test_array_display() { let e = Encoding::Array(12, &Encoding::Int); diff --git a/objc2-encode/src/lib.rs b/objc2-encode/src/lib.rs index 33c087064..96792291e 100644 --- a/objc2-encode/src/lib.rs +++ b/objc2-encode/src/lib.rs @@ -12,8 +12,11 @@ //! Objective-C encoding: Specifically [`Encode`] for structs, [`RefEncode`] //! for references and [`EncodeArguments`] for function arguments. //! -//! These types are exported under the [`objc2`] crate as well, so usually you -//! would just use them from there. +//! This crate is exported under the [`objc2`] crate as `objc2::encode`, so +//! usually you would just use it from there. +//! +//! [`objc2`]: https://crates.io/crates/objc2 +//! //! //! ## Example //! @@ -57,13 +60,29 @@ //! //! [`examples`]: https://github.com/madsmtm/objc2/tree/master/objc2-encode/examples //! -//! Further resources: +//! +//! ## Caveats +//! +//! We've taken the pragmatic approach with [`Encode`] and [`RefEncode`], and +//! have implemented it for as many types as possible (instead of defining a +//! bunch of subtraits for very specific purposes). However, that might +//! sometimes be slightly surprising. +//! +//! Notably we have implemented these for [`bool`], which, in reality, you +//! would never actually see in an Objective-C method (they use `BOOL`, see +//! `objc2::runtime::Bool`), but which _can_ techincally occur, and as such +//! does make sense to define an encoding for. +//! +//! The other example is [`()`][`unit`], which doesn't make sense as a method +//! argument, but is a very common return type, and hence implements `Encode`. +//! +//! +//! ## Further resources +//! //! - [Objective-C, Encoding and You](https://dmaclach.medium.com/objective-c-encoding-and-you-866624cc02de). //! - [Apple's documentation on Type Encodings](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html). //! - [How are the digits in ObjC method type encoding calculated?](https://stackoverflow.com/a/11527925) //! - [`clang`'s source code for generating `@encode`](https://github.com/llvm/llvm-project/blob/fae0dfa6421ea6c02f86ba7292fa782e1e2b69d1/clang/lib/AST/ASTContext.cpp#L7500-L7850). -//! -//! [`objc2`]: https://crates.io/crates/objc2 #![no_std] #![warn(elided_lifetimes_in_paths)] diff --git a/objc2-encode/src/parse.rs b/objc2-encode/src/parse.rs index ce701a720..eb6e78ced 100644 --- a/objc2-encode/src/parse.rs +++ b/objc2-encode/src/parse.rs @@ -40,7 +40,8 @@ pub(crate) fn rm_enc_prefix<'a>(s: &'a str, enc: &Encoding<'_>) -> Option<&'a st Class => "#", Sel => ":", Unknown => "?", - BitField(b) => { + BitField(b, _type) => { + // TODO: Use the type on GNUStep let s = s.strip_prefix('b')?; return rm_int_prefix(s, b as usize); } @@ -117,10 +118,11 @@ mod tests { #[test] fn test_bitfield() { - assert!(Encoding::BitField(32).equivalent_to_str("b32")); - assert!(!Encoding::BitField(32).equivalent_to_str("b32a")); - assert!(!Encoding::BitField(32).equivalent_to_str("b")); - assert!(!Encoding::BitField(32).equivalent_to_str("b-32")); + let bitfield = Encoding::BitField(32, &Encoding::Int); + assert!(bitfield.equivalent_to_str("b32")); + assert!(!bitfield.equivalent_to_str("b32a")); + assert!(!bitfield.equivalent_to_str("b")); + assert!(!bitfield.equivalent_to_str("b-32")); } #[test] diff --git a/objc2-foundation/src/enumerator.rs b/objc2-foundation/src/enumerator.rs index 927f116af..305f4c094 100644 --- a/objc2-foundation/src/enumerator.rs +++ b/objc2-foundation/src/enumerator.rs @@ -57,10 +57,10 @@ unsafe impl Encode for NSFastEnumerationState { const ENCODING: Encoding<'static> = Encoding::Struct( "?", &[ - Encoding::U_LONG, + Encoding::C_U_LONG, Encoding::Pointer(&Encoding::Object), // <*const *const T>::ENCODING - Encoding::Pointer(&Encoding::U_LONG), - Encoding::Array(5, &Encoding::U_LONG), + Encoding::Pointer(&Encoding::C_U_LONG), + Encoding::Array(5, &Encoding::C_U_LONG), ], ); } diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index d66b305b0..88b6fdcce 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -31,6 +31,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). let obj = unsafe { Id::new(obj) }.expect("Failed to allocate object."); ``` +### Fixed +* Properly sealed the `MessageArguments` trait (it already had a hidden + method, so this is not really a breaking change). + ## 0.3.0-alpha.6 - 2022-01-03 @@ -103,6 +107,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## 0.3.0-alpha.4 - 2021-11-22 +Note: To use this version, specify `objc2-encode = "=2.0.0-beta.0"` in your +`Cargo.toml` as well. + ### Added * **BREAKING**: GNUStep users must depend on, and specify the appropriate feature flag on `objc-sys` for the version they're using. @@ -122,6 +129,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## 0.3.0-alpha.3 - 2021-09-05 +Note: To use this version, specify `objc2-encode = "=2.0.0-alpha.1"` in your +`Cargo.toml` as well. + ### Added * Now uses the `objc-sys` (`v0.0.1`) crate for possibly better interoperability with other crates that link to `libobjc`. diff --git a/objc2/src/message/mod.rs b/objc2/src/message/mod.rs index 14a353189..361724c25 100644 --- a/objc2/src/message/mod.rs +++ b/objc2/src/message/mod.rs @@ -124,6 +124,7 @@ pub unsafe trait MessageReceiver: private::Sealed { R: Encode, { let this = self.as_raw_receiver(); + // TODO: Always enable this when `debug_assertions` are on. #[cfg(feature = "verify_message")] { // SAFETY: Caller ensures only valid or NULL pointers. @@ -268,8 +269,11 @@ unsafe impl MessageReceiver for ManuallyDrop { /// This is implemented for tuples of up to 12 arguments, where each argument /// implements [`Encode`]. /// -/// You should not need to implement this yourself. -pub trait MessageArguments: EncodeArguments { +/// # Safety +/// +/// This is a sealed trait, and should not need to be implemented. Open an +/// issue if you know a use-case where this restrition should be lifted! +pub unsafe trait MessageArguments: EncodeArguments { /// Invoke an [`Imp`] with the given object, selector, and arguments. /// /// This method is the primitive used when sending messages and should not @@ -281,7 +285,7 @@ pub trait MessageArguments: EncodeArguments { macro_rules! message_args_impl { ($($a:ident : $t:ident),*) => ( - impl<$($t: Encode),*> MessageArguments for ($($t,)*) { + unsafe impl<$($t: Encode),*> MessageArguments for ($($t,)*) { #[inline] #[doc(hidden)] unsafe fn __invoke(imp: Imp, obj: *mut Object, sel: Sel, ($($a,)*): Self) -> R { diff --git a/tests/build.rs b/tests/build.rs index ed907b019..bea726520 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -4,6 +4,9 @@ fn main() { println!("cargo:rerun-if-changed=extern/block_utils.c"); println!("cargo:rerun-if-changed=extern/encode_utils.m"); + let runtime = env::var("DEP_OBJC_RUNTIME").unwrap(); + println!("cargo:rustc-cfg={}", runtime); + let mut builder = cc::Build::new(); builder.compiler("clang"); builder.file("extern/block_utils.c"); diff --git a/tests/extern/encode_utils.m b/tests/extern/encode_utils.m index 744ca920f..79c8b3677 100644 --- a/tests/extern/encode_utils.m +++ b/tests/extern/encode_utils.m @@ -43,27 +43,66 @@ ENCODING_INNER(VOID_POINTER_CONST, const void*); ENCODING_INNER(VOID_POINTER_POINTER, void**); -// Array - -ENCODING_INNER(ARRAY_INT, int[10]); -ENCODING_INNER(ARRAY_INT_POINTER, int*[10]); -ENCODING_INNER(ARRAY_INT_ATOMIC, _Atomic int[10]); - // Struct struct empty {}; ENCODING(STRUCT_EMPTY, struct empty); +ENCODING_INNER(STRUCT_EMPTY_POINTER_POINTER, struct empty**); +ENCODING_INNER(STRUCT_EMPTY_POINTER_POINTER_POINTER, struct empty***); struct one_item { void* a; }; ENCODING(STRUCT_ONE_ITEM, struct one_item); +ENCODING_INNER(STRUCT_ONE_ITEM_POINTER_POINTER, struct one_item**); +ENCODING_INNER(STRUCT_ONE_ITEM_POINTER_POINTER_POINTER, struct one_item***); struct two_items { float a; int b; }; ENCODING(STRUCT_TWO_ITEMS, struct two_items); -// TODO: Structs with arrays, and vice-versa +struct with_arrays { + int a[1]; + int* b[2]; + int (*c)[3]; +}; +ENCODING(STRUCT_WITH_ARRAYS, struct with_arrays); + +// Bit field + +struct bitfield { + unsigned int a: 1; + unsigned int b: 30; +}; +ENCODING(BITFIELD, struct bitfield); + +// Union + +union union_ { + float a; + int b; +}; +ENCODING(UNION, union union_); + +// Array +// Using typedefs because the type of pointers to arrays are hard to name. +// Also, atomic arrays does not exist + +typedef int arr[10]; +ENCODING_INNER(ARRAY_INT, arr); +ENCODING_INNER(ARRAY_INT_POINTER, arr*); + +typedef int* arr_ptr[10]; +ENCODING_INNER(ARRAY_POINTER, arr_ptr); +ENCODING_INNER(ARRAY_POINTER_POINTER, arr_ptr*); + +typedef int arr_nested[10][20]; +ENCODING_INNER(ARRAY_NESTED, arr_nested); +ENCODING_INNER(ARRAY_NESTED_POINTER, arr_nested*); + +typedef struct two_items arr_struct[0]; +ENCODING_INNER(ARRAY_STRUCT, arr_struct); +ENCODING_INNER(ARRAY_STRUCT_POINTER, arr_struct*); // Objective-C diff --git a/tests/src/test_encode_utils.rs b/tests/src/test_encode_utils.rs index c5a848047..b74249229 100644 --- a/tests/src/test_encode_utils.rs +++ b/tests/src/test_encode_utils.rs @@ -3,12 +3,14 @@ use alloc::format; use core::fmt::Display; use objc2::ffi::{NSInteger, NSUInteger}; use objc2::runtime::{Bool, Class, Object, Sel}; -use objc2_encode::Encoding; +use objc2_encode::{Encode, Encoding, RefEncode}; use paste::paste; use std::ffi::CStr; use std::os::raw::*; use std::string::ToString; +use super::*; + unsafe fn assert_encoding(s: *const c_char, e: Encoding) { let s = CStr::from_ptr(s).to_str().unwrap(); if !e.equivalent_to_str(s) { @@ -55,15 +57,12 @@ macro_rules! assert_types { )+) => {$( paste! { assert_inner!(enc $(#[$m])* [] => <$type>::ENCODING); - assert_inner!(enc $(#[$m])* [] => <*const $type>::ENCODING); + assert_inner!(enc $(#[$m])* [] => <$type>::ENCODING_REF); assert_inner!(str $(#[$m])* [] => format!("A{}", <$type>::ENCODING)); } )+}; } -use super::*; -use objc2_encode::Encode; - assert_types! { // C types @@ -93,17 +92,25 @@ assert_types! { // VOID => void, - // Array - - // ARRAY_INT - // Struct // STRUCT_EMPTY // STRUCT_ONE_ITEM // STRUCT_TWO_ITEMS + // STRUCT_WITH_ARRAYS + + // Bitfields + + // BITFIELD + + // Array + + // ARRAY_INT + // ARRAY_POINTER + // ARRAY_NESTED + // ARRAY_STRUCT - // // Objective-C + // Objective-C OBJC_BOOL => Bool, ID => *mut Object, @@ -158,13 +165,13 @@ assert_inner!(enc ENCODING_VOID_POINTER_POINTER => <*const *const c_void>::ENCOD // `[unsigned] long`s are weird: -assert_inner!(enc ENCODING_LONG => Encoding::LONG); -assert_inner!(enc ENCODING_LONG_POINTER => Encoding::Pointer(&Encoding::LONG)); -assert_inner!(str ENCODING_LONG_ATOMIC => format!("A{}", Encoding::LONG)); +assert_inner!(enc ENCODING_LONG => Encoding::C_LONG); +assert_inner!(enc ENCODING_LONG_POINTER => Encoding::Pointer(&Encoding::C_LONG)); +assert_inner!(str ENCODING_LONG_ATOMIC => format!("A{}", Encoding::C_LONG)); -assert_inner!(enc ENCODING_UNSIGNED_LONG => Encoding::U_LONG); -assert_inner!(enc ENCODING_UNSIGNED_LONG_POINTER => Encoding::Pointer(&Encoding::U_LONG)); -assert_inner!(str ENCODING_UNSIGNED_LONG_ATOMIC => format!("A{}", Encoding::U_LONG)); +assert_inner!(enc ENCODING_UNSIGNED_LONG => Encoding::C_U_LONG); +assert_inner!(enc ENCODING_UNSIGNED_LONG_POINTER => Encoding::Pointer(&Encoding::C_U_LONG)); +assert_inner!(str ENCODING_UNSIGNED_LONG_ATOMIC => format!("A{}", Encoding::C_U_LONG)); // No appropriate Rust types for these: @@ -184,23 +191,20 @@ assert_inner!(enc ENCODING_LONG_DOUBLE_COMPLEX => Encoding::LongDoubleComplex); assert_inner!(enc ENCODING_LONG_DOUBLE_COMPLEX_POINTER => Encoding::Pointer(&Encoding::LongDoubleComplex)); assert_inner!(str ENCODING_LONG_DOUBLE_COMPLEX_ATOMIC => format!("A{}", Encoding::LongDoubleComplex)); -// Arrays (atomics and pointers are weirdly encoded?) - -const ARRAY_ENC: Encoding<'static> = Encoding::Array(10, &c_int::ENCODING); -assert_inner!(enc ENCODING_ARRAY_INT => ARRAY_ENC); -assert_inner!(str ENCODING_ARRAY_INT_POINTER => "[10^i]"); -assert_inner!(str ENCODING_ARRAY_INT_ATOMIC => "[10Ai]"); - -// Structs (atomics erase type information) +// Structs (atomics or double indirection erase type information) const ENC0: Encoding<'static> = Encoding::Struct("empty", &[]); assert_inner!(enc ENCODING_STRUCT_EMPTY => ENC0); assert_inner!(enc ENCODING_STRUCT_EMPTY_POINTER => Encoding::Pointer(&ENC0)); +assert_inner!(str ENCODING_STRUCT_EMPTY_POINTER_POINTER => "^^{empty}"); +assert_inner!(str ENCODING_STRUCT_EMPTY_POINTER_POINTER_POINTER => "^^^{empty}"); assert_inner!(str ENCODING_STRUCT_EMPTY_ATOMIC => "A{empty}"); const ENC1: Encoding<'static> = Encoding::Struct("one_item", &[<*const c_void>::ENCODING]); assert_inner!(enc ENCODING_STRUCT_ONE_ITEM => ENC1); assert_inner!(enc ENCODING_STRUCT_ONE_ITEM_POINTER => Encoding::Pointer(&ENC1)); +assert_inner!(str ENCODING_STRUCT_ONE_ITEM_POINTER_POINTER => "^^{one_item}"); +assert_inner!(str ENCODING_STRUCT_ONE_ITEM_POINTER_POINTER_POINTER => "^^^{one_item}"); assert_inner!(str ENCODING_STRUCT_ONE_ITEM_ATOMIC => "A{one_item}"); const ENC2: Encoding<'static> = Encoding::Struct("two_items", &[f32::ENCODING, c_int::ENCODING]); @@ -208,6 +212,69 @@ assert_inner!(enc ENCODING_STRUCT_TWO_ITEMS => ENC2); assert_inner!(enc ENCODING_STRUCT_TWO_ITEMS_POINTER => Encoding::Pointer(&ENC2)); assert_inner!(str ENCODING_STRUCT_TWO_ITEMS_ATOMIC => "A{two_items}"); +const WITH_ARRAYS: Encoding<'static> = Encoding::Struct( + "with_arrays", + &[ + <[c_int; 1]>::ENCODING, + <[&c_int; 2]>::ENCODING, + <&[c_int; 3]>::ENCODING, + ], +); +assert_inner!(str ENCODING_STRUCT_WITH_ARRAYS => WITH_ARRAYS); +assert_inner!(str ENCODING_STRUCT_WITH_ARRAYS_POINTER => Encoding::Pointer(&WITH_ARRAYS)); +assert_inner!(str ENCODING_STRUCT_WITH_ARRAYS_ATOMIC => "A{with_arrays}"); + +// Bitfields + +#[cfg(not(gnustep))] +mod bitfields { + use super::*; + + const BITFIELD: Encoding<'static> = Encoding::Struct( + "bitfield", + &[ + Encoding::BitField(1, &Encoding::UInt), + Encoding::BitField(30, &Encoding::UInt), + ], + ); + assert_inner!(enc ENCODING_BITFIELD => BITFIELD); + assert_inner!(enc ENCODING_BITFIELD_POINTER => Encoding::Pointer(&BITFIELD)); + assert_inner!(str ENCODING_BITFIELD_ATOMIC => "A{bitfield}"); +} + +#[cfg(gnustep)] +mod bitfields { + use super::*; + + assert_inner!(str ENCODING_BITFIELD => "{bitfield=b0I1b1I30}"); + assert_inner!(str ENCODING_BITFIELD_POINTER => "^{bitfield=b0I1b1I30}"); + assert_inner!(str ENCODING_BITFIELD_ATOMIC => "A{bitfield}"); +} + +// Unions + +const UNION: Encoding<'static> = Encoding::Union("union_", &[f32::ENCODING, c_int::ENCODING]); +assert_inner!(enc ENCODING_UNION => UNION); +assert_inner!(enc ENCODING_UNION_POINTER => Encoding::Pointer(&UNION)); +assert_inner!(str ENCODING_UNION_ATOMIC => "A(union_)"); + +// Arrays (atomics are not supported) + +type ARRAY = [c_int; 10]; +assert_inner!(enc ENCODING_ARRAY_INT => ARRAY::ENCODING); +assert_inner!(enc ENCODING_ARRAY_INT_POINTER => ARRAY::ENCODING_REF); + +type POINTER = [*const c_int; 10]; +assert_inner!(enc ENCODING_ARRAY_POINTER => POINTER::ENCODING); +assert_inner!(str ENCODING_ARRAY_POINTER_POINTER => POINTER::ENCODING_REF); + +type NESTED = [[c_int; 20]; 10]; +assert_inner!(enc ENCODING_ARRAY_NESTED => NESTED::ENCODING); +assert_inner!(str ENCODING_ARRAY_NESTED_POINTER => NESTED::ENCODING_REF); + +assert_inner!(enc ENCODING_ARRAY_STRUCT => Encoding::Array(0, &ENC2)); +assert_inner!(str ENCODING_ARRAY_STRUCT_POINTER => Encoding::Pointer(&Encoding::Array(0, &ENC2))); + // UUIDs assert_inner!(enc ENCODING_UUID_T => Encoding::Array(16, &u8::ENCODING));