objc2_foundation/
number.rs

1//! Note that due to limitations in Objective-C type encodings, it is not
2//! possible to distinguish between an `NSNumber` created from [`bool`],
3//! and one created from an [`i8`]/[`u8`]. You should use the getter
4//! methods that fit your use-case instead!
5//!
6//! This does not implement [`Eq`] nor [`Ord`], since it may contain a
7//! floating point value. Beware that the implementation of [`PartialEq`]
8//! and [`PartialOrd`] does not properly handle NaNs either. Compare
9//! [`NSNumber::encoding`] with [`Encoding::Float`] or
10//! [`Encoding::Double`], and use [`NSNumber::as_f32`] or
11//! [`NSNumber::as_f64`] to get the desired floating point value directly.
12//!
13//! TODO: Once we have better CoreFoundation support, use that to create
14//! efficient static numbers. See:
15//! <https://github.com/nvzqz/fruity/blob/811d7787495eaaee6bc39d372004e5d96ef9f49b/src/foundation/ns_number.rs#L328-L401>
16//!
17//! (Same goes for `NSNull`).
18#[cfg(feature = "NSObjCRuntime")]
19use core::cmp::Ordering;
20use core::fmt;
21use core::hash;
22use core::panic::{RefUnwindSafe, UnwindSafe};
23
24use objc2::encode::Encoding;
25use objc2::msg_send;
26use objc2::rc::Retained;
27use objc2::runtime::NSObject;
28
29use crate::util;
30use crate::NSNumber;
31
32impl UnwindSafe for NSNumber {}
33impl RefUnwindSafe for NSNumber {}
34
35macro_rules! def_new_fn {
36    {$(
37        $(#[$($m:meta)*])*
38        ($fn_name:ident($fn_inp:ty); $method_name:ident),
39    )*} => {$(
40        $(#[$($m)*])*
41        pub fn $fn_name(val: $fn_inp) -> Retained<Self> {
42            Self::$method_name(val as _)
43        }
44    )*}
45}
46
47/// Creation methods.
48impl NSNumber {
49    def_new_fn! {
50        (new_bool(bool); numberWithBool),
51        (new_i8(i8); numberWithChar),
52        (new_u8(u8); numberWithUnsignedChar),
53        (new_i16(i16); numberWithShort),
54        (new_u16(u16); numberWithUnsignedShort),
55        (new_i32(i32); numberWithInt),
56        (new_u32(u32); numberWithUnsignedInt),
57        (new_i64(i64); numberWithLongLong),
58        (new_u64(u64); numberWithUnsignedLongLong),
59        (new_isize(isize); numberWithInteger),
60        (new_usize(usize); numberWithUnsignedInteger),
61        (new_f32(f32); numberWithFloat),
62        (new_f64(f64); numberWithDouble),
63    }
64
65    #[inline]
66    #[cfg(all(feature = "objc2-core-foundation", feature = "NSGeometry"))]
67    pub fn new_cgfloat(val: objc2_core_foundation::CGFloat) -> Retained<Self> {
68        #[cfg(target_pointer_width = "64")]
69        {
70            Self::new_f64(val)
71        }
72        #[cfg(not(target_pointer_width = "64"))]
73        {
74            Self::new_f32(val)
75        }
76    }
77}
78
79macro_rules! def_get_fn {
80    {$(
81        $(#[$($m:meta)*])*
82        ($fn_name:ident -> $fn_ret:ty; $method_name:ident),
83    )*} => {$(
84        $(#[$($m)*])*
85        pub fn $fn_name(&self) -> $fn_ret {
86            self.$method_name() as _
87        }
88    )*}
89}
90
91/// Getter methods.
92impl NSNumber {
93    def_get_fn! {
94        (as_bool -> bool; boolValue),
95        (as_i8 -> i8; charValue),
96        (as_u8 -> u8; unsignedCharValue),
97        (as_i16 -> i16; shortValue),
98        (as_u16 -> u16; unsignedShortValue),
99        (as_i32 -> i32; intValue),
100        (as_u32 -> u32; unsignedIntValue),
101        (as_i64 -> i64; longLongValue),
102        (as_u64 -> u64; unsignedLongLongValue),
103        (as_isize -> isize; integerValue),
104        (as_usize -> usize; unsignedIntegerValue),
105        (as_f32 -> f32; floatValue),
106        (as_f64 -> f64; doubleValue),
107    }
108
109    #[inline]
110    #[cfg(all(feature = "objc2-core-foundation", feature = "NSGeometry"))]
111    pub fn as_cgfloat(&self) -> objc2_core_foundation::CGFloat {
112        #[cfg(target_pointer_width = "64")]
113        {
114            self.as_f64()
115        }
116        #[cfg(not(target_pointer_width = "64"))]
117        {
118            self.as_f32()
119        }
120    }
121
122    /// The Objective-C encoding of this `NSNumber`.
123    ///
124    /// This is guaranteed to return one of:
125    /// - [`Encoding::Char`]
126    /// - [`Encoding::UChar`]
127    /// - [`Encoding::Short`]
128    /// - [`Encoding::UShort`]
129    /// - [`Encoding::Int`]
130    /// - [`Encoding::UInt`]
131    /// - [`Encoding::Long`]
132    /// - [`Encoding::ULong`]
133    /// - [`Encoding::LongLong`]
134    /// - [`Encoding::ULongLong`]
135    /// - [`Encoding::Float`]
136    /// - [`Encoding::Double`]
137    ///
138    ///
139    /// # Examples
140    ///
141    /// Convert an `NSNumber` to/from an enumeration describing the different
142    /// number properties.
143    ///
144    /// ```
145    /// use objc2_foundation::NSNumber;
146    /// use objc2::encode::Encoding;
147    /// use objc2::rc::Retained;
148    ///
149    /// // Note: `bool` would convert to either `Signed` or `Unsigned`,
150    /// // depending on platform
151    /// #[derive(Copy, Clone)]
152    /// pub enum Number {
153    ///     Signed(i64),
154    ///     Unsigned(u64),
155    ///     Floating(f64),
156    /// }
157    ///
158    /// impl Number {
159    ///     fn into_nsnumber(self) -> Retained<NSNumber> {
160    ///         match self {
161    ///             Self::Signed(val) => NSNumber::new_i64(val),
162    ///             Self::Unsigned(val) => NSNumber::new_u64(val),
163    ///             Self::Floating(val) => NSNumber::new_f64(val),
164    ///         }
165    ///     }
166    /// }
167    ///
168    /// impl From<&NSNumber> for Number {
169    ///     fn from(n: &NSNumber) -> Self {
170    ///         match n.encoding() {
171    ///             Encoding::Char
172    ///             | Encoding::Short
173    ///             | Encoding::Int
174    ///             | Encoding::Long
175    ///             | Encoding::LongLong => Self::Signed(n.as_i64()),
176    ///             Encoding::UChar
177    ///             | Encoding::UShort
178    ///             | Encoding::UInt
179    ///             | Encoding::ULong
180    ///             | Encoding::ULongLong => Self::Unsigned(n.as_u64()),
181    ///             Encoding::Float
182    ///             | Encoding::Double => Self::Floating(n.as_f64()),
183    ///             _ => unreachable!(),
184    ///         }
185    ///     }
186    /// }
187    /// ```
188    pub fn encoding(&self) -> Encoding {
189        // Use NSValue::encoding
190        let enc = (**self)
191            .encoding()
192            .expect("NSNumber must have an encoding!");
193
194        // Guaranteed under "Subclassing Notes"
195        // <https://developer.apple.com/documentation/foundation/nsnumber?language=objc#1776615>
196        match enc {
197            "c" => Encoding::Char,
198            "C" => Encoding::UChar,
199            "s" => Encoding::Short,
200            "S" => Encoding::UShort,
201            "i" => Encoding::Int,
202            "I" => Encoding::UInt,
203            "l" => Encoding::Long,
204            "L" => Encoding::ULong,
205            "q" => Encoding::LongLong,
206            "Q" => Encoding::ULongLong,
207            "f" => Encoding::Float,
208            "d" => Encoding::Double,
209            _ => unreachable!("invalid encoding for NSNumber"),
210        }
211    }
212}
213
214impl hash::Hash for NSNumber {
215    #[inline]
216    fn hash<H: hash::Hasher>(&self, state: &mut H) {
217        (**self).hash(state);
218    }
219}
220
221/// Beware: This uses the Objective-C method "isEqualToNumber:", which has
222/// different floating point NaN semantics than Rust!
223impl PartialEq for NSNumber {
224    #[doc(alias = "isEqualToNumber:")]
225    fn eq(&self, other: &Self) -> bool {
226        // Use isEqualToNumber: instead of isEqual: since it is faster
227        self.isEqualToNumber(other)
228    }
229}
230
231/// Beware: This uses the Objective-C method "isEqualToNumber:", which has
232/// different floating point NaN semantics than Rust!
233//
234// This is valid since the following pass (i.e. Objective-C says that two NaNs
235// are equal):
236// ```
237// let nan = NSNumber::from_f32(f32::NAN);
238// assert_eq!(nan, nan);
239// ```
240impl Eq for NSNumber {}
241
242/// Beware: This uses the Objective-C method "compare:", which has different
243/// floating point NaN semantics than Rust!
244#[cfg(feature = "NSObjCRuntime")]
245impl PartialOrd for NSNumber {
246    #[doc(alias = "compare:")]
247    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
248        Some(self.cmp(other))
249    }
250}
251
252/// Beware: This uses the Objective-C method "compare:", which has different
253/// floating point NaN semantics than Rust!
254#[cfg(feature = "NSObjCRuntime")]
255impl Ord for NSNumber {
256    #[doc(alias = "compare:")]
257    fn cmp(&self, other: &Self) -> Ordering {
258        // Use Objective-C semantics for comparison
259        self.compare(other).into()
260    }
261}
262
263impl fmt::Display for NSNumber {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        let string: Retained<NSObject> = unsafe { msg_send![self, stringValue] };
266        // SAFETY: `stringValue` returns `NSString`.
267        unsafe { util::display_string(&string, f) }
268    }
269}
270
271impl fmt::Debug for NSNumber {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        // Delegate to -[NSObject description]
274        // (happens to return the same as -[NSNumber stringValue])
275        fmt::Debug::fmt(&***self, f)
276    }
277}