objc2_core_foundation/base.rs
1// The header `CoreFoundation/CFBase.h` contains:
2//
3// #if defined(__WIN64__) && !defined(__LLP64__)
4// #define __LLP64__ 1
5// #endif
6//
7// #if __LLP64__
8// typedef unsigned long long CFTypeID;
9// typedef unsigned long long CFOptionFlags;
10// typedef unsigned long long CFHashCode;
11// typedef signed long long CFIndex;
12// #else
13// typedef unsigned long CFTypeID;
14// typedef unsigned long CFOptionFlags;
15// typedef unsigned long CFHashCode;
16// typedef signed long CFIndex;
17// #endif
18//
19// Looking at the corresponding Rust definitions for longs:
20// <https://doc.rust-lang.org/1.83.0/src/core/ffi/mod.rs.html#168-179>
21// cfg_if! {
22// if #[cfg(all(target_pointer_width = "64", not(windows)))] {
23// pub type c_long = i64;
24// pub type c_ulong = u64;
25// } else {
26// // The minimal size of `long` in the C standard is 32 bits
27// pub type c_long = i32;
28// pub type c_ulong = u32;
29// }
30// }
31// <https://doc.rust-lang.org/1.83.0/src/core/ffi/mod.rs.html#65-66>
32// pub type c_longlong = i64;
33// pub type c_ulonglong = u64;
34//
35// It becomes easy to convince ourselves that combined, these amount to making
36// these types be 32-bit on systems with 32-bit pointers and 64-bit on systems
37// with 64-bit pointers.
38//
39// That means we can use `isize`/`usize`, which is more ergonomic.
40
41use core::cell::UnsafeCell;
42use core::cmp::Ordering;
43use core::convert::AsRef;
44use core::fmt;
45use core::hash;
46use core::marker::{PhantomData, PhantomPinned};
47
48use crate::{
49 CFComparisonResult, CFEqual, CFGetRetainCount, CFGetTypeID, CFHash, CFRange, ConcreteType, Type,
50};
51
52/// [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cftypeid?language=objc)
53pub type CFTypeID = usize;
54
55/// [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cfoptionflags?language=objc)
56pub type CFOptionFlags = usize;
57
58/// [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cfhashcode?language=objc)
59pub type CFHashCode = usize;
60
61/// [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cfindex?language=objc)
62pub type CFIndex = isize;
63
64// Manually define CFType
65
66/// An instance of a Core Foundation type.
67///
68/// This is meant to be used behind a reference. In the future, this will be
69/// defined as an [`extern type`][RFC-1861].
70///
71/// All Core Foundation types [`Deref`](std::ops::Deref) to this type (it can
72/// be considered the "root" type).
73///
74/// See also [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cftype?language=objc).
75///
76/// [RFC-1861]: https://rust-lang.github.io/rfcs/1861-extern-types.html
77#[repr(C)]
78pub struct CFType {
79 inner: [u8; 0],
80 _p: UnsafeCell<PhantomData<(*const UnsafeCell<()>, PhantomPinned)>>,
81}
82
83impl CFType {
84 /// Attempt to downcast the type to that of type `T`.
85 ///
86 /// This is the reference-variant. Use [`CFRetained::downcast`] if you
87 /// want to convert a retained type. See also [`ConcreteType`] for more
88 /// details on which types support being converted to.
89 ///
90 /// [`CFRetained::downcast`]: crate::CFRetained::downcast
91 //
92 // Not #[inline], we call two functions here.
93 #[doc(alias = "CFGetTypeID")]
94 pub fn downcast_ref<T: ConcreteType>(&self) -> Option<&T> {
95 if CFGetTypeID(Some(self)) == T::type_id() {
96 let ptr: *const Self = self;
97 let ptr: *const T = ptr.cast();
98 // SAFETY: Just checked that the object is a class of type `T`.
99 // Additionally, `ConcreteType::type_id` is guaranteed to uniquely
100 // identify the class (including ruling out mutable subclasses),
101 // so we know for _sure_ that the class is actually of that type
102 // here.
103 let this: &T = unsafe { &*ptr };
104 Some(this)
105 } else {
106 None
107 }
108 }
109
110 /// Get the reference count of the object.
111 ///
112 /// This function may be useful for debugging. You normally do not use
113 /// this function otherwise.
114 ///
115 /// Beware that some things (like `CFNumber`s, small `CFString`s etc.) may
116 /// not have a normal retain count for optimization purposes, and can
117 /// return `usize::MAX` in that case.
118 #[doc(alias = "CFGetRetainCount")]
119 pub fn retain_count(&self) -> usize {
120 // Cast is fine, if the reference count is `-1` we want to return
121 // `usize::MAX` as a sentinel instead.
122 CFGetRetainCount(Some(self)) as _
123 }
124}
125
126// Reflexive AsRef impl.
127impl AsRef<Self> for CFType {
128 #[inline]
129 fn as_ref(&self) -> &Self {
130 self
131 }
132}
133
134// SAFETY: CFType represents a CoreFoundation-like type (even though it isn't
135// a real type itself).
136unsafe impl Type for CFType {}
137
138impl fmt::Debug for CFType {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 #[cfg(feature = "CFString")]
141 {
142 let desc = crate::CFCopyDescription(Some(self)).expect("must have description");
143 write!(f, "{desc}")
144 }
145 #[cfg(not(feature = "CFString"))]
146 {
147 f.debug_struct("<CoreFoundation type (enable CFString feature for more info)>")
148 .finish_non_exhaustive()
149 }
150 }
151}
152
153// Equality in CF has approximately the same semantics as Rust equality.
154//
155// From the docs:
156// > Equality is something specific to each Core Foundation opaque type. For
157// > example, two CFNumber objects are equal if the numeric values they
158// > represent are equal. Two CFString objects are equal if they represent
159// > identical sequences of characters, regardless of encoding.
160impl PartialEq for CFType {
161 #[inline]
162 #[doc(alias = "CFEqual")]
163 fn eq(&self, other: &Self) -> bool {
164 CFEqual(Some(self), Some(other))
165 }
166}
167
168// Similar to NSObject, most types' equality is reflexive.
169impl Eq for CFType {}
170
171// From the documentation for CFHash:
172// > Two objects that are equal (as determined by the `CFEqual` function) have
173// > the same hashing value. However, the converse is not true: two objects
174// > with the same hashing value might not be equal. That is, hashing values
175// > are not necessarily unique.
176//
177// I.e. the same semantics as Rust's `Hash`.
178impl hash::Hash for CFType {
179 #[doc(alias = "CFHash")]
180 fn hash<H: hash::Hasher>(&self, state: &mut H) {
181 CFHash(Some(self)).hash(state);
182 }
183}
184
185// SAFETY: CFType is defined as the following in the header:
186// typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;
187#[cfg(feature = "objc2")]
188unsafe impl objc2::encode::RefEncode for CFType {
189 const ENCODING_REF: objc2::encode::Encoding =
190 objc2::encode::Encoding::Pointer(&objc2::encode::Encoding::Void);
191}
192
193// SAFETY: CF types are message-able in the Objective-C runtime.
194#[cfg(feature = "objc2")]
195unsafe impl objc2::Message for CFType {}
196
197#[cfg(feature = "objc2")]
198impl AsRef<objc2::runtime::AnyObject> for CFType {
199 fn as_ref(&self) -> &objc2::runtime::AnyObject {
200 // SAFETY: CFType is valid to re-interpret as AnyObject.
201 unsafe { core::mem::transmute(self) }
202 }
203}
204
205#[cfg(feature = "objc2")]
206impl core::borrow::Borrow<objc2::runtime::AnyObject> for CFType {
207 fn borrow(&self) -> &objc2::runtime::AnyObject {
208 <Self as AsRef<objc2::runtime::AnyObject>>::as_ref(self)
209 }
210}
211
212// NOTE: impl AsRef<CFType> for AnyObject would probably not be valid, since
213// not all Objective-C objects can be used as CoreFoundation objects (?)
214
215impl Default for CFComparisonResult {
216 #[inline]
217 fn default() -> Self {
218 Self::CompareEqualTo
219 }
220}
221
222impl From<Ordering> for CFComparisonResult {
223 #[inline]
224 fn from(order: Ordering) -> Self {
225 match order {
226 Ordering::Less => Self::CompareLessThan,
227 Ordering::Equal => Self::CompareEqualTo,
228 Ordering::Greater => Self::CompareGreaterThan,
229 }
230 }
231}
232
233impl From<CFComparisonResult> for Ordering {
234 #[inline]
235 fn from(comparison_result: CFComparisonResult) -> Self {
236 match comparison_result.0 {
237 ..=-1 => Self::Less, // ..=CFComparisonResult::CompareLessThan
238 0 => Self::Equal, // CFComparisonResult::CompareEqualTo
239 1.. => Self::Greater, // CFComparisonResult::CompareGreaterThan..
240 #[allow(unreachable_patterns)] // MSRV between 1.73 and 1.76
241 _ => Self::Equal,
242 }
243 }
244}
245
246impl CFRange {
247 /// Create a new [`CFRange`].
248 #[doc(alias = "CFRangeMake")]
249 pub fn new(location: CFIndex, length: CFIndex) -> Self {
250 Self { location, length }
251 }
252}