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}