Skip to main content

objc2/runtime/
bool.rs

1#![allow(clippy::upper_case_acronyms)]
2use core::{fmt, hash};
3
4use crate::encode::{Encode, Encoding, RefEncode};
5
6// Apple's objc4.
7//
8// Don't be fooled by the backup definition in `objc.h`; __OBJC_BOOL_IS_BOOL
9// is always defined by `clang` when compiling Objective-C sources. The below
10// cfgs are determined experimentally via cross compiling.
11//
12// See also <https://www.jviotti.com/2024/01/05/is-objective-c-bool-a-boolean-type-it-depends.html>.
13#[cfg(all(not(feature = "gnustep-1-7"), not(feature = "unstable-objfw")))]
14mod inner {
15    // __OBJC_BOOL_IS_BOOL
16    #[cfg(any(
17        // aarch64-apple-*
18        target_arch = "aarch64",
19        // + x86_64-apple-ios (i.e. the simulator) (but not x86_64-apple-ios-macabi)
20        all(target_os = "ios", target_pointer_width = "64", not(target_abi_macabi)),
21        // + x86_64-apple-tvos
22        all(target_os = "tvos", target_pointer_width = "64"),
23        // + *-apple-watchos
24        target_os = "watchos",
25    ))]
26    // C: _Bool
27    pub(crate) type BOOL = bool;
28
29    // Inverse of the above
30    #[cfg(not(any(
31        target_arch = "aarch64",
32        all(target_os = "ios", target_pointer_width = "64", not(target_abi_macabi)),
33        all(target_os = "tvos", target_pointer_width = "64"),
34        target_os = "watchos",
35    )))]
36    // C: (explicitly) signed char
37    pub(crate) type BOOL = i8;
38}
39
40// GNUStep's and Microsoft's libobjc2
41#[cfg(all(
42    feature = "gnustep-1-7",
43    feature = "unstable-gnustep-strict-apple-compat"
44))]
45mod inner {
46    // C: (explicitly) signed char
47    pub(crate) type BOOL = i8;
48}
49
50#[cfg(all(
51    feature = "gnustep-1-7",
52    not(feature = "unstable-gnustep-strict-apple-compat")
53))]
54mod inner {
55    // windows && !32bit-MinGW
56    #[cfg(all(windows, not(all(target_pointer_width = "64", target_env = "gnu"))))]
57    pub(crate) type BOOL = core::ffi::c_int;
58
59    // The inverse
60    #[cfg(not(all(windows, not(all(target_pointer_width = "64", target_env = "gnu")))))]
61    // C: unsigned char
62    pub(crate) type BOOL = u8;
63}
64
65// ObjFW
66#[cfg(feature = "unstable-objfw")]
67mod inner {
68    // Defined in ObjFW-RT.h
69    // C: signed char
70    // This has changed since v0.90, but we don't support that yet.
71    pub(crate) type BOOL = i8;
72
73    // Note that ObjFW usually uses `bool` in return types, but that doesn't
74    // change the ABI, so we'll use `BOOL` there as well, for ease of use.
75}
76
77/// The Objective-C `BOOL` type.
78///
79/// The type of `BOOL` varies across platforms, so we expose this wrapper. It
80/// is intended that you convert this into a Rust [`bool`] with the
81/// [`Bool::as_bool`] method as soon as possible.
82///
83/// This is FFI-safe and can be used directly with `msg_send!` and `extern`
84/// functions as a substitute for `BOOL` in Objective-C. If your Objective-C
85/// code uses C99 `_Bool`, you should use a `#[repr(transparent)]` wrapper
86/// around `bool` instead.
87///
88/// Note that this is able to contain more states than `bool` on some
89/// platforms, but these cases should not be relied on! The comparison traits
90/// `PartialEq`, `PartialOrd` etc. will completely ignore these states.
91///
92/// See also the [corresponding documentation entry][docs].
93///
94/// [docs]: https://developer.apple.com/documentation/objectivec/bool?language=objc
95#[repr(transparent)]
96#[derive(Copy, Clone, Default)]
97pub struct Bool {
98    value: inner::BOOL,
99}
100
101impl Bool {
102    /// The equivalent of [`true`] for Objective-C's `BOOL` type.
103    #[allow(clippy::unnecessary_cast)]
104    pub const YES: Self = Self::from_raw(true as inner::BOOL); // true -> 1
105
106    /// The equivalent of [`false`] for Objective-C's `BOOL` type.
107    #[allow(clippy::unnecessary_cast)]
108    pub const NO: Self = Self::from_raw(false as inner::BOOL); // false -> 0
109
110    /// Creates an Objective-C boolean from a Rust boolean.
111    #[inline]
112    pub const fn new(value: bool) -> Self {
113        // true as BOOL => 1 (YES)
114        // false as BOOL => 0 (NO)
115        let value = value as inner::BOOL;
116        Self { value }
117    }
118
119    /// Creates this from a raw boolean value.
120    ///
121    /// Avoid this, and instead use `Bool` in the raw FFI signature.
122    #[inline]
123    pub const fn from_raw(value: inner::BOOL) -> Self {
124        Self { value }
125    }
126
127    /// Retrieves the inner boolean type.
128    ///
129    /// Avoid this, and instead use `Bool` in the raw FFI signature.
130    #[inline]
131    pub const fn as_raw(self) -> inner::BOOL {
132        self.value
133    }
134
135    /// Returns `true` if `self` is [`NO`][Self::NO].
136    ///
137    /// You should prefer using [`as_bool`][Self::as_bool].
138    #[inline]
139    pub const fn is_false(self) -> bool {
140        !self.as_bool()
141    }
142
143    /// Returns `true` if `self` is not [`NO`][Self::NO].
144    ///
145    /// You should prefer using [`as_bool`][Self::as_bool].
146    #[inline]
147    pub const fn is_true(self) -> bool {
148        self.as_bool()
149    }
150
151    /// Converts this into the [`bool`] equivalent.
152    #[inline]
153    pub const fn as_bool(self) -> bool {
154        // Always compare with 0 (NO)
155        // This is what happens with the `!` operator / when using `if` in C.
156        self.value != (false as inner::BOOL)
157    }
158}
159
160impl From<bool> for Bool {
161    #[inline]
162    fn from(b: bool) -> Bool {
163        Bool::new(b)
164    }
165}
166
167impl From<Bool> for bool {
168    #[inline]
169    fn from(b: Bool) -> bool {
170        b.as_bool()
171    }
172}
173
174impl fmt::Debug for Bool {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.write_str(if self.as_bool() { "YES" } else { "NO" })
177    }
178}
179
180// Implement comparison traits by first converting to a boolean.
181
182impl PartialEq for Bool {
183    #[inline]
184    fn eq(&self, other: &Self) -> bool {
185        self.as_bool() == other.as_bool()
186    }
187}
188
189impl Eq for Bool {}
190
191impl hash::Hash for Bool {
192    #[inline]
193    fn hash<H: hash::Hasher>(&self, state: &mut H) {
194        self.as_bool().hash(state);
195    }
196}
197
198impl PartialOrd for Bool {
199    #[inline]
200    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
201        Some(self.cmp(other))
202    }
203}
204
205impl Ord for Bool {
206    #[inline]
207    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
208        self.as_bool().cmp(&other.as_bool())
209    }
210}
211
212trait Helper {
213    const __ENCODING: Encoding;
214}
215
216impl<T: Encode> Helper for T {
217    const __ENCODING: Encoding = T::ENCODING;
218}
219
220impl Helper for bool {
221    const __ENCODING: Encoding = Encoding::Bool;
222}
223
224// SAFETY: `Bool` is `repr(transparent)`.
225unsafe impl Encode for Bool {
226    // i8::__ENCODING == Encoding::Char
227    // u8::__ENCODING == Encoding::UChar
228    // bool::__ENCODING == Encoding::Bool
229    // i32::__ENCODING == Encoding::Int
230    const ENCODING: Encoding = inner::BOOL::__ENCODING;
231}
232
233// Note that we shouldn't delegate to `BOOL`'s  `ENCODING_REF` since `BOOL` is
234// sometimes `i8`/`u8`, and their `ENCODING_REF`s are `Encoding::String`,
235// which is incorrect for `BOOL`:
236//
237// ```objc
238// @encode(BOOL); // -> "c", "C" or "B"
239// @encode(BOOL*); // -> "^c", "^C" or "^B"
240// @encode(char); // -> "c" or "C"
241// @encode(char*); // -> "*"
242// ```
243unsafe impl RefEncode for Bool {
244    const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use crate::__macro_helpers::{ConvertArgument, ConvertReturn};
251    use alloc::format;
252
253    #[test]
254    fn test_basic() {
255        let b = Bool::new(true);
256        assert!(b.as_bool());
257        assert!(b.is_true());
258        assert!(!b.is_false());
259        assert!(bool::from(b));
260        assert_eq!(b.as_raw() as usize, 1);
261
262        let b = Bool::new(false);
263        assert!(!b.as_bool());
264        assert!(!b.is_true());
265        assert!(b.is_false());
266        assert!(!bool::from(b));
267        assert_eq!(b.as_raw() as usize, 0);
268    }
269
270    #[test]
271    fn test_associated_constants() {
272        let b = Bool::YES;
273        assert!(b.as_bool());
274        assert!(b.is_true());
275        assert_eq!(b.as_raw() as usize, 1);
276
277        let b = Bool::NO;
278        assert!(!b.as_bool());
279        assert!(b.is_false());
280        assert_eq!(b.as_raw() as usize, 0);
281    }
282
283    #[test]
284    fn test_encode() {
285        assert_eq!(bool::__ENCODING, Encoding::Bool);
286
287        assert_eq!(
288            <bool as ConvertArgument>::__Inner::__ENCODING,
289            <bool as ConvertArgument>::__Inner::ENCODING
290        );
291        assert_eq!(
292            <bool as ConvertReturn<()>>::Inner::__ENCODING,
293            <bool as ConvertReturn<()>>::Inner::ENCODING
294        );
295    }
296
297    #[test]
298    fn test_impls() {
299        let b: Bool = Default::default();
300        assert!(!b.as_bool());
301        assert!(b.is_false());
302
303        assert!(Bool::from(true).as_bool());
304        assert!(Bool::from(true).is_true());
305        assert!(Bool::from(false).is_false());
306
307        assert!(Bool::from(true).is_true());
308        assert!(Bool::from(false).is_false());
309
310        assert_eq!(Bool::new(true), Bool::new(true));
311        assert_eq!(Bool::new(false), Bool::new(false));
312
313        assert!(Bool::new(false) < Bool::new(true));
314    }
315
316    #[test]
317    fn test_debug() {
318        assert_eq!(format!("{:?}", Bool::from(true)), "YES");
319        assert_eq!(format!("{:?}", Bool::from(false)), "NO");
320    }
321
322    #[test]
323    // Test on platform where we know the type of BOOL
324    #[cfg(all(target_vendor = "apple", target_os = "macos", target_arch = "x86_64"))]
325    fn test_outside_normal() {
326        let b = Bool::from_raw(42);
327        assert!(b.is_true());
328        assert!(!b.is_false());
329        assert_eq!(b.as_raw(), 42);
330
331        // PartialEq ignores extra data
332        assert_eq!(b, Bool::new(true));
333        assert_ne!(b, Bool::new(false));
334    }
335}