Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
523
third-party/vendor/objc2/src/foundation/__ns_string.rs
vendored
Normal file
523
third-party/vendor/objc2/src/foundation/__ns_string.rs
vendored
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
//! Macro for making a static NSString.
|
||||
//!
|
||||
//! This basically does what clang does, see:
|
||||
//! - Apple: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5057-L5249>
|
||||
//! - GNUStep 2.0 (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L973-L1118>
|
||||
//! - Other (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L2471-L2507>
|
||||
//!
|
||||
//! Note that this uses the `CFString` static, while `clang` has support for
|
||||
//! generating a pure `NSString`. We don't support that yet (since I don't
|
||||
//! know the use-case), but we definitely could!
|
||||
//! See: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L2007-L2068>
|
||||
use core::ffi::c_void;
|
||||
use core::mem::ManuallyDrop;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use crate::foundation::NSString;
|
||||
use crate::rc::Id;
|
||||
use crate::runtime::Class;
|
||||
|
||||
// This is defined in CoreFoundation, but we don't emit a link attribute
|
||||
// here because it is already linked via Foundation.
|
||||
//
|
||||
// Although this is a "private" (underscored) symbol, it is directly
|
||||
// referenced in Objective-C binaries. So it's safe for us to reference.
|
||||
extern "C" {
|
||||
pub static __CFConstantStringClassReference: Class;
|
||||
}
|
||||
|
||||
/// Structure used to describe a constant `CFString`.
|
||||
///
|
||||
/// This struct is the same as [`CF_CONST_STRING`], which contains
|
||||
/// [`CFRuntimeBase`]. While the documentation clearly says that the ABI of
|
||||
/// `CFRuntimeBase` should not be relied on, we can rely on it as long as we
|
||||
/// only do it with regards to `CFString` (because `clang` does this as well).
|
||||
///
|
||||
/// [`CFRuntimeBase`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.h#L216-L228>
|
||||
/// [`CF_CONST_STRING`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFInternal.h#L332-L336>
|
||||
#[repr(C)]
|
||||
pub struct CFConstString {
|
||||
isa: &'static Class,
|
||||
// Important that we don't just use `usize` here, since that would be
|
||||
// wrong on big-endian systems!
|
||||
cfinfo: u32,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
_rc: u32,
|
||||
data: *const c_void,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
// Required to place in a `static`.
|
||||
unsafe impl Sync for CFConstString {}
|
||||
|
||||
impl CFConstString {
|
||||
// From `CFString.c`:
|
||||
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFString.c#L184-L212>
|
||||
// > !!! Note: Constant CFStrings use the bit patterns:
|
||||
// > C8 (11001000 = default allocator, not inline, not freed contents; 8-bit; has NULL byte; doesn't have length; is immutable)
|
||||
// > D0 (11010000 = default allocator, not inline, not freed contents; Unicode; is immutable)
|
||||
// > The bit usages should not be modified in a way that would effect these bit patterns.
|
||||
//
|
||||
// Hence CoreFoundation guarantees that these two are always valid.
|
||||
//
|
||||
// The `CFTypeID` of `CFStringRef` is guaranteed to always be 7:
|
||||
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.c#L982>
|
||||
const FLAGS_ASCII: u32 = 0x07_C8;
|
||||
const FLAGS_UTF16: u32 = 0x07_D0;
|
||||
|
||||
pub const unsafe fn new_ascii(isa: &'static Class, data: &'static [u8]) -> Self {
|
||||
Self {
|
||||
isa,
|
||||
cfinfo: Self::FLAGS_ASCII,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
_rc: 0,
|
||||
data: data.as_ptr().cast(),
|
||||
// The length does not include the trailing NUL.
|
||||
len: data.len() - 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub const unsafe fn new_utf16(isa: &'static Class, data: &'static [u16]) -> Self {
|
||||
Self {
|
||||
isa,
|
||||
cfinfo: Self::FLAGS_UTF16,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
_rc: 0,
|
||||
data: data.as_ptr().cast(),
|
||||
// The length does not include the trailing NUL.
|
||||
len: data.len() - 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn as_nsstring_const(&self) -> &NSString {
|
||||
let ptr: *const Self = self;
|
||||
unsafe { &*ptr.cast::<NSString>() }
|
||||
}
|
||||
|
||||
// This is deliberately not `const` to prevent the result from being used
|
||||
// in other statics, since not all platforms support that (yet).
|
||||
#[inline]
|
||||
pub fn as_nsstring(&self) -> &NSString {
|
||||
self.as_nsstring_const()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `bytes` is entirely ASCII with no interior NULs.
|
||||
pub const fn is_ascii_no_nul(bytes: &[u8]) -> bool {
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let byte = bytes[i];
|
||||
if !byte.is_ascii() || byte == b'\0' {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub struct Utf16Char {
|
||||
pub repr: [u16; 2],
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl Utf16Char {
|
||||
const fn encode(ch: u32) -> Self {
|
||||
if ch <= 0xffff {
|
||||
Self {
|
||||
repr: [ch as u16, 0],
|
||||
len: 1,
|
||||
}
|
||||
} else {
|
||||
let payload = ch - 0x10000;
|
||||
let hi = (payload >> 10) | 0xd800;
|
||||
let lo = (payload & 0x3ff) | 0xdc00;
|
||||
Self {
|
||||
repr: [hi as u16, lo as u16],
|
||||
len: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn as_slice(&self) -> &[u16] {
|
||||
&self.repr[..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncodeUtf16Iter {
|
||||
str: &'static [u8],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl EncodeUtf16Iter {
|
||||
pub const fn new(str: &'static [u8]) -> Self {
|
||||
Self { str, index: 0 }
|
||||
}
|
||||
|
||||
pub const fn next(self) -> Option<(Self, Utf16Char)> {
|
||||
if self.index >= self.str.len() {
|
||||
None
|
||||
} else {
|
||||
let (index, ch) = decode_utf8(self.str, self.index);
|
||||
Some((Self { index, ..self }, Utf16Char::encode(ch)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (&str bytes, index) -> (new index, decoded char)
|
||||
const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
|
||||
let b0 = s[i];
|
||||
match b0 {
|
||||
// one-byte seq
|
||||
0b0000_0000..=0b0111_1111 => {
|
||||
let decoded = b0 as u32;
|
||||
(i + 1, decoded)
|
||||
}
|
||||
// two-byte seq
|
||||
0b1100_0000..=0b1101_1111 => {
|
||||
let decoded = ((b0 as u32 & 0x1f) << 6) | (s[i + 1] as u32 & 0x3f);
|
||||
(i + 2, decoded)
|
||||
}
|
||||
// 3 byte seq
|
||||
0b1110_0000..=0b1110_1111 => {
|
||||
let decoded = ((b0 as u32 & 0x0f) << 12)
|
||||
| ((s[i + 1] as u32 & 0x3f) << 6)
|
||||
| (s[i + 2] as u32 & 0x3f);
|
||||
(i + 3, decoded)
|
||||
}
|
||||
// 4 byte seq
|
||||
0b1111_0000..=0b1111_0111 => {
|
||||
let decoded = ((b0 as u32 & 0x07) << 18)
|
||||
| ((s[i + 1] as u32 & 0x3f) << 12)
|
||||
| ((s[i + 2] as u32 & 0x3f) << 6)
|
||||
| (s[i + 3] as u32 & 0x3f);
|
||||
(i + 4, decoded)
|
||||
}
|
||||
// continuation bytes, or never-valid bytes.
|
||||
0b1000_0000..=0b1011_1111 | 0b1111_1000..=0b1111_1111 => {
|
||||
panic!("Encountered invalid bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows storing a [`NSString`] in a static and lazily loading it.
|
||||
#[doc(hidden)]
|
||||
pub struct CachedNSString {
|
||||
ptr: AtomicPtr<NSString>,
|
||||
}
|
||||
|
||||
impl CachedNSString {
|
||||
/// Constructs a new [`CachedNSString`].
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ptr: AtomicPtr::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cached NSString. If no string is yet cached, creates one
|
||||
/// with the given name and stores it.
|
||||
#[inline]
|
||||
pub fn get(&self, s: &str) -> &'static NSString {
|
||||
// TODO: Investigate if we can use weaker orderings.
|
||||
let ptr = self.ptr.load(Ordering::SeqCst);
|
||||
// SAFETY: The pointer is either NULL, or has been created below.
|
||||
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
|
||||
// "Forget" about releasing the string, effectively promoting it
|
||||
// to a static.
|
||||
let s = ManuallyDrop::new(NSString::from_str(s));
|
||||
let ptr = Id::as_ptr(&s);
|
||||
self.ptr.store(ptr as *mut NSString, Ordering::SeqCst);
|
||||
// SAFETY: The pointer is valid, and will always be valid, since
|
||||
// we haven't released it.
|
||||
unsafe { ptr.as_ref().unwrap_unchecked() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [`NSString`][`crate::foundation::NSString`] from a static string.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This macro takes a either a `"string"` literal or `const` string slice as
|
||||
/// the argument, and produces a `&'static NSString`:
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::ns_string;
|
||||
/// use objc2::foundation::NSString;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let hello: &'static NSString = ns_string!("hello");
|
||||
/// assert_eq!(hello.to_string(), "hello");
|
||||
///
|
||||
/// const WORLD: &str = "world";
|
||||
/// let world = ns_string!(WORLD);
|
||||
/// assert_eq!(world.to_string(), WORLD);
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Unicode Strings
|
||||
///
|
||||
/// An NSString can contain strings with many different encodings, including
|
||||
/// ASCII, UTF-8, UTF-16, and so on. This macro automatically converts your
|
||||
/// string to the most efficient encoding, you don't have to do anything!
|
||||
///
|
||||
/// ```
|
||||
/// # use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let hello_ru = ns_string!("Привет");
|
||||
/// assert_eq!(hello_ru.to_string(), "Привет");
|
||||
/// ```
|
||||
///
|
||||
/// Note that because this is implemented with `const` evaluation, massive
|
||||
/// strings can increase compile time, and may even hit the `const` evaluation
|
||||
/// limit.
|
||||
///
|
||||
///
|
||||
/// # NUL handling
|
||||
///
|
||||
/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
|
||||
/// expect:
|
||||
///
|
||||
/// ```
|
||||
/// # use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let example = ns_string!("example\0");
|
||||
/// assert_eq!(example.to_string(), "example\0");
|
||||
///
|
||||
/// let example = ns_string!("exa\0mple");
|
||||
/// assert_eq!(example.to_string(), "exa\0mple");
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Runtime Cost
|
||||
///
|
||||
/// None.
|
||||
///
|
||||
/// The result is equivalent to `@"string"` syntax in Objective-C.
|
||||
///
|
||||
/// Because of that, this should be preferred over [`NSString::from_str`]
|
||||
/// where possible.
|
||||
///
|
||||
/// [`NSString::from_str`]: crate::foundation::NSString::from_str
|
||||
#[cfg(feature = "foundation")] // For auto_doc_cfg
|
||||
#[macro_export]
|
||||
macro_rules! ns_string {
|
||||
($s:expr) => {{
|
||||
// Immediately place in constant for better UI
|
||||
const INPUT: &str = $s;
|
||||
$crate::__ns_string_inner!(INPUT)
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "apple")]
|
||||
#[macro_export]
|
||||
macro_rules! __ns_string_inner {
|
||||
($inp:ident) => {{
|
||||
const X: &[u8] = $inp.as_bytes();
|
||||
$crate::__ns_string_inner!(@inner X);
|
||||
// Return &'static NSString
|
||||
CFSTRING.as_nsstring()
|
||||
}};
|
||||
(@inner $inp:ident) => {
|
||||
// Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
|
||||
// of the string, since we can't conditionally create a static.
|
||||
//
|
||||
// Since we don't add the `#[used]` attribute, Rust can fairly easily
|
||||
// figure out that one of the variants are never used, and simply
|
||||
// exclude it.
|
||||
|
||||
// Convert the input slice to a C-style string with a NUL byte.
|
||||
//
|
||||
// The section is the same as what clang sets, see:
|
||||
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
|
||||
#[link_section = "__TEXT,__cstring,cstring_literals"]
|
||||
static ASCII: [u8; $inp.len() + 1] = {
|
||||
// Zero-fill with $inp.len() + 1
|
||||
let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1];
|
||||
let mut i = 0;
|
||||
// Fill with data from $inp
|
||||
while i < $inp.len() {
|
||||
res[i] = $inp[i];
|
||||
i += 1;
|
||||
}
|
||||
// Now contains $inp + '\0'
|
||||
res
|
||||
};
|
||||
|
||||
// The full UTF-16 contents along with the written length.
|
||||
const UTF16_FULL: (&[u16; $inp.len()], usize) = {
|
||||
let mut out = [0u16; $inp.len()];
|
||||
let mut iter = $crate::foundation::__ns_string::EncodeUtf16Iter::new($inp);
|
||||
let mut written = 0;
|
||||
|
||||
while let Some((state, chars)) = iter.next() {
|
||||
iter = state;
|
||||
out[written] = chars.repr[0];
|
||||
written += 1;
|
||||
|
||||
if chars.len > 1 {
|
||||
out[written] = chars.repr[1];
|
||||
written += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(&{ out }, written)
|
||||
};
|
||||
|
||||
// Convert the slice to an UTF-16 array + a final NUL byte.
|
||||
//
|
||||
// The section is the same as what clang sets, see:
|
||||
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5193
|
||||
#[link_section = "__TEXT,__ustring"]
|
||||
static UTF16: [u16; UTF16_FULL.1 + 1] = {
|
||||
// Zero-fill with UTF16_FULL.1 + 1
|
||||
let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1];
|
||||
let mut i = 0;
|
||||
// Fill with data from UTF16_FULL.0 up until UTF16_FULL.1
|
||||
while i < UTF16_FULL.1 {
|
||||
res[i] = UTF16_FULL.0[i];
|
||||
i += 1;
|
||||
}
|
||||
// Now contains UTF16_FULL.1 + NUL
|
||||
res
|
||||
};
|
||||
|
||||
// Create the constant string structure, and store it in a static
|
||||
// within a special section.
|
||||
//
|
||||
// The section is the same as what clang sets, see:
|
||||
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
|
||||
#[link_section = "__DATA,__cfstring"]
|
||||
static CFSTRING: $crate::foundation::__ns_string::CFConstString = unsafe {
|
||||
if $crate::foundation::__ns_string::is_ascii_no_nul($inp) {
|
||||
// This is technically an optimization (UTF-16 strings are
|
||||
// always valid), but it's a fairly important one!
|
||||
$crate::foundation::__ns_string::CFConstString::new_ascii(
|
||||
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
|
||||
&ASCII,
|
||||
)
|
||||
} else {
|
||||
$crate::foundation::__ns_string::CFConstString::new_utf16(
|
||||
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
|
||||
&UTF16,
|
||||
)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "apple"))]
|
||||
#[macro_export]
|
||||
macro_rules! __ns_string_inner {
|
||||
($inp:ident) => {{
|
||||
use $crate::foundation::__ns_string::CachedNSString;
|
||||
static CACHED_NSSTRING: CachedNSString = CachedNSString::new();
|
||||
CACHED_NSSTRING.get($inp)
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_ascii() {
|
||||
assert!(is_ascii_no_nul(b"a"));
|
||||
assert!(is_ascii_no_nul(b"abc"));
|
||||
|
||||
assert!(!is_ascii_no_nul(b"\xff"));
|
||||
|
||||
assert!(!is_ascii_no_nul(b"\0"));
|
||||
assert!(!is_ascii_no_nul(b"a\0b"));
|
||||
assert!(!is_ascii_no_nul(b"ab\0"));
|
||||
assert!(!is_ascii_no_nul(b"a\0b\0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_utf8() {
|
||||
for c in '\u{0}'..=core::char::MAX {
|
||||
let mut buf;
|
||||
for off in 0..4 {
|
||||
// Ensure we see garbage if we read outside bounds.
|
||||
buf = [0xff; 8];
|
||||
let len = c.encode_utf8(&mut buf[off..(off + 4)]).len();
|
||||
let (end_idx, decoded) = decode_utf8(&buf, off);
|
||||
assert_eq!(
|
||||
(end_idx, decoded),
|
||||
(off + len, c as u32),
|
||||
"failed for U+{code:04X} ({ch:?}) encoded as {buf:#x?} over {range:?}",
|
||||
code = c as u32,
|
||||
ch = c,
|
||||
buf = &buf[off..(off + len)],
|
||||
range = off..(off + len),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_utf16() {
|
||||
for c in '\u{0}'..=core::char::MAX {
|
||||
assert_eq!(
|
||||
c.encode_utf16(&mut [0u16; 2]),
|
||||
Utf16Char::encode(c as u32).as_slice(),
|
||||
"failed for U+{:04X} ({:?})",
|
||||
c as u32,
|
||||
c
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ns_string() {
|
||||
macro_rules! test {
|
||||
($($s:expr,)+) => {$({
|
||||
let s1 = ns_string!($s);
|
||||
let s2 = NSString::from_str($s);
|
||||
|
||||
assert_eq!(s1, s1);
|
||||
assert_eq!(s1, &*s2);
|
||||
|
||||
assert_eq!(s1.to_string(), $s);
|
||||
assert_eq!(s2.to_string(), $s);
|
||||
})+};
|
||||
}
|
||||
|
||||
test! {
|
||||
"",
|
||||
"asdf",
|
||||
"🦀",
|
||||
"🏳️🌈",
|
||||
"𝄞music",
|
||||
"abcd【e】fg",
|
||||
"abcd⒠fg",
|
||||
"ääääh",
|
||||
"lööps, bröther?",
|
||||
"\u{fffd} \u{fffd} \u{fffd}",
|
||||
"讓每個人都能打造出。",
|
||||
"\0",
|
||||
"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09",
|
||||
// "\u{feff}", // TODO
|
||||
include_str!("__ns_string.rs"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ns_string_in_unsafe() {
|
||||
// Test that the `unused_unsafe` lint doesn't trigger
|
||||
let s = unsafe {
|
||||
let s: *const NSString = ns_string!("abc");
|
||||
&*s
|
||||
};
|
||||
assert_eq!(s.to_string(), "abc");
|
||||
}
|
||||
}
|
||||
506
third-party/vendor/objc2/src/foundation/array.rs
vendored
Normal file
506
third-party/vendor/objc2/src/foundation/array.rs
vendored
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Index, IndexMut, Range};
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{
|
||||
NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableArray, NSMutableCopying,
|
||||
NSObject, NSRange,
|
||||
};
|
||||
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
|
||||
use crate::runtime::{Class, Object};
|
||||
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
|
||||
|
||||
__inner_extern_class!(
|
||||
/// An immutable ordered collection of objects.
|
||||
///
|
||||
/// This is the Objective-C equivalent of a "boxed slice" (`Box<[T]>`),
|
||||
/// so effectively a `Vec<T>` where you can't change the number of
|
||||
/// elements.
|
||||
///
|
||||
/// The type of the contained objects is described by the generic
|
||||
/// parameter `T`, and the ownership of the objects is described with the
|
||||
/// generic parameter `O`.
|
||||
///
|
||||
///
|
||||
/// # Ownership
|
||||
///
|
||||
/// While `NSArray` _itself_ is immutable, i.e. the number of objects it
|
||||
/// contains can't change, it is still possible to modify the contained
|
||||
/// objects themselves, if you know you're the sole owner of them -
|
||||
/// quite similar to how you can modify elements in `Box<[T]>`.
|
||||
///
|
||||
/// To mutate the contained objects the ownership must be `O = Owned`. A
|
||||
/// summary of what the different "types" of arrays allow you to do can be
|
||||
/// found below. `Array` refers to either `NSArray` or `NSMutableArray`.
|
||||
/// - `Id<NSMutableArray<T, Owned>, Owned>`: Allows you to mutate the
|
||||
/// objects, and the array itself.
|
||||
/// - `Id<NSMutableArray<T, Shared>, Owned>`: Allows you to mutate the
|
||||
/// array itself, but not it's contents.
|
||||
/// - `Id<NSArray<T, Owned>, Owned>`: Allows you to mutate the objects,
|
||||
/// but not the array itself.
|
||||
/// - `Id<NSArray<T, Shared>, Owned>`: Effectively the same as the below.
|
||||
/// - `Id<Array<T, Shared>, Shared>`: Allows you to copy the array, but
|
||||
/// does not allow you to modify it in any way.
|
||||
/// - `Id<Array<T, Owned>, Shared>`: Pretty useless compared to the
|
||||
/// others, avoid this.
|
||||
///
|
||||
/// See [Apple's documentation][apple-doc].
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsarray?language=objc
|
||||
// `T: PartialEq` bound correct because `NSArray` does deep (instead of
|
||||
// shallow) equality comparisons.
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSArray<T: Message, O: Ownership = Shared> {
|
||||
item: PhantomData<Id<T, O>>,
|
||||
notunwindsafe: PhantomData<&'static mut ()>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> ClassType for NSArray<T, O> {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Same as Id<T, O> (which is what NSArray effectively stores).
|
||||
unsafe impl<T: Message + Sync + Send> Sync for NSArray<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync + Send> Send for NSArray<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync> Sync for NSArray<T, Owned> {}
|
||||
unsafe impl<T: Message + Send> Send for NSArray<T, Owned> {}
|
||||
|
||||
// Also same as Id<T, O>
|
||||
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSArray<T, O> {}
|
||||
impl<T: Message + RefUnwindSafe> UnwindSafe for NSArray<T, Shared> {}
|
||||
impl<T: Message + UnwindSafe> UnwindSafe for NSArray<T, Owned> {}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
|
||||
cls: &Class,
|
||||
objects: &[&T],
|
||||
) -> Id<R, O> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![cls, alloc],
|
||||
initWithObjects: objects.as_ptr(),
|
||||
count: objects.len(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
/// Generic creation methods.
|
||||
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
|
||||
/// Get an empty array.
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
// SAFETY:
|
||||
// - `new` may not create a new object, but instead return a shared
|
||||
// instance. We remedy this by returning `Id<Self, Shared>`.
|
||||
// - `O` don't actually matter here! E.g. `NSArray<T, Owned>` is
|
||||
// perfectly legal, since the array doesn't have any elements, and
|
||||
// hence the notion of ownership over the elements is void.
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
|
||||
// SAFETY:
|
||||
// `initWithObjects:` may choose to deduplicate arrays (I could
|
||||
// imagine it having a special case for arrays with one `NSNumber`
|
||||
// object), and returning mutable references to those would be
|
||||
// unsound!
|
||||
// However, when we know that we have ownership over the variables, we
|
||||
// also know that there cannot be another array in existence with the
|
||||
// same objects, so `Id<NSArray<T, Owned>, Owned>` is safe to return.
|
||||
//
|
||||
// In essence, we can choose between always returning `Id<T, Shared>`
|
||||
// or `Id<T, O>`, and the latter is probably the most useful, as we
|
||||
// would like to know when we're the only owner of the array, to
|
||||
// allow mutation of the array's items.
|
||||
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creation methods that produce shared arrays.
|
||||
unsafe impl<T: Message> NSArray<T, Shared> {
|
||||
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
|
||||
// SAFETY: Taking `&T` would not be sound, since the `&T` could come
|
||||
// from an `Id<T, Owned>` that would now no longer be owned!
|
||||
//
|
||||
// (Note that NSArray internally retains all the objects it is given,
|
||||
// effectively making the safety requirements the same as
|
||||
// `Id::retain`).
|
||||
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic accessor methods.
|
||||
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
|
||||
#[doc(alias = "count")]
|
||||
#[sel(count)]
|
||||
pub fn len(&self) -> usize;
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[sel(objectAtIndex:)]
|
||||
unsafe fn get_unchecked(&self, index: usize) -> &T;
|
||||
|
||||
#[doc(alias = "objectAtIndex:")]
|
||||
pub fn get(&self, index: usize) -> Option<&T> {
|
||||
// TODO: Replace this check with catching the thrown NSRangeException
|
||||
if index < self.len() {
|
||||
// SAFETY: The index is checked to be in bounds.
|
||||
Some(unsafe { self.get_unchecked(index) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "firstObject")]
|
||||
#[sel(firstObject)]
|
||||
pub fn first(&self) -> Option<&T>;
|
||||
|
||||
#[doc(alias = "lastObject")]
|
||||
#[sel(lastObject)]
|
||||
pub fn last(&self) -> Option<&T>;
|
||||
|
||||
#[doc(alias = "objectEnumerator")]
|
||||
pub fn iter(&self) -> NSEnumerator<'_, T> {
|
||||
unsafe {
|
||||
let result: *mut Object = msg_send![self, objectEnumerator];
|
||||
NSEnumerator::from_ptr(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(getObjects:range:)]
|
||||
unsafe fn get_objects(&self, ptr: *mut &T, range: NSRange);
|
||||
|
||||
pub fn objects_in_range(&self, range: Range<usize>) -> Vec<&T> {
|
||||
let range = NSRange::from(range);
|
||||
let mut vec = Vec::with_capacity(range.length);
|
||||
unsafe {
|
||||
self.get_objects(vec.as_mut_ptr(), range);
|
||||
vec.set_len(range.length);
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<&T> {
|
||||
self.objects_in_range(0..self.len())
|
||||
}
|
||||
|
||||
// TODO: Take Id<Self, Self::ItemOwnership> ?
|
||||
pub fn into_vec(array: Id<Self, Owned>) -> Vec<Id<T, O>> {
|
||||
array
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor methods that work on shared arrays.
|
||||
unsafe impl<T: Message> NSArray<T, Shared> {
|
||||
#[doc(alias = "objectAtIndex:")]
|
||||
pub fn get_retained(&self, index: usize) -> Id<T, Shared> {
|
||||
let obj = self.get(index).unwrap();
|
||||
// SAFETY: The object is originally shared (see `where` bound).
|
||||
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
|
||||
}
|
||||
|
||||
pub fn to_shared_vec(&self) -> Vec<Id<T, Shared>> {
|
||||
self.to_vec()
|
||||
.into_iter()
|
||||
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor methods that work on owned arrays.
|
||||
unsafe impl<T: Message> NSArray<T, Owned> {
|
||||
#[doc(alias = "objectAtIndex:")]
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
// TODO: Replace this check with catching the thrown NSRangeException
|
||||
if index < self.len() {
|
||||
// SAFETY: The index is checked to be in bounds.
|
||||
Some(unsafe { msg_send![self, objectAtIndex: index] })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "firstObject")]
|
||||
#[sel(firstObject)]
|
||||
pub fn first_mut(&mut self) -> Option<&mut T>;
|
||||
|
||||
#[doc(alias = "lastObject")]
|
||||
#[sel(lastObject)]
|
||||
pub fn last_mut(&mut self) -> Option<&mut T>;
|
||||
}
|
||||
);
|
||||
|
||||
/// This is implemented as a shallow copy.
|
||||
///
|
||||
/// As such, it is only possible when the array's contents are `Shared`.
|
||||
unsafe impl<T: Message> NSCopying for NSArray<T, Shared> {
|
||||
type Ownership = Shared;
|
||||
type Output = NSArray<T, Shared>;
|
||||
}
|
||||
|
||||
/// This is implemented as a shallow copy.
|
||||
unsafe impl<T: Message> NSMutableCopying for NSArray<T, Shared> {
|
||||
type Output = NSMutableArray<T, Shared>;
|
||||
}
|
||||
|
||||
impl<T: Message> alloc::borrow::ToOwned for NSArray<T, Shared> {
|
||||
type Owned = Id<NSArray<T, Shared>, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSArray<T, O> {
|
||||
type Item = T;
|
||||
}
|
||||
|
||||
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSArray<T, O> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = NSFastEnumerator<'a, NSArray<T, O>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_fast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> Index<usize> for NSArray<T, O> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &T {
|
||||
self.get(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message> IndexMut<usize> for NSArray<T, Owned> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut T {
|
||||
self.get_mut(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> DefaultId for NSArray<T, O> {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSArray<T, O> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.iter_fast()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::{NSNumber, NSString};
|
||||
use crate::rc::{RcTestObject, ThreadTestData};
|
||||
|
||||
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vec.push(NSObject::new());
|
||||
}
|
||||
NSArray::from_vec(vec)
|
||||
}
|
||||
|
||||
fn sample_number_array(len: u8) -> Id<NSArray<NSNumber, Shared>, Shared> {
|
||||
let mut vec = Vec::with_capacity(len as usize);
|
||||
for i in 0..len {
|
||||
vec.push(NSNumber::new_u8(i));
|
||||
}
|
||||
NSArray::from_vec(vec)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_empty() {
|
||||
let _empty_array1 = NSArray::<NSObject>::new();
|
||||
let _empty_array2 = NSArray::<NSObject>::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let empty_array = NSArray::<NSObject>::new();
|
||||
assert_eq!(empty_array.len(), 0);
|
||||
|
||||
let array = sample_array(4);
|
||||
assert_eq!(array.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let array1 = sample_array(3);
|
||||
let array2 = sample_array(3);
|
||||
assert_ne!(array1, array2);
|
||||
|
||||
let array1 = sample_number_array(3);
|
||||
let array2 = sample_number_array(3);
|
||||
assert_eq!(array1, array2);
|
||||
|
||||
let array1 = sample_number_array(3);
|
||||
let array2 = sample_number_array(4);
|
||||
assert_ne!(array1, array2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let obj = sample_number_array(0);
|
||||
assert_eq!(format!("{:?}", obj), "[]");
|
||||
let obj = sample_number_array(3);
|
||||
assert_eq!(format!("{:?}", obj), "[0, 1, 2]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let array = sample_array(4);
|
||||
assert_ne!(array.get(0), array.get(3));
|
||||
assert_eq!(array.first(), array.get(0));
|
||||
assert_eq!(array.last(), array.get(3));
|
||||
|
||||
let empty_array = <NSArray<NSObject>>::new();
|
||||
assert!(empty_array.first().is_none());
|
||||
assert!(empty_array.last().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retains_stored() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let input = [obj.clone(), obj.clone()];
|
||||
expected.retain += 2;
|
||||
expected.assert_current();
|
||||
|
||||
let array = NSArray::from_slice(&input);
|
||||
expected.retain += 2;
|
||||
expected.assert_current();
|
||||
|
||||
let _obj = array.first().unwrap();
|
||||
expected.assert_current();
|
||||
|
||||
drop(array);
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
|
||||
let array = NSArray::from_vec(Vec::from(input));
|
||||
expected.retain += 2;
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
|
||||
let _obj = array.get(0).unwrap();
|
||||
let _obj = array.get(1).unwrap();
|
||||
assert!(array.get(2).is_none());
|
||||
expected.assert_current();
|
||||
|
||||
drop(array);
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
|
||||
drop(obj);
|
||||
expected.release += 1;
|
||||
expected.dealloc += 1;
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nscopying_uses_retain() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let array = NSArray::from_slice(&[obj]);
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let _copy = array.copy();
|
||||
expected.assert_current();
|
||||
|
||||
let _copy = array.mutable_copy();
|
||||
expected.retain += 1;
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
feature = "apple",
|
||||
ignore = "this works differently on different framework versions"
|
||||
)]
|
||||
fn test_iter_no_retain() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let array = NSArray::from_slice(&[obj]);
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let iter = array.iter();
|
||||
expected.retain += 0;
|
||||
expected.assert_current();
|
||||
|
||||
assert_eq!(iter.count(), 1);
|
||||
expected.autorelease += 0;
|
||||
expected.assert_current();
|
||||
|
||||
let iter = array.iter_fast();
|
||||
assert_eq!(iter.count(), 1);
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let array = sample_array(4);
|
||||
|
||||
assert_eq!(array.iter().count(), 4);
|
||||
assert!(array
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, obj)| Some(obj) == array.get(i)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_objects_in_range() {
|
||||
let array = sample_array(4);
|
||||
|
||||
let middle_objs = array.objects_in_range(1..3);
|
||||
assert_eq!(middle_objs.len(), 2);
|
||||
assert_eq!(middle_objs[0], array.get(1).unwrap());
|
||||
assert_eq!(middle_objs[1], array.get(2).unwrap());
|
||||
|
||||
let empty_objs = array.objects_in_range(1..1);
|
||||
assert!(empty_objs.is_empty());
|
||||
|
||||
let all_objs = array.objects_in_range(0..4);
|
||||
assert_eq!(all_objs.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_vec() {
|
||||
let array = sample_array(4);
|
||||
|
||||
let vec = NSArray::into_vec(array);
|
||||
assert_eq!(vec.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_ownership_traits() {
|
||||
fn assert_partialeq<T: PartialEq>() {}
|
||||
|
||||
assert_partialeq::<NSArray<NSString, Shared>>();
|
||||
assert_partialeq::<NSArray<NSString, Owned>>();
|
||||
|
||||
fn test_ownership_implies_partialeq<O: Ownership>() {
|
||||
assert_partialeq::<NSArray<NSString, O>>();
|
||||
}
|
||||
|
||||
test_ownership_implies_partialeq::<Shared>();
|
||||
test_ownership_implies_partialeq::<Owned>();
|
||||
}
|
||||
}
|
||||
212
third-party/vendor/objc2/src/foundation/attributed_string.rs
vendored
Normal file
212
third-party/vendor/objc2/src/foundation/attributed_string.rs
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{
|
||||
NSCopying, NSDictionary, NSMutableAttributedString, NSMutableCopying, NSObject, NSString,
|
||||
};
|
||||
use crate::rc::{DefaultId, Id, Shared};
|
||||
use crate::runtime::Object;
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A string that has associated attributes for portions of its text.
|
||||
///
|
||||
/// Examples of attributes could be: Visual style, hyperlinks, or
|
||||
/// accessibility data.
|
||||
///
|
||||
/// Conceptually, each UTF-16 code unit in an attributed string has its
|
||||
/// own collection of attributes - most often though
|
||||
///
|
||||
/// Only the most basic functionality is defined here, the `AppKit`
|
||||
/// framework contains most of the extension methods.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsattributedstring?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSAttributedString;
|
||||
|
||||
unsafe impl ClassType for NSAttributedString {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: `NSAttributedString` is immutable and `NSMutableAttributedString`
|
||||
// can only be mutated from `&mut` methods.
|
||||
unsafe impl Sync for NSAttributedString {}
|
||||
unsafe impl Send for NSAttributedString {}
|
||||
|
||||
// Same reasoning as `NSString`.
|
||||
impl UnwindSafe for NSAttributedString {}
|
||||
impl RefUnwindSafe for NSAttributedString {}
|
||||
|
||||
/// Attributes that you can apply to text in an attributed string.
|
||||
pub type NSAttributedStringKey = NSString;
|
||||
|
||||
extern_methods!(
|
||||
/// Creating attributed strings.
|
||||
unsafe impl NSAttributedString {
|
||||
/// Construct an empty attributed string.
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// Creates a new attributed string from the given string and attributes.
|
||||
///
|
||||
/// The attributes are associated with every UTF-16 code unit in the
|
||||
/// string.
|
||||
#[doc(alias = "initWithString:")]
|
||||
pub fn new_with_attributes(
|
||||
string: &NSString,
|
||||
// TODO: Mutability of the dictionary should be (Shared, Shared)
|
||||
attributes: &NSDictionary<NSAttributedStringKey, Object>,
|
||||
) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithString: string, attributes: attributes]
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new attributed string without any attributes.
|
||||
#[doc(alias = "initWithString:")]
|
||||
pub fn from_nsstring(string: &NSString) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithString: string]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Querying.
|
||||
unsafe impl NSAttributedString {
|
||||
// TODO: Lifetimes?
|
||||
pub fn string(&self) -> Id<NSString, Shared> {
|
||||
unsafe { msg_send_id![self, string] }
|
||||
}
|
||||
|
||||
/// Alias for `self.string().len_utf16()`.
|
||||
#[doc(alias = "length")]
|
||||
#[sel(length)]
|
||||
pub fn len_utf16(&self) -> usize;
|
||||
|
||||
// /// TODO
|
||||
// ///
|
||||
// /// See [Apple's documentation on Effective and Maximal Ranges][doc].
|
||||
// ///
|
||||
// /// [doc]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/AccessingAttrs.html#//apple_ref/doc/uid/20000161-SW2
|
||||
// #[doc(alias = "attributesAtIndex:effectiveRange:")]
|
||||
// pub fn attributes_in_effective_range(
|
||||
// &self,
|
||||
// index: usize,
|
||||
// range: Range<usize>,
|
||||
// ) -> Id<Self, Shared> {
|
||||
// let range = NSRange::from(range);
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// attributesAtIndex:longestEffectiveRange:inRange:
|
||||
|
||||
// TODO: attributedSubstringFromRange:
|
||||
// TODO: enumerateAttributesInRange:options:usingBlock:
|
||||
}
|
||||
);
|
||||
|
||||
impl DefaultId for NSAttributedString {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSCopying for NSAttributedString {
|
||||
type Ownership = Shared;
|
||||
type Output = NSAttributedString;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSAttributedString {
|
||||
type Output = NSMutableAttributedString;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSAttributedString {
|
||||
type Owned = Id<NSAttributedString, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSAttributedString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Use -[NSAttributedString description] since it is pretty good
|
||||
let obj: &NSObject = self;
|
||||
fmt::Debug::fmt(obj, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
use alloc::{format, vec};
|
||||
|
||||
use super::*;
|
||||
use crate::rc::{autoreleasepool, Owned};
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let s = NSAttributedString::new();
|
||||
assert_eq!(&s.string().to_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_bound_to_attributed() {
|
||||
let attr_s = {
|
||||
let source = NSString::from_str("Hello world!");
|
||||
NSAttributedString::from_nsstring(&source)
|
||||
};
|
||||
let s = autoreleasepool(|_| attr_s.string());
|
||||
assert_eq!(s.len(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_nsstring() {
|
||||
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
|
||||
assert_eq!(&s.string().to_string(), "abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy() {
|
||||
let s1 = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
|
||||
let s2 = s1.copy();
|
||||
// NSAttributedString performs this optimization in GNUStep's runtime,
|
||||
// but not in Apple's; so we don't test for it!
|
||||
// assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
|
||||
assert!(s2.is_kind_of::<NSAttributedString>());
|
||||
|
||||
let s3 = s1.mutable_copy();
|
||||
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
|
||||
assert!(s3.is_kind_of::<NSMutableAttributedString>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
|
||||
let expected = if cfg!(feature = "gnustep-1-7") {
|
||||
"abc{}"
|
||||
} else {
|
||||
"abc{\n}"
|
||||
};
|
||||
assert_eq!(format!("{:?}", s), expected);
|
||||
|
||||
let obj: Id<Object, Owned> = unsafe { Id::cast(NSObject::new()) };
|
||||
let ptr: *const Object = &*obj;
|
||||
let s = NSAttributedString::new_with_attributes(
|
||||
&NSString::from_str("abc"),
|
||||
&NSDictionary::from_keys_and_objects(&[&*NSString::from_str("test")], vec![obj]),
|
||||
);
|
||||
let expected = if cfg!(feature = "gnustep-1-7") {
|
||||
format!("abc{{test = \"<NSObject: {:?}>\"; }}", ptr)
|
||||
} else {
|
||||
format!("abc{{\n test = \"<NSObject: {:?}>\";\n}}", ptr)
|
||||
};
|
||||
assert_eq!(format!("{:?}", s), expected);
|
||||
}
|
||||
}
|
||||
73
third-party/vendor/objc2/src/foundation/bundle.rs
vendored
Normal file
73
third-party/vendor/objc2/src/foundation/bundle.rs
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSCopying, NSDictionary, NSObject, NSString};
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ns_string, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A representation of the code and resources stored in a bundle
|
||||
/// directory on disk.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsbundle?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSBundle;
|
||||
|
||||
unsafe impl ClassType for NSBundle {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Bundles are documented as thread-safe.
|
||||
unsafe impl Sync for NSBundle {}
|
||||
unsafe impl Send for NSBundle {}
|
||||
|
||||
impl UnwindSafe for NSBundle {}
|
||||
impl RefUnwindSafe for NSBundle {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSBundle {
|
||||
pub fn main() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), mainBundle] }
|
||||
}
|
||||
|
||||
pub fn info(&self) -> Id<NSDictionary<NSString, NSObject>, Shared> {
|
||||
unsafe { msg_send_id![self, infoDictionary] }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<Id<NSString, Shared>> {
|
||||
self.info().get(ns_string!("CFBundleName")).map(|name| {
|
||||
let ptr: *const NSObject = name;
|
||||
let ptr: *const NSString = ptr.cast();
|
||||
// SAFETY: TODO
|
||||
let name = unsafe { ptr.as_ref().unwrap_unchecked() };
|
||||
name.copy()
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl fmt::Debug for NSBundle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Delegate to NSObject
|
||||
(**self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
use std::println;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(target_os = "macos"), ignore = "varies between platforms")]
|
||||
fn try_running_functions() {
|
||||
// This is mostly empty since cargo doesn't bundle the application
|
||||
// before executing.
|
||||
let bundle = NSBundle::main();
|
||||
println!("{:?}", bundle);
|
||||
assert_eq!(format!("{:?}", bundle.info()), "{}");
|
||||
assert_eq!(bundle.name(), None);
|
||||
}
|
||||
}
|
||||
54
third-party/vendor/objc2/src/foundation/comparison_result.rs
vendored
Normal file
54
third-party/vendor/objc2/src/foundation/comparison_result.rs
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use core::cmp::Ordering;
|
||||
|
||||
use crate::{Encode, Encoding, RefEncode};
|
||||
|
||||
/// Constants that indicate sort order.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nscomparisonresult?language=objc).
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum NSComparisonResult {
|
||||
/// The left operand is smaller than the right operand.
|
||||
Ascending = -1,
|
||||
/// The two operands are equal.
|
||||
Same = 0,
|
||||
/// The left operand is greater than the right operand.
|
||||
Descending = 1,
|
||||
}
|
||||
|
||||
impl Default for NSComparisonResult {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::Same
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSComparisonResult {
|
||||
const ENCODING: Encoding = isize::ENCODING;
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for NSComparisonResult {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
impl From<Ordering> for NSComparisonResult {
|
||||
#[inline]
|
||||
fn from(order: Ordering) -> Self {
|
||||
match order {
|
||||
Ordering::Less => Self::Ascending,
|
||||
Ordering::Equal => Self::Same,
|
||||
Ordering::Greater => Self::Descending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSComparisonResult> for Ordering {
|
||||
#[inline]
|
||||
fn from(comparison_result: NSComparisonResult) -> Self {
|
||||
match comparison_result {
|
||||
NSComparisonResult::Ascending => Self::Less,
|
||||
NSComparisonResult::Same => Self::Equal,
|
||||
NSComparisonResult::Descending => Self::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
48
third-party/vendor/objc2/src/foundation/copying.rs
vendored
Normal file
48
third-party/vendor/objc2/src/foundation/copying.rs
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use crate::rc::{Id, Owned, Ownership};
|
||||
use crate::{msg_send_id, Message};
|
||||
|
||||
pub unsafe trait NSCopying: Message {
|
||||
/// Indicates whether the type is mutable or immutable.
|
||||
///
|
||||
/// This can be [`Owned`] if and only if `copy` creates a new instance,
|
||||
/// see the following example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x: Id<MyObject, _> = MyObject::new();
|
||||
/// // This is valid only if `y` is a new instance. Otherwise `x` and `y`
|
||||
/// // would be able to create aliasing mutable references!
|
||||
/// let y: Id<MyObject, Owned> = x.copy();
|
||||
/// ```
|
||||
///
|
||||
/// Note that for the same reason, you should be careful when defining
|
||||
/// `new` methods on your object; e.g. immutable types like [`NSString`]
|
||||
/// don't return `Id<NSString, Owned>`, because that would allow this
|
||||
/// trait to create an aliasing `Id<NSString, Shared>` (since sending the
|
||||
/// `copy` message (and others) does not create a new instance, but
|
||||
/// instead just retains the instance).
|
||||
///
|
||||
/// [`NSString`]: crate::foundation::NSString
|
||||
type Ownership: Ownership;
|
||||
|
||||
/// The output type.
|
||||
///
|
||||
/// This is usually `Self`, but e.g. `NSMutableString` returns `NSString`.
|
||||
/// TODO: Verify???
|
||||
type Output: Message;
|
||||
|
||||
fn copy(&self) -> Id<Self::Output, Self::Ownership> {
|
||||
unsafe { msg_send_id![self, copy] }
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
///
|
||||
/// Note that the `mutableCopy` selector must return an owned object!
|
||||
pub unsafe trait NSMutableCopying: Message {
|
||||
/// TODO
|
||||
type Output: Message;
|
||||
|
||||
fn mutable_copy(&self) -> Id<Self::Output, Owned> {
|
||||
unsafe { msg_send_id![self, mutableCopy] }
|
||||
}
|
||||
}
|
||||
228
third-party/vendor/objc2/src/foundation/data.rs
vendored
Normal file
228
third-party/vendor/objc2/src/foundation/data.rs
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#[cfg(feature = "block")]
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::ops::Index;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use core::slice::{self, SliceIndex};
|
||||
|
||||
use super::{NSCopying, NSMutableCopying, NSMutableData, NSObject};
|
||||
use crate::rc::{DefaultId, Id, Shared};
|
||||
use crate::runtime::{Class, Object};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A static byte buffer in memory.
|
||||
///
|
||||
/// This is similar to a [`slice`][`prim@slice`] of [`u8`].
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsdata?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSData;
|
||||
|
||||
unsafe impl ClassType for NSData {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: `NSData` is immutable and `NSMutableData` can only be mutated from
|
||||
// `&mut` methods.
|
||||
unsafe impl Sync for NSData {}
|
||||
unsafe impl Send for NSData {}
|
||||
|
||||
impl UnwindSafe for NSData {}
|
||||
impl RefUnwindSafe for NSData {}
|
||||
|
||||
extern_methods!(
|
||||
/// Creation methods.
|
||||
unsafe impl NSData {
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Shared> {
|
||||
unsafe { Id::cast(with_slice(Self::class(), bytes)) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "block")]
|
||||
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Shared> {
|
||||
// GNUStep's NSData `initWithBytesNoCopy:length:deallocator:` has a
|
||||
// bug; it forgets to assign the input buffer and length to the
|
||||
// instance before it swizzles to NSDataWithDeallocatorBlock.
|
||||
// See https://github.com/gnustep/libs-base/pull/213
|
||||
// So we just use NSDataWithDeallocatorBlock directly.
|
||||
//
|
||||
// NSMutableData does not have this problem.
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
let cls = crate::class!(NSDataWithDeallocatorBlock);
|
||||
#[cfg(not(feature = "gnustep-1-7"))]
|
||||
let cls = Self::class();
|
||||
|
||||
unsafe { Id::cast(with_vec(cls, bytes)) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor methods.
|
||||
unsafe impl NSData {
|
||||
#[sel(length)]
|
||||
#[doc(alias = "length")]
|
||||
pub fn len(&self) -> usize;
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[sel(bytes)]
|
||||
fn bytes_raw(&self) -> *const c_void;
|
||||
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
let ptr = self.bytes_raw();
|
||||
let ptr: *const u8 = ptr.cast();
|
||||
// The bytes pointer may be null for length zero
|
||||
if ptr.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(ptr, self.len()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSData {
|
||||
type Ownership = Shared;
|
||||
type Output = NSData;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSData {
|
||||
type Output = NSMutableData;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSData {
|
||||
type Owned = Id<NSData, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for NSData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't implement `Borrow<[u8]>` since we can't guarantee that `Eq`,
|
||||
// `Ord` and `Hash` are equal for `NSData` vs. `[u8]`!
|
||||
|
||||
impl<I: SliceIndex<[u8]>> Index<I> for NSData {
|
||||
type Output = I::Output;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: I) -> &Self::Output {
|
||||
// Replaces the need for getBytes:range:
|
||||
Index::index(self.bytes(), index)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSData {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSData {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// -[NSData description] is quite unreadable
|
||||
fmt::Debug::fmt(self.bytes(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a NSData {
|
||||
type Item = &'a u8;
|
||||
type IntoIter = core::slice::Iter<'a, u8>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.bytes().iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn with_slice(cls: &Class, bytes: &[u8]) -> Id<Object, Shared> {
|
||||
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![cls, alloc],
|
||||
initWithBytes: bytes_ptr,
|
||||
length: bytes.len(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "block")]
|
||||
pub(crate) unsafe fn with_vec(cls: &Class, bytes: Vec<u8>) -> Id<Object, Shared> {
|
||||
use core::mem::ManuallyDrop;
|
||||
|
||||
use block2::{Block, ConcreteBlock};
|
||||
|
||||
let capacity = bytes.capacity();
|
||||
|
||||
let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe {
|
||||
// Recreate the Vec and let it drop
|
||||
let _ = Vec::<u8>::from_raw_parts(bytes.cast(), len, capacity);
|
||||
});
|
||||
let dealloc = dealloc.copy();
|
||||
let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc;
|
||||
|
||||
let mut bytes = ManuallyDrop::new(bytes);
|
||||
let bytes_ptr: *mut c_void = bytes.as_mut_ptr().cast();
|
||||
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![cls, alloc],
|
||||
initWithBytesNoCopy: bytes_ptr,
|
||||
length: bytes.len(),
|
||||
deallocator: dealloc,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
#[cfg(feature = "block")]
|
||||
use alloc::vec;
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
let bytes = [3, 7, 16, 52, 112, 19];
|
||||
let data = NSData::with_bytes(&bytes);
|
||||
assert_eq!(data.len(), bytes.len());
|
||||
assert_eq!(data.bytes(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_bytes() {
|
||||
let data = NSData::new();
|
||||
assert!(Some(data.bytes()).is_some());
|
||||
}
|
||||
|
||||
#[cfg(feature = "block")]
|
||||
#[test]
|
||||
fn test_from_vec() {
|
||||
let bytes = vec![3, 7, 16];
|
||||
let bytes_ptr = bytes.as_ptr();
|
||||
|
||||
let data = NSData::from_vec(bytes);
|
||||
assert_eq!(data.bytes().as_ptr(), bytes_ptr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let bytes = [3, 7, 16, 52, 112, 19];
|
||||
let data = NSData::with_bytes(&bytes);
|
||||
assert_eq!(format!("{:?}", data), "[3, 7, 16, 52, 112, 19]");
|
||||
}
|
||||
}
|
||||
264
third-party/vendor/objc2/src/foundation/dictionary.rs
vendored
Normal file
264
third-party/vendor/objc2/src/foundation/dictionary.rs
vendored
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::cmp::min;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Index;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use core::ptr;
|
||||
|
||||
use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject};
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared, SliceId};
|
||||
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send, msg_send_id, Message};
|
||||
|
||||
__inner_extern_class!(
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSDictionary<K: Message, V: Message> {
|
||||
key: PhantomData<Id<K, Shared>>,
|
||||
obj: PhantomData<Id<V, Owned>>,
|
||||
}
|
||||
|
||||
unsafe impl<K: Message, V: Message> ClassType for NSDictionary<K, V> {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: SAFETY
|
||||
// Approximately same as `NSArray<T, Shared>`
|
||||
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSDictionary<K, V> {}
|
||||
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSDictionary<K, V> {}
|
||||
|
||||
// Approximately same as `NSArray<T, Shared>`
|
||||
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSDictionary<K, V> {}
|
||||
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe for NSDictionary<K, V> {}
|
||||
extern_methods!(
|
||||
unsafe impl<K: Message, V: Message> NSDictionary<K, V> {
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
#[doc(alias = "count")]
|
||||
#[sel(count)]
|
||||
pub fn len(&self) -> usize;
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[doc(alias = "objectForKey:")]
|
||||
#[sel(objectForKey:)]
|
||||
pub fn get(&self, key: &K) -> Option<&V>;
|
||||
|
||||
#[sel(getObjects:andKeys:)]
|
||||
unsafe fn get_objects_and_keys(&self, objects: *mut &V, keys: *mut &K);
|
||||
|
||||
#[doc(alias = "getObjects:andKeys:")]
|
||||
pub fn keys(&self) -> Vec<&K> {
|
||||
let len = self.len();
|
||||
let mut keys = Vec::with_capacity(len);
|
||||
unsafe {
|
||||
self.get_objects_and_keys(ptr::null_mut(), keys.as_mut_ptr());
|
||||
keys.set_len(len);
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
#[doc(alias = "getObjects:andKeys:")]
|
||||
pub fn values(&self) -> Vec<&V> {
|
||||
let len = self.len();
|
||||
let mut vals = Vec::with_capacity(len);
|
||||
unsafe {
|
||||
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
|
||||
vals.set_len(len);
|
||||
}
|
||||
vals
|
||||
}
|
||||
|
||||
#[doc(alias = "getObjects:andKeys:")]
|
||||
pub fn keys_and_objects(&self) -> (Vec<&K>, Vec<&V>) {
|
||||
let len = self.len();
|
||||
let mut keys = Vec::with_capacity(len);
|
||||
let mut objs = Vec::with_capacity(len);
|
||||
unsafe {
|
||||
self.get_objects_and_keys(objs.as_mut_ptr(), keys.as_mut_ptr());
|
||||
keys.set_len(len);
|
||||
objs.set_len(len);
|
||||
}
|
||||
(keys, objs)
|
||||
}
|
||||
|
||||
#[doc(alias = "keyEnumerator")]
|
||||
pub fn iter_keys(&self) -> NSEnumerator<'_, K> {
|
||||
unsafe {
|
||||
let result = msg_send![self, keyEnumerator];
|
||||
NSEnumerator::from_ptr(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "objectEnumerator")]
|
||||
pub fn iter_values(&self) -> NSEnumerator<'_, V> {
|
||||
unsafe {
|
||||
let result = msg_send![self, objectEnumerator];
|
||||
NSEnumerator::from_ptr(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys_array(&self) -> Id<NSArray<K, Shared>, Shared> {
|
||||
unsafe { msg_send_id![self, allKeys] }
|
||||
}
|
||||
|
||||
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Shared>
|
||||
where
|
||||
T: NSCopying<Output = K>,
|
||||
{
|
||||
let vals = vals.as_slice_ref();
|
||||
|
||||
let cls = Self::class();
|
||||
let count = min(keys.len(), vals.len());
|
||||
let obj = unsafe { msg_send_id![cls, alloc] };
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
obj,
|
||||
initWithObjects: vals.as_ptr(),
|
||||
forKeys: keys.as_ptr(),
|
||||
count: count,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
|
||||
unsafe { msg_send_id![&dict, allValues] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl<K: Message, V: Message> DefaultId for NSDictionary<K, V> {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSDictionary<K, V> {
|
||||
type Item = K;
|
||||
}
|
||||
|
||||
impl<'a, K: Message, V: Message> Index<&'a K> for NSDictionary<K, V> {
|
||||
type Output = V;
|
||||
|
||||
fn index<'s>(&'s self, index: &'a K) -> &'s V {
|
||||
self.get(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSDictionary<K, V> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let iter = self.iter_keys().zip(self.iter_values());
|
||||
f.debug_map().entries(iter).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::NSString;
|
||||
use crate::rc::autoreleasepool;
|
||||
|
||||
fn sample_dict(key: &str) -> Id<NSDictionary<NSString, NSObject>, Shared> {
|
||||
let string = NSString::from_str(key);
|
||||
let obj = NSObject::new();
|
||||
NSDictionary::from_keys_and_objects(&[&*string], vec![obj])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let dict = sample_dict("abcd");
|
||||
assert_eq!(dict.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let dict = sample_dict("abcd");
|
||||
|
||||
let string = NSString::from_str("abcd");
|
||||
assert!(dict.get(&string).is_some());
|
||||
|
||||
let string = NSString::from_str("abcde");
|
||||
assert!(dict.get(&string).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keys() {
|
||||
let dict = sample_dict("abcd");
|
||||
let keys = dict.keys();
|
||||
|
||||
assert_eq!(keys.len(), 1);
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(keys[0].as_str(pool), "abcd");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values() {
|
||||
let dict = sample_dict("abcd");
|
||||
let vals = dict.values();
|
||||
|
||||
assert_eq!(vals.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keys_and_objects() {
|
||||
let dict = sample_dict("abcd");
|
||||
let (keys, objs) = dict.keys_and_objects();
|
||||
|
||||
assert_eq!(keys.len(), 1);
|
||||
assert_eq!(objs.len(), 1);
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(keys[0].as_str(pool), "abcd");
|
||||
});
|
||||
assert_eq!(objs[0], dict.get(keys[0]).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_keys() {
|
||||
let dict = sample_dict("abcd");
|
||||
assert_eq!(dict.iter_keys().count(), 1);
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(dict.iter_keys().next().unwrap().as_str(pool), "abcd");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_values() {
|
||||
let dict = sample_dict("abcd");
|
||||
assert_eq!(dict.iter_values().count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arrays() {
|
||||
let dict = sample_dict("abcd");
|
||||
|
||||
let keys = dict.keys_array();
|
||||
assert_eq!(keys.len(), 1);
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(keys[0].as_str(pool), "abcd");
|
||||
});
|
||||
|
||||
// let objs = NSDictionary::into_values_array(dict);
|
||||
// assert_eq!(objs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let key = NSString::from_str("a");
|
||||
// TODO: Fix this
|
||||
let val = unsafe { Id::from_shared(NSString::from_str("b")) };
|
||||
let dict = NSDictionary::from_keys_and_objects(&[&*key], vec![val]);
|
||||
assert_eq!(format!("{:?}", dict), r#"{"a": "b"}"#);
|
||||
}
|
||||
}
|
||||
196
third-party/vendor/objc2/src/foundation/enumerator.rs
vendored
Normal file
196
third-party/vendor/objc2/src/foundation/enumerator.rs
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::ptr;
|
||||
use core::slice;
|
||||
use std::os::raw::c_ulong;
|
||||
|
||||
use crate::rc::{Id, Owned};
|
||||
use crate::runtime::Object;
|
||||
use crate::{msg_send, Encode, Encoding, Message, RefEncode};
|
||||
|
||||
// TODO: https://doc.rust-lang.org/stable/reference/trait-bounds.html#lifetime-bounds
|
||||
pub struct NSEnumerator<'a, T: Message> {
|
||||
id: Id<Object, Owned>,
|
||||
item: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Message> NSEnumerator<'a, T> {
|
||||
/// TODO
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The object pointer must be a valid `NSEnumerator` with `Owned`
|
||||
/// ownership.
|
||||
pub unsafe fn from_ptr(ptr: *mut Object) -> Self {
|
||||
Self {
|
||||
id: unsafe { Id::retain_autoreleased(ptr) }.unwrap(),
|
||||
item: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Message> Iterator for NSEnumerator<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<&'a T> {
|
||||
unsafe { msg_send![&mut self.id, nextObject] }
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait NSFastEnumeration: Message {
|
||||
type Item: Message;
|
||||
|
||||
fn iter_fast(&self) -> NSFastEnumerator<'_, Self> {
|
||||
NSFastEnumerator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct NSFastEnumerationState<T: Message> {
|
||||
state: c_ulong, // TODO: Verify this is actually always 64 bit
|
||||
items_ptr: *const *const T,
|
||||
mutations_ptr: *mut c_ulong,
|
||||
extra: [c_ulong; 5],
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> Encode for NSFastEnumerationState<T> {
|
||||
const ENCODING: Encoding = Encoding::Struct(
|
||||
"?",
|
||||
&[
|
||||
Encoding::C_ULONG,
|
||||
Encoding::Pointer(&Encoding::Object), // <*const *const T>::ENCODING
|
||||
Encoding::Pointer(&Encoding::C_ULONG),
|
||||
Encoding::Array(5, &Encoding::C_ULONG),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> RefEncode for NSFastEnumerationState<T> {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
fn enumerate<'a, 'b: 'a, C: NSFastEnumeration + ?Sized>(
|
||||
object: &'b C,
|
||||
state: &mut NSFastEnumerationState<C::Item>,
|
||||
buf: &'a mut [*const C::Item],
|
||||
) -> Option<&'a [*const C::Item]> {
|
||||
let count: usize = unsafe {
|
||||
// Reborrow state so that we don't move it
|
||||
let state = &mut *state;
|
||||
msg_send![
|
||||
object,
|
||||
countByEnumeratingWithState: state,
|
||||
objects: buf.as_mut_ptr(),
|
||||
count: buf.len(),
|
||||
]
|
||||
};
|
||||
|
||||
if count > 0 {
|
||||
unsafe { Some(slice::from_raw_parts(state.items_ptr, count)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const FAST_ENUM_BUF_SIZE: usize = 16;
|
||||
|
||||
pub struct NSFastEnumerator<'a, C: 'a + NSFastEnumeration + ?Sized> {
|
||||
object: &'a C,
|
||||
|
||||
ptr: *const *const C::Item,
|
||||
end: *const *const C::Item,
|
||||
|
||||
state: NSFastEnumerationState<C::Item>,
|
||||
buf: [*const C::Item; FAST_ENUM_BUF_SIZE],
|
||||
}
|
||||
|
||||
impl<'a, C: NSFastEnumeration + ?Sized> NSFastEnumerator<'a, C> {
|
||||
fn new(object: &'a C) -> Self {
|
||||
Self {
|
||||
object,
|
||||
|
||||
ptr: ptr::null(),
|
||||
end: ptr::null(),
|
||||
|
||||
state: unsafe { mem::zeroed() },
|
||||
buf: [ptr::null(); FAST_ENUM_BUF_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
fn update_buf(&mut self) -> bool {
|
||||
// If this isn't our first time enumerating, record the previous value
|
||||
// from the mutations pointer.
|
||||
let mutations = if !self.ptr.is_null() {
|
||||
Some(unsafe { *self.state.mutations_ptr })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let next_buf = enumerate(self.object, &mut self.state, &mut self.buf);
|
||||
if let Some(buf) = next_buf {
|
||||
// Check if the collection was mutated
|
||||
if let Some(mutations) = mutations {
|
||||
assert_eq!(
|
||||
mutations,
|
||||
unsafe { *self.state.mutations_ptr },
|
||||
"Mutation detected during enumeration of object {:p}",
|
||||
self.object
|
||||
);
|
||||
}
|
||||
|
||||
self.ptr = buf.as_ptr();
|
||||
self.end = unsafe { self.ptr.add(buf.len()) };
|
||||
true
|
||||
} else {
|
||||
self.ptr = ptr::null();
|
||||
self.end = ptr::null();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: NSFastEnumeration + ?Sized> Iterator for NSFastEnumerator<'a, C> {
|
||||
type Item = &'a C::Item;
|
||||
|
||||
fn next(&mut self) -> Option<&'a C::Item> {
|
||||
if self.ptr == self.end && !self.update_buf() {
|
||||
None
|
||||
} else {
|
||||
unsafe {
|
||||
let obj = *self.ptr;
|
||||
self.ptr = self.ptr.offset(1);
|
||||
Some(obj.as_ref().unwrap_unchecked())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NSFastEnumeration;
|
||||
use crate::foundation::{NSArray, NSNumber};
|
||||
|
||||
#[test]
|
||||
fn test_enumerator() {
|
||||
let vec = (0..4).map(NSNumber::new_usize).collect();
|
||||
let array = NSArray::from_vec(vec);
|
||||
|
||||
let enumerator = array.iter();
|
||||
assert_eq!(enumerator.count(), 4);
|
||||
|
||||
let enumerator = array.iter();
|
||||
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fast_enumerator() {
|
||||
let vec = (0..4).map(NSNumber::new_usize).collect();
|
||||
let array = NSArray::from_vec(vec);
|
||||
|
||||
let enumerator = array.iter_fast();
|
||||
assert_eq!(enumerator.count(), 4);
|
||||
|
||||
let enumerator = array.iter_fast();
|
||||
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
|
||||
}
|
||||
}
|
||||
165
third-party/vendor/objc2/src/foundation/error.rs
vendored
Normal file
165
third-party/vendor/objc2/src/foundation/error.rs
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSCopying, NSDictionary, NSObject, NSString};
|
||||
use crate::ffi::NSInteger;
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// Information about an error condition including a domain, a
|
||||
/// domain-specific error code, and application-specific information.
|
||||
///
|
||||
/// See also Apple's [documentation on error handling][err], and their
|
||||
/// NSError [API reference][api].
|
||||
///
|
||||
/// [err]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html#//apple_ref/doc/uid/TP40001806-CH201-SW1
|
||||
/// [api]: https://developer.apple.com/documentation/foundation/nserror?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSError;
|
||||
|
||||
unsafe impl ClassType for NSError {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Error objects are immutable data containers.
|
||||
unsafe impl Sync for NSError {}
|
||||
unsafe impl Send for NSError {}
|
||||
|
||||
impl UnwindSafe for NSError {}
|
||||
impl RefUnwindSafe for NSError {}
|
||||
|
||||
pub type NSErrorUserInfoKey = NSString;
|
||||
pub type NSErrorDomain = NSString;
|
||||
|
||||
extern_methods!(
|
||||
/// Creation methods.
|
||||
unsafe impl NSError {
|
||||
/// Construct a new [`NSError`] with the given code in the given domain.
|
||||
pub fn new(code: NSInteger, domain: &NSString) -> Id<Self, Shared> {
|
||||
unsafe { Self::with_user_info(code, domain, None) }
|
||||
}
|
||||
|
||||
// TODO: Figure out safety of `user_info` dict!
|
||||
unsafe fn with_user_info(
|
||||
code: NSInteger,
|
||||
domain: &NSString,
|
||||
user_info: Option<&NSDictionary<NSErrorUserInfoKey, NSObject>>,
|
||||
) -> Id<Self, Shared> {
|
||||
// SAFETY: `domain` and `user_info` are copied to the error object, so
|
||||
// even if the `&NSString` came from a `&mut NSMutableString`, we're
|
||||
// still good!
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![Self::class(), alloc],
|
||||
initWithDomain: domain,
|
||||
code: code,
|
||||
userInfo: user_info,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor methods.
|
||||
unsafe impl NSError {
|
||||
pub fn domain(&self) -> Id<NSString, Shared> {
|
||||
unsafe { msg_send_id![self, domain] }
|
||||
}
|
||||
|
||||
#[sel(code)]
|
||||
pub fn code(&self) -> NSInteger;
|
||||
|
||||
pub fn user_info(&self) -> Option<Id<NSDictionary<NSErrorUserInfoKey, NSObject>, Shared>> {
|
||||
unsafe { msg_send_id![self, userInfo] }
|
||||
}
|
||||
|
||||
pub fn localized_description(&self) -> Id<NSString, Shared> {
|
||||
// TODO: For some reason this leaks a lot?
|
||||
let obj: Option<_> = unsafe { msg_send_id![self, localizedDescription] };
|
||||
obj.expect(
|
||||
"unexpected NULL localized description; a default should have been generated!",
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: localizedRecoveryOptions
|
||||
// TODO: localizedRecoverySuggestion
|
||||
// TODO: localizedFailureReason
|
||||
// TODO: helpAnchor
|
||||
// TODO: +setUserInfoValueProviderForDomain:provider:
|
||||
// TODO: +userInfoValueProviderForDomain:
|
||||
|
||||
// TODO: recoveryAttempter
|
||||
// TODO: attemptRecoveryFromError:...
|
||||
|
||||
// TODO: Figure out if this is a good design, or if we should do something
|
||||
// differently (like a Rusty name for the function, or putting a bunch of
|
||||
// statics in a module instead)?
|
||||
#[allow(non_snake_case)]
|
||||
pub fn NSLocalizedDescriptionKey() -> &'static NSErrorUserInfoKey {
|
||||
extern "C" {
|
||||
#[link_name = "NSLocalizedDescriptionKey"]
|
||||
static VALUE: &'static NSErrorUserInfoKey;
|
||||
}
|
||||
unsafe { VALUE }
|
||||
}
|
||||
|
||||
// TODO: Other NSErrorUserInfoKey values
|
||||
// TODO: NSErrorDomain values
|
||||
}
|
||||
);
|
||||
|
||||
impl fmt::Debug for NSError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NSError")
|
||||
.field("domain", &self.domain())
|
||||
.field("code", &self.code())
|
||||
.field("user_info", &self.user_info())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NSError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.localized_description())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NSError {}
|
||||
|
||||
unsafe impl NSCopying for NSError {
|
||||
type Ownership = Shared;
|
||||
type Output = Self;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
|
||||
use crate::ns_string;
|
||||
|
||||
#[test]
|
||||
fn custom_domain() {
|
||||
let error = NSError::new(42, ns_string!("MyDomain"));
|
||||
assert_eq!(error.code(), 42);
|
||||
assert_eq!(&*error.domain(), ns_string!("MyDomain"));
|
||||
let expected = if cfg!(feature = "apple") {
|
||||
"The operation couldn’t be completed. (MyDomain error 42.)"
|
||||
} else {
|
||||
"MyDomain 42"
|
||||
};
|
||||
assert_eq!(format!("{}", error), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let error = NSError::new(-999, ns_string!("NSURLErrorDomain"));
|
||||
let expected = if cfg!(feature = "apple") {
|
||||
"The operation couldn’t be completed. (NSURLErrorDomain error -999.)"
|
||||
} else {
|
||||
"NSURLErrorDomain -999"
|
||||
};
|
||||
assert_eq!(format!("{}", error), expected);
|
||||
}
|
||||
}
|
||||
200
third-party/vendor/objc2/src/foundation/exception.rs
vendored
Normal file
200
third-party/vendor/objc2/src/foundation/exception.rs
vendored
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
use core::fmt;
|
||||
use core::hint::unreachable_unchecked;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSCopying, NSDictionary, NSObject, NSString};
|
||||
use crate::exception::Exception;
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::runtime::Object;
|
||||
use crate::{extern_class, extern_methods, msg_send_id, sel, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A special condition that interrupts the normal flow of program
|
||||
/// execution.
|
||||
///
|
||||
/// Exceptions can be thrown and caught using the `objc2::exception`
|
||||
/// module.
|
||||
///
|
||||
/// See also [Apple's documentation][doc].
|
||||
///
|
||||
/// [doc]: https://developer.apple.com/documentation/foundation/nsexception?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSException;
|
||||
|
||||
unsafe impl ClassType for NSException {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Exception objects are immutable data containers, and documented as
|
||||
// thread safe.
|
||||
unsafe impl Sync for NSException {}
|
||||
unsafe impl Send for NSException {}
|
||||
|
||||
impl UnwindSafe for NSException {}
|
||||
impl RefUnwindSafe for NSException {}
|
||||
|
||||
type NSExceptionName = NSString;
|
||||
extern_methods!(
|
||||
unsafe impl NSException {
|
||||
/// Create a new [`NSException`] object.
|
||||
///
|
||||
/// Returns `None` if the exception couldn't be created (example: If the
|
||||
/// process is out of memory).
|
||||
pub fn new(
|
||||
name: &NSExceptionName,
|
||||
reason: Option<&NSString>,
|
||||
user_info: Option<&NSDictionary<Object, Object>>,
|
||||
) -> Option<Id<Self, Shared>> {
|
||||
let obj = unsafe { msg_send_id![Self::class(), alloc] };
|
||||
unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] }
|
||||
}
|
||||
|
||||
#[sel(raise)]
|
||||
unsafe fn raise_raw(&self);
|
||||
|
||||
/// Raises the exception, causing program flow to jump to the local
|
||||
/// exception handler.
|
||||
///
|
||||
/// This is equivalent to using `objc2::exception::throw`.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Same as `objc2::exception::throw`.
|
||||
pub unsafe fn raise(&self) -> ! {
|
||||
// SAFETY: We only create `Shared` NSExceptions, so it is safe to give
|
||||
// to the place where `@catch` receives it.
|
||||
unsafe { self.raise_raw() };
|
||||
// SAFETY: `raise` will throw an exception, or abort if something
|
||||
// unexpected happened.
|
||||
unsafe { unreachable_unchecked() }
|
||||
}
|
||||
|
||||
/// A that uniquely identifies the type of exception.
|
||||
///
|
||||
/// See [Apple's documentation][doc] for some of the different values this
|
||||
/// can take.
|
||||
///
|
||||
/// [doc]: https://developer.apple.com/documentation/foundation/nsexceptionname?language=objc
|
||||
pub fn name(&self) -> Id<NSExceptionName, Shared> {
|
||||
// Nullability not documented, but a name is expected in most places.
|
||||
unsafe { msg_send_id![self, name] }
|
||||
}
|
||||
|
||||
/// A human-readable message summarizing the reason for the exception.
|
||||
pub fn reason(&self) -> Option<Id<NSString, Shared>> {
|
||||
unsafe { msg_send_id![self, reason] }
|
||||
}
|
||||
|
||||
/// Application-specific data pertaining to the exception.
|
||||
pub fn user_info(&self) -> Option<Id<NSDictionary<Object, Object>, Shared>> {
|
||||
unsafe { msg_send_id![self, userInfo] }
|
||||
}
|
||||
|
||||
/// Convert this into an [`Exception`] object.
|
||||
pub fn into_exception(this: Id<Self, Shared>) -> Id<Exception, Shared> {
|
||||
// SAFETY: Downcasting to "subclass"
|
||||
unsafe { Id::cast(this) }
|
||||
}
|
||||
|
||||
pub(crate) fn is_nsexception(obj: &Exception) -> bool {
|
||||
if obj.class().responds_to(sel!(isKindOfClass:)) {
|
||||
// SAFETY: We only use `isKindOfClass:` on NSObject
|
||||
let obj: *const Exception = obj;
|
||||
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
|
||||
obj.is_kind_of::<Self>()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Create this from an [`Exception`] object.
|
||||
///
|
||||
/// This should be considered a hint; it may return `Err` in very, very
|
||||
/// few cases where the object is actually an instance of `NSException`.
|
||||
pub fn from_exception(
|
||||
obj: Id<Exception, Shared>,
|
||||
) -> Result<Id<Self, Shared>, Id<Exception, Shared>> {
|
||||
if Self::is_nsexception(&obj) {
|
||||
// SAFETY: Just checked the object is an NSException
|
||||
Ok(unsafe { Id::cast::<Self>(obj) })
|
||||
} else {
|
||||
Err(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSException {
|
||||
type Ownership = Shared;
|
||||
type Output = NSException;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSException {
|
||||
type Owned = Id<NSException, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSException {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let obj: &Object = self.as_ref();
|
||||
write!(f, "{:?} '{}'", obj, self.name())?;
|
||||
if let Some(reason) = self.reason() {
|
||||
write!(f, " reason:{}", reason)?;
|
||||
} else {
|
||||
write!(f, " reason:(NULL)")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_and_query() {
|
||||
let exc = NSException::new(
|
||||
&NSString::from_str("abc"),
|
||||
Some(&NSString::from_str("def")),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(exc.name(), NSString::from_str("abc"));
|
||||
assert_eq!(exc.reason().unwrap(), NSString::from_str("def"));
|
||||
assert!(exc.user_info().is_none());
|
||||
|
||||
let debug = format!("<NSException: {:p}> 'abc' reason:def", exc);
|
||||
assert_eq!(format!("{:?}", exc), debug);
|
||||
|
||||
let description = if cfg!(feature = "gnustep-1-7") {
|
||||
format!("<NSException: {:p}> NAME:abc REASON:def", exc)
|
||||
} else {
|
||||
"def".into()
|
||||
};
|
||||
|
||||
let exc: &NSObject = &exc;
|
||||
assert_eq!(format!("{:?}", exc), description);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "'abc' reason:def"]
|
||||
fn unwrap() {
|
||||
let exc = NSException::new(
|
||||
&NSString::from_str("abc"),
|
||||
Some(&NSString::from_str("def")),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _: () = Err(exc).unwrap();
|
||||
}
|
||||
|
||||
// Further tests in `tests::exception`
|
||||
}
|
||||
446
third-party/vendor/objc2/src/foundation/geometry.rs
vendored
Normal file
446
third-party/vendor/objc2/src/foundation/geometry.rs
vendored
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
use crate::{Encode, Encoding, RefEncode};
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
type InnerFloat = f64;
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
type InnerFloat = f32;
|
||||
|
||||
/// The basic type for all floating-point values.
|
||||
///
|
||||
/// This is [`f32`] on 32-bit platforms and [`f64`] on 64-bit platforms.
|
||||
///
|
||||
/// This technically belongs to the `CoreGraphics` framework, but we define it
|
||||
/// here for convenience.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/coregraphics/cgfloat?language=objc).
|
||||
// Defined in CoreGraphics/CGBase.h
|
||||
// TODO: Use a newtype here?
|
||||
pub type CGFloat = InnerFloat;
|
||||
|
||||
// NSGeometry types are just aliases to CGGeometry types on iOS, tvOS, watchOS
|
||||
// and macOS 64bit (and hence their Objective-C encodings are different).
|
||||
//
|
||||
// TODO: Adjust `objc2-encode` so that this is handled there, and so that we
|
||||
// can effectively just forget about it and use `NS` and `CG` types equally.
|
||||
#[cfg(all(
|
||||
feature = "apple",
|
||||
not(all(target_os = "macos", target_pointer_width = "32"))
|
||||
))]
|
||||
mod names {
|
||||
pub(super) const POINT: &str = "CGPoint";
|
||||
pub(super) const SIZE: &str = "CGSize";
|
||||
pub(super) const RECT: &str = "CGRect";
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "gnustep-1-7",
|
||||
all(target_os = "macos", target_pointer_width = "32")
|
||||
))]
|
||||
mod names {
|
||||
pub(super) const POINT: &str = "_NSPoint";
|
||||
pub(super) const SIZE: &str = "_NSSize";
|
||||
pub(super) const RECT: &str = "_NSRect";
|
||||
}
|
||||
|
||||
/// A point in a two-dimensional coordinate system.
|
||||
///
|
||||
/// This technically belongs to the `CoreGraphics` framework, but we define it
|
||||
/// here for convenience.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgpoint?language=objc).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct CGPoint {
|
||||
/// The x-coordinate of the point.
|
||||
pub x: CGFloat,
|
||||
/// The y-coordinate of the point.
|
||||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
unsafe impl Encode for CGPoint {
|
||||
const ENCODING: Encoding =
|
||||
Encoding::Struct(names::POINT, &[CGFloat::ENCODING, CGFloat::ENCODING]);
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for CGPoint {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
impl CGPoint {
|
||||
/// Create a new point with the given coordinates.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGPoint;
|
||||
/// assert_eq!(CGPoint::new(10.0, -2.3), CGPoint { x: 10.0, y: -2.3 });
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "NSMakePoint")]
|
||||
#[doc(alias = "CGPointMake")]
|
||||
pub const fn new(x: CGFloat, y: CGFloat) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// A point with both coordinates set to `0.0`.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGPoint;
|
||||
/// assert_eq!(CGPoint::ZERO, CGPoint { x: 0.0, y: 0.0 });
|
||||
/// ```
|
||||
#[doc(alias = "NSZeroPoint")]
|
||||
#[doc(alias = "CGPointZero")]
|
||||
#[doc(alias = "ORIGIN")]
|
||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||
}
|
||||
|
||||
/// A two-dimensional size.
|
||||
///
|
||||
/// As this is sometimes used to represent a distance vector, rather than a
|
||||
/// physical size, the width and height are _not_ guaranteed to be
|
||||
/// non-negative! Methods that expect that must use one of [`CGSize::abs`] or
|
||||
/// [`CGRect::standardize`].
|
||||
///
|
||||
/// This technically belongs to the `CoreGraphics` framework, but we define it
|
||||
/// here for convenience.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgsize?language=objc).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct CGSize {
|
||||
/// The dimensions along the x-axis.
|
||||
pub width: CGFloat,
|
||||
/// The dimensions along the y-axis.
|
||||
pub height: CGFloat,
|
||||
}
|
||||
|
||||
unsafe impl Encode for CGSize {
|
||||
const ENCODING: Encoding =
|
||||
Encoding::Struct(names::SIZE, &[CGFloat::ENCODING, CGFloat::ENCODING]);
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for CGSize {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
impl CGSize {
|
||||
/// Create a new size with the given dimensions.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGSize;
|
||||
/// let size = CGSize::new(10.0, 2.3);
|
||||
/// assert_eq!(size.width, 10.0);
|
||||
/// assert_eq!(size.height, 2.3);
|
||||
/// ```
|
||||
///
|
||||
/// Negative values are allowed (though often undesired).
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGSize;
|
||||
/// let size = CGSize::new(-1.0, 0.0);
|
||||
/// assert_eq!(size.width, -1.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "NSMakeSize")]
|
||||
#[doc(alias = "CGSizeMake")]
|
||||
pub const fn new(width: CGFloat, height: CGFloat) -> Self {
|
||||
// The documentation for NSSize explicitly says:
|
||||
// > If the value of width or height is negative, however, the
|
||||
// > behavior of some methods may be undefined.
|
||||
//
|
||||
// But since this type can come from FFI, we'll leave it up to the
|
||||
// user to ensure that it is used safely.
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
/// Convert the size to a non-negative size.
|
||||
///
|
||||
/// This can be used to convert the size to a safe value.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGSize;
|
||||
/// assert_eq!(CGSize::new(-1.0, 1.0).abs(), CGSize::new(1.0, 1.0));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
Self::new(self.width.abs(), self.height.abs())
|
||||
}
|
||||
|
||||
/// A size that is 0.0 in both dimensions.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::CGSize;
|
||||
/// assert_eq!(CGSize::ZERO, CGSize { width: 0.0, height: 0.0 });
|
||||
/// ```
|
||||
#[doc(alias = "NSZeroSize")]
|
||||
#[doc(alias = "CGSizeZero")]
|
||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||
}
|
||||
|
||||
/// The location and dimensions of a rectangle.
|
||||
///
|
||||
/// In the default Core Graphics coordinate space (macOS), the origin is
|
||||
/// located in the lower-left corner of the rectangle and the rectangle
|
||||
/// extends towards the upper-right corner.
|
||||
///
|
||||
/// If the context has a flipped coordinate space (iOS, tvOS, watchOS) the
|
||||
/// origin is in the upper-left corner and the rectangle extends towards the
|
||||
/// lower-right corner.
|
||||
///
|
||||
/// This technically belongs to the `CoreGraphics` framework, but we define it
|
||||
/// here for convenience.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgrect?language=objc).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct CGRect {
|
||||
/// The coordinates of the rectangle’s origin.
|
||||
pub origin: CGPoint,
|
||||
/// The dimensions of the rectangle.
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
unsafe impl Encode for CGRect {
|
||||
const ENCODING: Encoding =
|
||||
Encoding::Struct(names::RECT, &[CGPoint::ENCODING, CGSize::ENCODING]);
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for CGRect {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
impl CGRect {
|
||||
/// Create a new rectangle with the given origin and dimensions.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
|
||||
/// let origin = CGPoint::new(10.0, -2.3);
|
||||
/// let size = CGSize::new(5.0, 0.0);
|
||||
/// let rect = CGRect::new(origin, size);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "NSMakeRect")]
|
||||
#[doc(alias = "CGRectMake")]
|
||||
pub const fn new(origin: CGPoint, size: CGSize) -> Self {
|
||||
Self { origin, size }
|
||||
}
|
||||
|
||||
/// A rectangle with origin (0.0, 0.0) and zero width and height.
|
||||
#[doc(alias = "NSZeroRect")]
|
||||
#[doc(alias = "CGRectZero")]
|
||||
pub const ZERO: Self = Self::new(CGPoint::ZERO, CGSize::ZERO);
|
||||
|
||||
/// Returns a rectangle with a positive width and height.
|
||||
///
|
||||
/// This is often useful
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
|
||||
/// let origin = CGPoint::new(1.0, 1.0);
|
||||
/// let size = CGSize::new(-5.0, -2.0);
|
||||
/// let rect = CGRect::new(origin, size);
|
||||
/// assert_eq!(rect.standardize().size, CGSize::new(5.0, 2.0));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "CGRectStandardize")]
|
||||
pub fn standardize(self) -> Self {
|
||||
Self::new(self.origin, self.size.abs())
|
||||
}
|
||||
|
||||
/// The smallest coordinate of the rectangle.
|
||||
#[inline]
|
||||
#[doc(alias = "CGRectGetMinX")]
|
||||
#[doc(alias = "CGRectGetMinY")]
|
||||
#[doc(alias = "NSMinX")]
|
||||
#[doc(alias = "NSMinY")]
|
||||
pub fn min(self) -> CGPoint {
|
||||
self.origin
|
||||
}
|
||||
|
||||
/// The center point of the rectangle.
|
||||
#[inline]
|
||||
#[doc(alias = "CGRectGetMidX")]
|
||||
#[doc(alias = "CGRectGetMidY")]
|
||||
#[doc(alias = "NSMidX")]
|
||||
#[doc(alias = "NSMidY")]
|
||||
pub fn mid(self) -> CGPoint {
|
||||
CGPoint::new(
|
||||
self.origin.x + (self.size.width * 0.5),
|
||||
self.origin.y + (self.size.height * 0.5),
|
||||
)
|
||||
}
|
||||
|
||||
/// The largest coordinate of the rectangle.
|
||||
#[inline]
|
||||
#[doc(alias = "CGRectGetMaxX")]
|
||||
#[doc(alias = "CGRectGetMaxY")]
|
||||
#[doc(alias = "NSMaxX")]
|
||||
#[doc(alias = "NSMaxY")]
|
||||
pub fn max(self) -> CGPoint {
|
||||
CGPoint::new(
|
||||
self.origin.x + self.size.width,
|
||||
self.origin.y + self.size.height,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns whether a rectangle has zero width or height.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
|
||||
/// assert!(CGRect::ZERO.is_empty());
|
||||
/// let point = CGPoint::new(1.0, 2.0);
|
||||
/// assert!(CGRect::new(point, CGSize::ZERO).is_empty());
|
||||
/// assert!(!CGRect::new(point, CGSize::new(1.0, 1.0)).is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "CGRectIsEmpty")]
|
||||
pub fn is_empty(self) -> bool {
|
||||
!(self.size.width > 0.0 && self.size.height > 0.0)
|
||||
// TODO: NaN handling?
|
||||
// self.size.width <= 0.0 || self.size.height <= 0.0
|
||||
}
|
||||
|
||||
// TODO: NSContainsRect / CGRectContainsRect
|
||||
// TODO: NSDivideRect / CGRectDivide
|
||||
// TODO: NSInsetRect / CGRectInset
|
||||
// TODO: NSIntegralRect / CGRectIntegral
|
||||
// TODO: NSIntersectionRect / CGRectIntersection
|
||||
// TODO: NSUnionRect / CGRectUnion
|
||||
// TODO: NSIntersectsRect / CGRectIntersectsRect
|
||||
// TODO: NSMouseInRect
|
||||
// TODO: NSMouseInRect
|
||||
// TODO: NSPointInRect / CGRectContainsPoint
|
||||
// TODO: NSOffsetRect / CGRectOffset
|
||||
|
||||
// TODO: CGRectIsNull
|
||||
// TODO: CGRectIsInfinite
|
||||
// TODO: CGRectInfinite
|
||||
// TODO: CGRectNull
|
||||
|
||||
// TODO: NSHeight / CGRectGetHeight (standardized)
|
||||
// TODO: NSWidth / CGRectGetWidth (standardized)
|
||||
}
|
||||
|
||||
/// A point in a Cartesian coordinate system.
|
||||
///
|
||||
/// This is just a convenience alias for [`CGPoint`]. For ease of use, it is
|
||||
/// available on all platforms, though in practice it is only useful on macOS.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nspoint?language=objc).
|
||||
pub type NSPoint = CGPoint;
|
||||
|
||||
/// A two-dimensional size.
|
||||
///
|
||||
/// This is just a convenience alias for [`CGSize`]. For ease of use, it is
|
||||
/// available on all platforms, though in practice it is only useful on macOS.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nssize?language=objc).
|
||||
pub type NSSize = CGSize;
|
||||
|
||||
/// A rectangle.
|
||||
///
|
||||
/// This is just a convenience alias for [`CGRect`]. For ease of use, it is
|
||||
/// available on all platforms, though in practice it is only useful on macOS.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrect?language=objc).
|
||||
pub type NSRect = CGRect;
|
||||
|
||||
// TODO: struct NSEdgeInsets
|
||||
// TODO: enum NSRectEdge
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cgsize_new() {
|
||||
CGSize::new(1.0, 1.0);
|
||||
CGSize::new(0.0, -0.0);
|
||||
CGSize::new(-0.0, 0.0);
|
||||
CGSize::new(-0.0, -0.0);
|
||||
CGSize::new(-1.0, -1.0);
|
||||
CGSize::new(-1.0, 1.0);
|
||||
CGSize::new(1.0, -1.0);
|
||||
}
|
||||
|
||||
// We know the Rust implementation handles NaN, infinite, negative zero
|
||||
// and so on properly, so let's ensure that NSEqualXXX handles these as
|
||||
// well (so that we're confident that the implementations are equivalent).
|
||||
#[test]
|
||||
#[cfg(any(all(feature = "apple", target_os = "macos"), feature = "gnustep-1-7"))] // or macabi
|
||||
fn test_partial_eq() {
|
||||
use crate::runtime::Bool;
|
||||
|
||||
// Note: No need to use "C-unwind"
|
||||
extern "C" {
|
||||
fn NSEqualPoints(a: NSPoint, b: NSPoint) -> Bool;
|
||||
fn NSEqualSizes(a: NSSize, b: NSSize) -> Bool;
|
||||
fn NSEqualRects(a: NSRect, b: NSRect) -> Bool;
|
||||
}
|
||||
|
||||
// We assume that comparisons handle e.g. `x` and `y` in the same way,
|
||||
// therefore we just set the coordinates / dimensions to the same.
|
||||
let cases: &[(CGFloat, CGFloat)] = &[
|
||||
(0.0, 0.0),
|
||||
(-0.0, -0.0),
|
||||
(0.0, -0.0),
|
||||
(1.0, 1.0 + CGFloat::EPSILON),
|
||||
(0.0, CGFloat::MIN_POSITIVE),
|
||||
(0.0, CGFloat::EPSILON),
|
||||
(1.0, 1.0),
|
||||
(1.0, -1.0),
|
||||
// Infinity
|
||||
(CGFloat::INFINITY, CGFloat::INFINITY),
|
||||
(CGFloat::INFINITY, CGFloat::NEG_INFINITY),
|
||||
(CGFloat::NEG_INFINITY, CGFloat::NEG_INFINITY),
|
||||
// NaN
|
||||
(CGFloat::NAN, 0.0),
|
||||
(CGFloat::NAN, 1.0),
|
||||
(CGFloat::NAN, CGFloat::NAN),
|
||||
(CGFloat::NAN, -CGFloat::NAN),
|
||||
(-CGFloat::NAN, -CGFloat::NAN),
|
||||
(CGFloat::NAN, CGFloat::INFINITY),
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let point_a = NSPoint::new(case.0, case.1);
|
||||
let point_b = NSPoint::new(case.0, case.1);
|
||||
let actual = unsafe { NSEqualPoints(point_a, point_b).as_bool() };
|
||||
assert_eq!(point_a == point_b, actual);
|
||||
|
||||
if case.0 >= 0.0 && case.1 >= 0.0 {
|
||||
let size_a = NSSize::new(case.0, case.1);
|
||||
let size_b = NSSize::new(case.0, case.1);
|
||||
let actual = unsafe { NSEqualSizes(size_a, size_b).as_bool() };
|
||||
assert_eq!(size_a == size_b, actual);
|
||||
|
||||
let rect_a = NSRect::new(point_a, size_a);
|
||||
let rect_b = NSRect::new(point_b, size_b);
|
||||
let actual = unsafe { NSEqualRects(rect_a, rect_b).as_bool() };
|
||||
assert_eq!(rect_a == rect_b, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
215
third-party/vendor/objc2/src/foundation/mod.rs
vendored
Normal file
215
third-party/vendor/objc2/src/foundation/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
//! Bindings to the `Foundation` framework.
|
||||
//!
|
||||
//! This is the [`std`] equivalent for Objective-C, containing essential data
|
||||
//! types, collections, and operating-system services.
|
||||
//!
|
||||
//! See [Apple's documentation](https://developer.apple.com/documentation/foundation?language=objc).
|
||||
//!
|
||||
//!
|
||||
//! ## Philosophy
|
||||
//!
|
||||
//! The `Foundation` framework is _huge_! If we aspired to map every API it
|
||||
//! exposes (a lot of it is just helper methods to make Objective-C more
|
||||
//! ergonomic), this library would never be finished. Instead, our focus lies
|
||||
//! on conversion methods, to allow easily using them from Rust.
|
||||
//!
|
||||
//! If you find some API that an object doesn't expose (but should), we gladly
|
||||
//! accept [pull requests]. If it is something that is out of scope, these
|
||||
//! objects implement the [`Message`] trait, so you can always just manually
|
||||
//! call a method on them using the [`msg_send!`] family of macros.
|
||||
//!
|
||||
//! [pull requests]: https://github.com/madsmtm/objc2/pulls
|
||||
//! [`Message`]: crate::Message
|
||||
//! [`msg_send!`]: crate::msg_send
|
||||
//!
|
||||
//!
|
||||
//! # Use of `Deref`
|
||||
//!
|
||||
//! `objc2::foundation` uses the [`Deref`] trait in a bit special way: All
|
||||
//! objects deref to their superclasses. For example, `NSMutableArray` derefs
|
||||
//! to `NSArray`, which in turn derefs to `NSObject`.
|
||||
//!
|
||||
//! Note that this is explicitly recommended against in [the
|
||||
//! documentation][`Deref`] and [the Rust Design patterns
|
||||
//! book][anti-pattern-deref] (see those links for details).
|
||||
//!
|
||||
//! Due to Objective-C objects only ever being accessible behind pointers in
|
||||
//! the first place, the problems stated there are less severe, and having the
|
||||
//! implementation just means that everything is much nicer when you actually
|
||||
//! want to use the objects!
|
||||
//!
|
||||
//! All objects also implement [`AsRef`] and [`AsMut`] to their superclass,
|
||||
//! and can be used in [`Id::into_super`], so if you favour explicit
|
||||
//! conversion, that is a possibility too.
|
||||
//!
|
||||
//! [`Deref`]: std::ops::Deref
|
||||
//! [`ClassType`]: crate::ClassType
|
||||
//! [anti-pattern-deref]: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
|
||||
//! [`Id::into_super`]: crate::rc::Id::into_super
|
||||
|
||||
// TODO: Remove these
|
||||
#![allow(missing_docs)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use std::os::raw::c_double;
|
||||
|
||||
pub use self::array::NSArray;
|
||||
pub use self::attributed_string::{NSAttributedString, NSAttributedStringKey};
|
||||
pub use self::bundle::NSBundle;
|
||||
pub use self::comparison_result::NSComparisonResult;
|
||||
pub use self::copying::{NSCopying, NSMutableCopying};
|
||||
pub use self::data::NSData;
|
||||
pub use self::dictionary::NSDictionary;
|
||||
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
|
||||
pub use self::error::{NSError, NSErrorDomain, NSErrorUserInfoKey};
|
||||
pub use self::exception::NSException;
|
||||
pub use self::geometry::{CGFloat, CGPoint, CGRect, CGSize, NSPoint, NSRect, NSSize};
|
||||
pub use self::mutable_array::NSMutableArray;
|
||||
pub use self::mutable_attributed_string::NSMutableAttributedString;
|
||||
pub use self::mutable_data::NSMutableData;
|
||||
pub use self::mutable_dictionary::NSMutableDictionary;
|
||||
pub use self::mutable_set::NSMutableSet;
|
||||
pub use self::mutable_string::NSMutableString;
|
||||
pub use self::number::NSNumber;
|
||||
pub use self::object::NSObject;
|
||||
pub use self::process_info::NSProcessInfo;
|
||||
pub use self::range::NSRange;
|
||||
pub use self::set::NSSet;
|
||||
pub use self::string::NSString;
|
||||
pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker, NSThread};
|
||||
#[cfg(not(macos_10_7))] // Temporary
|
||||
pub use self::uuid::NSUUID;
|
||||
pub use self::value::NSValue;
|
||||
pub use self::zone::NSZone;
|
||||
|
||||
// Available under Foundation, so makes sense here as well:
|
||||
// https://developer.apple.com/documentation/foundation/numbers_data_and_basic_values?language=objc
|
||||
#[doc(no_inline)]
|
||||
pub use crate::ffi::{NSInteger, NSUInteger};
|
||||
|
||||
/// A value indicating that a requested item couldn’t be found or doesn’t exist.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsnotfound?language=objc).
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const NSNotFound: NSInteger = crate::ffi::NSIntegerMax;
|
||||
|
||||
/// A number of seconds.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nstimeinterval?language=objc).
|
||||
pub type NSTimeInterval = c_double;
|
||||
|
||||
#[cfg(feature = "apple")]
|
||||
#[link(name = "Foundation", kind = "framework")]
|
||||
extern "C" {}
|
||||
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
#[link(name = "gnustep-base", kind = "dylib")]
|
||||
extern "C" {}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __ns_string;
|
||||
mod array;
|
||||
mod attributed_string;
|
||||
mod bundle;
|
||||
mod comparison_result;
|
||||
mod copying;
|
||||
mod data;
|
||||
mod dictionary;
|
||||
mod enumerator;
|
||||
mod error;
|
||||
mod exception;
|
||||
mod geometry;
|
||||
mod mutable_array;
|
||||
mod mutable_attributed_string;
|
||||
mod mutable_data;
|
||||
mod mutable_dictionary;
|
||||
mod mutable_set;
|
||||
mod mutable_string;
|
||||
mod number;
|
||||
mod object;
|
||||
mod process_info;
|
||||
mod range;
|
||||
mod set;
|
||||
mod string;
|
||||
mod thread;
|
||||
// Temporarily disable testing UUID on macOS 10.7 until
|
||||
#[cfg(not(macos_10_7))]
|
||||
mod uuid;
|
||||
mod value;
|
||||
mod zone;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::*;
|
||||
use crate::rc::{Id, Owned, Shared};
|
||||
|
||||
// We expect most Foundation types to be UnwindSafe and RefUnwindSafe,
|
||||
// since they follow Rust's usual mutability rules (&T = immutable).
|
||||
//
|
||||
// A _lot_ of Objective-C code out there would be subtly broken if e.g.
|
||||
// `NSString` wasn't exception safe!
|
||||
// As an example: -[NSArray objectAtIndex:] can throw, but it is still
|
||||
// perfectly valid to access the array after that!
|
||||
//
|
||||
// Note that e.g. `&mut NSMutableString` is still not exception safe, but
|
||||
// that is the entire idea of `UnwindSafe` (that if the object could have
|
||||
// been mutated, it is not exception safe).
|
||||
//
|
||||
// Also note that this is still just a speed bump, not actually part of
|
||||
// any unsafe contract; we really protect against it if something is not
|
||||
// exception safe, since `UnwindSafe` is a safe trait.
|
||||
fn assert_unwindsafe<T: UnwindSafe + RefUnwindSafe>() {}
|
||||
|
||||
fn assert_auto_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {
|
||||
assert_unwindsafe::<T>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_sync_unwindsafe() {
|
||||
assert_auto_traits::<NSArray<NSString, Shared>>();
|
||||
assert_auto_traits::<NSArray<NSString, Owned>>();
|
||||
assert_auto_traits::<Id<NSArray<NSString, Shared>, Shared>>();
|
||||
assert_auto_traits::<Id<NSArray<NSString, Owned>, Shared>>();
|
||||
assert_auto_traits::<Id<NSArray<NSString, Shared>, Owned>>();
|
||||
assert_auto_traits::<Id<NSArray<NSString, Owned>, Owned>>();
|
||||
|
||||
assert_auto_traits::<NSAttributedString>();
|
||||
assert_auto_traits::<NSComparisonResult>();
|
||||
assert_auto_traits::<NSData>();
|
||||
assert_auto_traits::<NSDictionary<NSString, NSString>>();
|
||||
assert_auto_traits::<NSSet<NSString, Shared>>();
|
||||
assert_auto_traits::<NSSet<NSString, Owned>>();
|
||||
assert_auto_traits::<Id<NSSet<NSString, Shared>, Shared>>();
|
||||
assert_auto_traits::<Id<NSSet<NSString, Owned>, Shared>>();
|
||||
assert_auto_traits::<Id<NSSet<NSString, Shared>, Owned>>();
|
||||
assert_auto_traits::<Id<NSSet<NSString, Owned>, Owned>>();
|
||||
// TODO: Figure out if Send + Sync is safe?
|
||||
// assert_auto_traits::<NSEnumerator<NSString>>();
|
||||
// assert_auto_traits::<NSFastEnumerator<NSArray<NSString, Shared>>>();
|
||||
assert_auto_traits::<NSError>();
|
||||
assert_auto_traits::<NSException>();
|
||||
assert_auto_traits::<CGFloat>();
|
||||
assert_auto_traits::<NSPoint>();
|
||||
assert_auto_traits::<NSRect>();
|
||||
assert_auto_traits::<NSSize>();
|
||||
assert_auto_traits::<NSMutableArray<NSString, Shared>>();
|
||||
assert_auto_traits::<NSMutableAttributedString>();
|
||||
assert_auto_traits::<NSMutableData>();
|
||||
assert_auto_traits::<NSMutableDictionary<NSString, NSString>>();
|
||||
assert_auto_traits::<NSMutableSet<NSString, Shared>>();
|
||||
assert_auto_traits::<NSMutableString>();
|
||||
assert_auto_traits::<NSNumber>();
|
||||
// assert_auto_traits::<NSObject>(); // Intentional
|
||||
assert_auto_traits::<NSProcessInfo>();
|
||||
assert_auto_traits::<NSRange>();
|
||||
assert_auto_traits::<NSString>();
|
||||
assert_unwindsafe::<MainThreadMarker>(); // Intentional
|
||||
assert_auto_traits::<NSThread>();
|
||||
#[cfg(not(macos_10_7))]
|
||||
assert_auto_traits::<NSUUID>();
|
||||
// assert_auto_traits::<NSValue>(); // Intentional
|
||||
assert_unwindsafe::<NSZone>(); // Intentional
|
||||
}
|
||||
}
|
||||
326
third-party/vendor/objc2/src/foundation/mutable_array.rs
vendored
Normal file
326
third-party/vendor/objc2/src/foundation/mutable_array.rs
vendored
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::cmp::Ordering;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Index, IndexMut};
|
||||
|
||||
use super::array::with_objects;
|
||||
use super::{
|
||||
NSArray, NSComparisonResult, NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
|
||||
NSObject,
|
||||
};
|
||||
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
|
||||
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
|
||||
|
||||
__inner_extern_class!(
|
||||
/// A growable ordered collection of objects.
|
||||
///
|
||||
/// See the documentation for [`NSArray`] and/or [Apple's
|
||||
/// documentation][apple-doc] for more information.
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutablearray?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableArray<T: Message, O: Ownership = Owned> {
|
||||
p: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableArray<T, O> {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSArray<T, O>;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Same as NSArray<T, O>
|
||||
//
|
||||
// Put here because rustdoc doesn't show these otherwise
|
||||
unsafe impl<T: Message + Sync + Send> Sync for NSMutableArray<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync + Send> Send for NSMutableArray<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync> Sync for NSMutableArray<T, Owned> {}
|
||||
unsafe impl<T: Message + Send> Send for NSMutableArray<T, Owned> {}
|
||||
|
||||
extern_methods!(
|
||||
/// Generic creation methods.
|
||||
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
// SAFETY: Same as `NSArray::new`, except mutable arrays are always
|
||||
// unique.
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
|
||||
// SAFETY: Same as `NSArray::from_vec`, except mutable arrays are
|
||||
// always unique.
|
||||
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creation methods that produce shared arrays.
|
||||
unsafe impl<T: Message> NSMutableArray<T, Shared> {
|
||||
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
|
||||
// SAFETY: Same as `NSArray::from_slice`, except mutable arrays are
|
||||
// always unique.
|
||||
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic accessor methods.
|
||||
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
|
||||
#[doc(alias = "addObject:")]
|
||||
pub fn push(&mut self, obj: Id<T, O>) {
|
||||
// SAFETY: The object is not nil
|
||||
unsafe { msg_send![self, addObject: &*obj] }
|
||||
}
|
||||
|
||||
#[doc(alias = "insertObject:atIndex:")]
|
||||
pub fn insert(&mut self, index: usize, obj: Id<T, O>) {
|
||||
// TODO: Replace this check with catching the thrown NSRangeException
|
||||
let len = self.len();
|
||||
if index < len {
|
||||
// SAFETY: The object is not nil and the index is checked to be in
|
||||
// bounds.
|
||||
unsafe { msg_send![self, insertObject: &*obj, atIndex: index] }
|
||||
} else {
|
||||
panic!(
|
||||
"insertion index (is {}) should be <= len (is {})",
|
||||
index, len
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "replaceObjectAtIndex:withObject:")]
|
||||
pub fn replace(&mut self, index: usize, obj: Id<T, O>) -> Id<T, O> {
|
||||
let old_obj = unsafe {
|
||||
let obj = self.get(index).unwrap();
|
||||
Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked()
|
||||
};
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
self,
|
||||
replaceObjectAtIndex: index,
|
||||
withObject: &*obj,
|
||||
];
|
||||
}
|
||||
old_obj
|
||||
}
|
||||
|
||||
#[sel(removeObjectAtIndex:)]
|
||||
unsafe fn remove_at(&mut self, index: usize);
|
||||
|
||||
#[doc(alias = "removeObjectAtIndex:")]
|
||||
pub fn remove(&mut self, index: usize) -> Id<T, O> {
|
||||
let obj = if let Some(obj) = self.get(index) {
|
||||
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
|
||||
} else {
|
||||
panic!("removal index should be < len");
|
||||
};
|
||||
unsafe { self.remove_at(index) };
|
||||
obj
|
||||
}
|
||||
|
||||
#[sel(removeLastObject)]
|
||||
unsafe fn remove_last(&mut self);
|
||||
|
||||
#[doc(alias = "removeLastObject")]
|
||||
pub fn pop(&mut self) -> Option<Id<T, O>> {
|
||||
self.last()
|
||||
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
|
||||
.map(|obj| {
|
||||
// SAFETY: `Self::last` just checked that there is an object
|
||||
unsafe { self.remove_last() };
|
||||
obj
|
||||
})
|
||||
}
|
||||
|
||||
#[doc(alias = "removeAllObjects")]
|
||||
#[sel(removeAllObjects)]
|
||||
pub fn clear(&mut self);
|
||||
|
||||
#[doc(alias = "sortUsingFunction:context:")]
|
||||
pub fn sort_by<F: FnMut(&T, &T) -> Ordering>(&mut self, compare: F) {
|
||||
// TODO: "C-unwind"
|
||||
extern "C" fn compare_with_closure<U, F: FnMut(&U, &U) -> Ordering>(
|
||||
obj1: &U,
|
||||
obj2: &U,
|
||||
context: *mut c_void,
|
||||
) -> NSComparisonResult {
|
||||
// Bring back a reference to the closure.
|
||||
// Guaranteed to be unique, we gave `sortUsingFunction` unique is
|
||||
// ownership, and that method only runs one function at a time.
|
||||
let closure: &mut F = unsafe { context.cast::<F>().as_mut().unwrap_unchecked() };
|
||||
|
||||
NSComparisonResult::from((*closure)(obj1, obj2))
|
||||
}
|
||||
|
||||
// We can't name the actual lifetimes in use here, so use `_`.
|
||||
// See also https://github.com/rust-lang/rust/issues/56105
|
||||
let f: extern "C" fn(_, _, *mut c_void) -> NSComparisonResult =
|
||||
compare_with_closure::<T, F>;
|
||||
|
||||
// Grab a type-erased pointer to the closure (a pointer to stack).
|
||||
let mut closure = compare;
|
||||
let context: *mut F = &mut closure;
|
||||
let context: *mut c_void = context.cast();
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![self, sortUsingFunction: f, context: context];
|
||||
}
|
||||
// Keep the closure alive until the function has run.
|
||||
drop(closure);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Copying only possible when ItemOwnership = Shared
|
||||
|
||||
/// This is implemented as a shallow copy.
|
||||
unsafe impl<T: Message> NSCopying for NSMutableArray<T, Shared> {
|
||||
type Ownership = Shared;
|
||||
type Output = NSArray<T, Shared>;
|
||||
}
|
||||
|
||||
/// This is implemented as a shallow copy.
|
||||
unsafe impl<T: Message> NSMutableCopying for NSMutableArray<T, Shared> {
|
||||
type Output = NSMutableArray<T, Shared>;
|
||||
}
|
||||
|
||||
impl<T: Message> alloc::borrow::ToOwned for NSMutableArray<T, Shared> {
|
||||
type Owned = Id<NSMutableArray<T, Shared>, Owned>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.mutable_copy()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableArray<T, O> {
|
||||
type Item = T;
|
||||
}
|
||||
|
||||
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableArray<T, O> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = NSFastEnumerator<'a, NSMutableArray<T, O>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_fast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> Extend<Id<T, O>> for NSMutableArray<T, O> {
|
||||
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
|
||||
let iterator = iter.into_iter();
|
||||
iterator.for_each(move |item| self.push(item));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> Index<usize> for NSMutableArray<T, O> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &T {
|
||||
self.get(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message> IndexMut<usize> for NSMutableArray<T, Owned> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut T {
|
||||
self.get_mut(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> DefaultId for NSMutableArray<T, O> {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableArray<T, O> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::NSString;
|
||||
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
|
||||
|
||||
#[test]
|
||||
fn test_adding() {
|
||||
let mut array = NSMutableArray::new();
|
||||
let obj1 = RcTestObject::new();
|
||||
let obj2 = RcTestObject::new();
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
array.push(obj1);
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(array.len(), 1);
|
||||
assert_eq!(array.get(0), array.get(0));
|
||||
|
||||
array.insert(0, obj2);
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(array.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace() {
|
||||
let mut array = NSMutableArray::new();
|
||||
let obj1 = RcTestObject::new();
|
||||
let obj2 = RcTestObject::new();
|
||||
array.push(obj1);
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let old_obj = array.replace(0, obj2);
|
||||
expected.retain += 2;
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
assert_ne!(&*old_obj, array.get(0).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let mut array = NSMutableArray::new();
|
||||
for _ in 0..4 {
|
||||
array.push(RcTestObject::new());
|
||||
}
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let _obj = array.remove(1);
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(array.len(), 3);
|
||||
|
||||
let _obj = array.pop();
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(array.len(), 2);
|
||||
|
||||
array.clear();
|
||||
expected.release += 2;
|
||||
expected.dealloc += 2;
|
||||
expected.assert_current();
|
||||
assert_eq!(array.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort() {
|
||||
let strings = vec![NSString::from_str("hello"), NSString::from_str("hi")];
|
||||
let mut strings = NSMutableArray::from_vec(strings);
|
||||
|
||||
autoreleasepool(|pool| {
|
||||
strings.sort_by(|s1, s2| s1.as_str(pool).len().cmp(&s2.as_str(pool).len()));
|
||||
assert_eq!(strings[0].as_str(pool), "hi");
|
||||
assert_eq!(strings[1].as_str(pool), "hello");
|
||||
});
|
||||
}
|
||||
}
|
||||
115
third-party/vendor/objc2/src/foundation/mutable_attributed_string.rs
vendored
Normal file
115
third-party/vendor/objc2/src/foundation/mutable_attributed_string.rs
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
use core::fmt;
|
||||
|
||||
use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString};
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A mutable string that has associated attributes.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutableattributedstring?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableAttributedString;
|
||||
|
||||
unsafe impl ClassType for NSMutableAttributedString {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSAttributedString;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
/// Creating mutable attributed strings.
|
||||
unsafe impl NSMutableAttributedString {
|
||||
/// Construct an empty mutable attributed string.
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
// TODO: new_with_attributes
|
||||
|
||||
#[doc(alias = "initWithString:")]
|
||||
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithString: string]
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "initWithAttributedString:")]
|
||||
pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithAttributedString: attributed_string]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifying the attributed string.
|
||||
unsafe impl NSMutableAttributedString {
|
||||
// TODO
|
||||
// - mutableString
|
||||
// - replaceCharactersInRange:withString:
|
||||
// - setAttributes:range:
|
||||
|
||||
/// Replaces the entire attributed string.
|
||||
#[doc(alias = "setAttributedString:")]
|
||||
#[sel(setAttributedString:)]
|
||||
pub fn replace(&mut self, attributed_string: &NSAttributedString);
|
||||
}
|
||||
);
|
||||
|
||||
impl DefaultId for NSMutableAttributedString {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSCopying for NSMutableAttributedString {
|
||||
type Ownership = Shared;
|
||||
type Output = NSAttributedString;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSMutableAttributedString {
|
||||
type Output = NSMutableAttributedString;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSMutableAttributedString {
|
||||
type Owned = Id<NSMutableAttributedString, Owned>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.mutable_copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSMutableAttributedString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let s = NSAttributedString::new();
|
||||
assert_eq!(&s.string().to_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy() {
|
||||
let s1 = NSMutableAttributedString::from_nsstring(&NSString::from_str("abc"));
|
||||
let s2 = s1.copy();
|
||||
assert_ne!(Id::as_ptr(&s1).cast(), Id::as_ptr(&s2));
|
||||
assert!(s2.is_kind_of::<NSAttributedString>());
|
||||
|
||||
let s3 = s1.mutable_copy();
|
||||
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
|
||||
assert!(s3.is_kind_of::<NSMutableAttributedString>());
|
||||
}
|
||||
}
|
||||
361
third-party/vendor/objc2/src/foundation/mutable_data.rs
vendored
Normal file
361
third-party/vendor/objc2/src/foundation/mutable_data.rs
vendored
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
#[cfg(feature = "block")]
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::ops::{Index, IndexMut, Range};
|
||||
use core::slice::{self, SliceIndex};
|
||||
use std::io;
|
||||
|
||||
use super::data::with_slice;
|
||||
use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange};
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A dynamic byte buffer in memory.
|
||||
///
|
||||
/// This is the Objective-C equivalent of a [`Vec`] containing [`u8`].
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutabledata?language=objc).
|
||||
///
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableData;
|
||||
|
||||
unsafe impl ClassType for NSMutableData {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSData;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
/// Creation methods
|
||||
unsafe impl NSMutableData {
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Owned> {
|
||||
unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "block")]
|
||||
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Owned> {
|
||||
unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) }
|
||||
}
|
||||
|
||||
// TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`?
|
||||
|
||||
#[doc(alias = "initWithData:")]
|
||||
pub fn from_data(data: &NSData) -> Id<Self, Owned> {
|
||||
// Not provided on NSData, one should just use NSData::copy or similar
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithData: data]
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "initWithCapacity:")]
|
||||
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithCapacity: capacity]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutation methods
|
||||
unsafe impl NSMutableData {
|
||||
/// Expands with zeroes, or truncates the buffer.
|
||||
#[doc(alias = "setLength:")]
|
||||
#[sel(setLength:)]
|
||||
pub fn set_len(&mut self, len: usize);
|
||||
|
||||
#[sel(mutableBytes)]
|
||||
fn bytes_mut_raw(&mut self) -> *mut c_void;
|
||||
|
||||
#[doc(alias = "mutableBytes")]
|
||||
pub fn bytes_mut(&mut self) -> &mut [u8] {
|
||||
let ptr = self.bytes_mut_raw();
|
||||
let ptr: *mut u8 = ptr.cast();
|
||||
// The bytes pointer may be null for length zero
|
||||
if ptr.is_null() {
|
||||
&mut []
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts_mut(ptr, self.len()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(appendBytes:length:)]
|
||||
unsafe fn append_raw(&mut self, ptr: *const c_void, len: usize);
|
||||
|
||||
#[doc(alias = "appendBytes:length:")]
|
||||
pub fn extend_from_slice(&mut self, bytes: &[u8]) {
|
||||
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
|
||||
unsafe { self.append_raw(bytes_ptr, bytes.len()) }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, byte: u8) {
|
||||
self.extend_from_slice(&[byte])
|
||||
}
|
||||
|
||||
#[sel(replaceBytesInRange:withBytes:length:)]
|
||||
unsafe fn replace_raw(&mut self, range: NSRange, ptr: *const c_void, len: usize);
|
||||
|
||||
#[doc(alias = "replaceBytesInRange:withBytes:length:")]
|
||||
pub fn replace_range(&mut self, range: Range<usize>, bytes: &[u8]) {
|
||||
let range = NSRange::from(range);
|
||||
// No need to verify the length of the range here,
|
||||
// `replaceBytesInRange:` just zero-fills if out of bounds.
|
||||
let ptr: *const c_void = bytes.as_ptr().cast();
|
||||
unsafe { self.replace_raw(range, ptr, bytes.len()) }
|
||||
}
|
||||
|
||||
pub fn set_bytes(&mut self, bytes: &[u8]) {
|
||||
let len = self.len();
|
||||
self.replace_range(0..len, bytes);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSMutableData {
|
||||
type Ownership = Shared;
|
||||
type Output = NSData;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSMutableData {
|
||||
type Output = NSMutableData;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSMutableData {
|
||||
type Owned = Id<NSMutableData, Owned>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.mutable_copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for NSMutableData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for NSMutableData {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.bytes_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: SliceIndex<[u8]>> Index<I> for NSMutableData {
|
||||
type Output = I::Output;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: I) -> &Self::Output {
|
||||
Index::index(self.bytes(), index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: SliceIndex<[u8]>> IndexMut<I> for NSMutableData {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||
IndexMut::index_mut(self.bytes_mut(), index)
|
||||
}
|
||||
}
|
||||
|
||||
// impl FromIterator<u8> for Id<NSMutableData, Owned> {
|
||||
// fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
|
||||
// let iter = iter.into_iter();
|
||||
// let (lower, _) = iter.size_hint();
|
||||
// let data = Self::with_capacity(lower);
|
||||
// for item in iter {
|
||||
// data.push(item);
|
||||
// }
|
||||
// data
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Extend<u8> for NSMutableData {
|
||||
/// You should use [`extend_from_slice`] whenever possible, it is more
|
||||
/// performant.
|
||||
///
|
||||
/// [`extend_from_slice`]: Self::extend_from_slice
|
||||
fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
|
||||
let iterator = iter.into_iter();
|
||||
iterator.for_each(move |item| self.push(item));
|
||||
}
|
||||
}
|
||||
|
||||
// Vec also has this impl
|
||||
impl<'a> Extend<&'a u8> for NSMutableData {
|
||||
fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
|
||||
let iterator = iter.into_iter();
|
||||
iterator.for_each(move |item| self.push(*item));
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for NSMutableData {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
||||
self.extend_from_slice(buf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSMutableData {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSMutableData {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a NSMutableData {
|
||||
type Item = &'a u8;
|
||||
type IntoIter = core::slice::Iter<'a, u8>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.bytes().iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut NSMutableData {
|
||||
type Item = &'a mut u8;
|
||||
type IntoIter = core::slice::IterMut<'a, u8>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.bytes_mut().iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::Object;
|
||||
|
||||
#[test]
|
||||
fn test_bytes_mut() {
|
||||
let mut data = NSMutableData::with_bytes(&[7, 16]);
|
||||
data.bytes_mut()[0] = 3;
|
||||
assert_eq!(data.bytes(), [3, 16]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_len() {
|
||||
let mut data = NSMutableData::with_bytes(&[7, 16]);
|
||||
data.set_len(4);
|
||||
assert_eq!(data.len(), 4);
|
||||
assert_eq!(data.bytes(), [7, 16, 0, 0]);
|
||||
|
||||
data.set_len(1);
|
||||
assert_eq!(data.len(), 1);
|
||||
assert_eq!(data.bytes(), [7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append() {
|
||||
let mut data = NSMutableData::with_bytes(&[7, 16]);
|
||||
data.extend_from_slice(&[3, 52]);
|
||||
assert_eq!(data.len(), 4);
|
||||
assert_eq!(data.bytes(), [7, 16, 3, 52]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace() {
|
||||
let mut data = NSMutableData::with_bytes(&[7, 16]);
|
||||
data.replace_range(0..0, &[3]);
|
||||
assert_eq!(data.bytes(), [3, 7, 16]);
|
||||
|
||||
data.replace_range(1..2, &[52, 13]);
|
||||
assert_eq!(data.bytes(), [3, 52, 13, 16]);
|
||||
|
||||
data.replace_range(2..4, &[6]);
|
||||
assert_eq!(data.bytes(), [3, 52, 6]);
|
||||
|
||||
data.set_bytes(&[8, 17]);
|
||||
assert_eq!(data.bytes(), [8, 17]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_data() {
|
||||
let data = NSData::with_bytes(&[1, 2]);
|
||||
let mut_data = NSMutableData::from_data(&data);
|
||||
assert_eq!(&*data, &**mut_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_capacity() {
|
||||
let mut data = NSMutableData::with_capacity(5);
|
||||
assert_eq!(data.bytes(), &[]);
|
||||
data.extend_from_slice(&[1, 2, 3, 4, 5]);
|
||||
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
|
||||
data.extend_from_slice(&[6, 7]);
|
||||
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend() {
|
||||
let mut data = NSMutableData::with_bytes(&[1, 2]);
|
||||
data.extend(3..=5);
|
||||
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
|
||||
data.extend(&*NSData::with_bytes(&[6, 7]));
|
||||
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_ref_borrow() {
|
||||
use core::borrow::{Borrow, BorrowMut};
|
||||
|
||||
fn impls_borrow<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
|
||||
fn impls_borrow_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
|
||||
|
||||
let mut obj = NSMutableData::new();
|
||||
impls_borrow::<Id<NSMutableData, Owned>, NSMutableData>(&obj);
|
||||
impls_borrow_mut::<Id<NSMutableData, Owned>, NSMutableData>(&mut obj);
|
||||
|
||||
impls_borrow::<NSMutableData, NSMutableData>(&obj);
|
||||
impls_borrow_mut::<NSMutableData, NSMutableData>(&mut obj);
|
||||
impls_borrow::<NSMutableData, NSData>(&obj);
|
||||
impls_borrow_mut::<NSMutableData, NSData>(&mut obj);
|
||||
impls_borrow::<NSMutableData, NSObject>(&obj);
|
||||
impls_borrow_mut::<NSMutableData, NSObject>(&mut obj);
|
||||
impls_borrow::<NSMutableData, Object>(&obj);
|
||||
impls_borrow_mut::<NSMutableData, Object>(&mut obj);
|
||||
|
||||
impls_borrow::<NSData, NSData>(&obj);
|
||||
impls_borrow_mut::<NSData, NSData>(&mut obj);
|
||||
impls_borrow::<NSData, NSObject>(&obj);
|
||||
impls_borrow_mut::<NSData, NSObject>(&mut obj);
|
||||
impls_borrow::<NSData, Object>(&obj);
|
||||
impls_borrow_mut::<NSData, Object>(&mut obj);
|
||||
|
||||
fn impls_as_ref<T: AsRef<U> + ?Sized, U: ?Sized>(_: &T) {}
|
||||
fn impls_as_mut<T: AsMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
|
||||
|
||||
impls_as_ref::<NSMutableData, [u8]>(&obj);
|
||||
impls_as_mut::<NSMutableData, [u8]>(&mut obj);
|
||||
impls_as_ref::<NSData, [u8]>(&obj);
|
||||
|
||||
let obj: &mut NSMutableData = &mut obj;
|
||||
let _: &[u8] = obj.as_ref();
|
||||
let _: &mut [u8] = obj.as_mut();
|
||||
|
||||
let obj: &mut NSData = obj;
|
||||
let _: &[u8] = obj.as_ref();
|
||||
}
|
||||
}
|
||||
399
third-party/vendor/objc2/src/foundation/mutable_dictionary.rs
vendored
Normal file
399
third-party/vendor/objc2/src/foundation/mutable_dictionary.rs
vendored
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Index, IndexMut};
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use core::ptr;
|
||||
|
||||
use super::{NSArray, NSCopying, NSDictionary, NSFastEnumeration, NSObject};
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared};
|
||||
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send_id, Message};
|
||||
|
||||
__inner_extern_class!(
|
||||
/// A mutable collection of objects associated with unique keys.
|
||||
///
|
||||
/// See the documentation for [`NSDictionary`] and/or [Apple's
|
||||
/// documentation][apple-doc] for more information.
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutabledictionary?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableDictionary<K: Message, V: Message> {
|
||||
key: PhantomData<Id<K, Shared>>,
|
||||
obj: PhantomData<Id<V, Owned>>,
|
||||
}
|
||||
|
||||
unsafe impl<K: Message, V: Message> ClassType for NSMutableDictionary<K, V> {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSDictionary<K, V>;
|
||||
}
|
||||
);
|
||||
|
||||
// Same as `NSDictionary<K, V>`
|
||||
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSMutableDictionary<K, V> {}
|
||||
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSMutableDictionary<K, V> {}
|
||||
|
||||
// Same as `NSDictionary<K, V>`
|
||||
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSMutableDictionary<K, V> {}
|
||||
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe
|
||||
for NSMutableDictionary<K, V>
|
||||
{
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl<K: Message, V: Message> NSMutableDictionary<K, V> {
|
||||
/// Creates an empty [`NSMutableDictionary`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let dict = NSMutableDictionary::<NSString, NSObject>::new();
|
||||
/// ```
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
// SAFETY:
|
||||
// Mutable dictionaries are always unique, so it's safe to return
|
||||
// `Id<Self, Owned>`
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
#[sel(setDictionary:)]
|
||||
fn set_dictionary(&mut self, dict: &NSDictionary<K, V>);
|
||||
|
||||
/// Creates an [`NSMutableDictionary`] from a slice of keys and a
|
||||
/// vector of values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSNumber, NSObject};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let dict = NSMutableDictionary::from_keys_and_objects(
|
||||
/// &[
|
||||
/// &*NSNumber::new_i32(1),
|
||||
/// &*NSNumber::new_i32(2),
|
||||
/// &*NSNumber::new_i32(3),
|
||||
/// ],
|
||||
/// vec![NSObject::new(), NSObject::new(), NSObject::new()],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Owned>
|
||||
where
|
||||
T: NSCopying<Output = K>,
|
||||
{
|
||||
let mut dict = NSMutableDictionary::new();
|
||||
dict.set_dictionary(&*NSDictionary::from_keys_and_objects(keys, vals));
|
||||
dict
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value corresponding to the key.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// println!("{:?}", dict.get_mut(ns_string!("one")));
|
||||
/// ```
|
||||
#[doc(alias = "objectForKey:")]
|
||||
#[sel(objectForKey:)]
|
||||
pub fn get_mut(&mut self, key: &K) -> Option<&mut V>;
|
||||
|
||||
#[sel(getObjects:andKeys:)]
|
||||
unsafe fn get_objects_and_keys(&self, objects: *mut &mut V, keys: *mut &K);
|
||||
|
||||
/// Returns a vector of mutable references to the values in the dictionary.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// for val in dict.values_mut() {
|
||||
/// println!("{:?}", val);
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(alias = "getObjects:andKeys:")]
|
||||
pub fn values_mut(&mut self) -> Vec<&mut V> {
|
||||
let len = self.len();
|
||||
let mut vals: Vec<&mut V> = Vec::with_capacity(len);
|
||||
// SAFETY: `vals` is not null
|
||||
unsafe {
|
||||
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
|
||||
vals.set_len(len);
|
||||
}
|
||||
vals
|
||||
}
|
||||
|
||||
#[sel(setObject:forKey:)]
|
||||
fn set_object_for_key(&mut self, object: &V, key: &K);
|
||||
|
||||
/// Inserts a key-value pair into the dictionary.
|
||||
///
|
||||
/// If the dictionary did not have this key present, None is returned.
|
||||
/// If the dictionary did have this key present, the value is updated,
|
||||
/// and the old value is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// ```
|
||||
#[doc(alias = "setObject:forKey:")]
|
||||
pub fn insert(&mut self, key: Id<K, Shared>, value: Id<V, Owned>) -> Option<Id<V, Owned>> {
|
||||
// SAFETY:
|
||||
// `obj` is a reference to a value in the dictionary so it's safe
|
||||
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
|
||||
let obj = self.get(&*key).map(|obj| unsafe {
|
||||
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
|
||||
});
|
||||
self.set_object_for_key(&*value, &*key);
|
||||
obj
|
||||
}
|
||||
|
||||
#[sel(removeObjectForKey:)]
|
||||
fn remove_object_for_key(&mut self, key: &K);
|
||||
|
||||
/// Removes a key from the dictionary, returning the value at the key
|
||||
/// if the key was previously in the dictionary.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// dict.remove(ns_string!("one"));
|
||||
/// assert!(dict.is_empty());
|
||||
/// ```
|
||||
#[doc(alias = "removeObjectForKey:")]
|
||||
pub fn remove(&mut self, key: &K) -> Option<Id<V, Owned>> {
|
||||
// SAFETY:
|
||||
// `obj` is a reference to a value in the dictionary so it's safe
|
||||
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
|
||||
let obj = self.get(key).map(|obj| unsafe {
|
||||
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
|
||||
});
|
||||
self.remove_object_for_key(key);
|
||||
obj
|
||||
}
|
||||
|
||||
/// Clears the dictionary, removing all key-value pairs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// dict.clear();
|
||||
/// assert!(dict.is_empty());
|
||||
/// ```
|
||||
#[doc(alias = "removeAllObjects")]
|
||||
#[sel(removeAllObjects)]
|
||||
pub fn clear(&mut self);
|
||||
|
||||
/// Returns an [`NSArray`] containing the dictionary's values,
|
||||
/// consuming the dictionary.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut dict = NSMutableDictionary::new();
|
||||
/// dict.insert(NSString::from_str("one"), NSObject::new());
|
||||
/// let array = NSMutableDictionary::into_values_array(dict);
|
||||
/// println!("{:?}", array);
|
||||
/// ```
|
||||
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
|
||||
unsafe { msg_send_id![&dict, allValues] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSMutableDictionary<K, V> {
|
||||
type Item = K;
|
||||
}
|
||||
|
||||
impl<'a, K: Message, V: Message> Index<&'a K> for NSMutableDictionary<K, V> {
|
||||
type Output = V;
|
||||
|
||||
fn index<'s>(&'s self, index: &'a K) -> &'s V {
|
||||
self.get(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Message, V: Message> IndexMut<&'a K> for NSMutableDictionary<K, V> {
|
||||
fn index_mut<'s>(&'s mut self, index: &'a K) -> &'s mut V {
|
||||
self.get_mut(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Message, V: Message> DefaultId for NSMutableDictionary<K, V> {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSMutableDictionary<K, V> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
|
||||
use crate::{
|
||||
foundation::{NSNumber, NSString},
|
||||
rc::{RcTestObject, ThreadTestData},
|
||||
};
|
||||
|
||||
fn sample_dict() -> Id<NSMutableDictionary<NSNumber, NSObject>, Owned> {
|
||||
NSMutableDictionary::from_keys_and_objects(
|
||||
&[
|
||||
&*NSNumber::new_i32(1),
|
||||
&*NSNumber::new_i32(2),
|
||||
&*NSNumber::new_i32(3),
|
||||
],
|
||||
vec![NSObject::new(), NSObject::new(), NSObject::new()],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let dict = NSMutableDictionary::<NSString, NSObject>::new();
|
||||
assert!(dict.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mut() {
|
||||
let mut dict = sample_dict();
|
||||
assert!(dict.get_mut(&NSNumber::new_i32(1)).is_some());
|
||||
assert!(dict.get_mut(&NSNumber::new_i32(2)).is_some());
|
||||
assert!(dict.get_mut(&NSNumber::new_i32(4)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values_mut() {
|
||||
let mut dict = sample_dict();
|
||||
let vec = dict.values_mut();
|
||||
assert_eq!(vec.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
let mut dict = NSMutableDictionary::new();
|
||||
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_none());
|
||||
assert!(dict.insert(NSNumber::new_i32(2), NSObject::new()).is_none());
|
||||
assert!(dict.insert(NSNumber::new_i32(3), NSObject::new()).is_none());
|
||||
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_some());
|
||||
assert_eq!(dict.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_retain_release() {
|
||||
let mut dict = NSMutableDictionary::new();
|
||||
dict.insert(NSNumber::new_i32(1), RcTestObject::new());
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let old = dict.insert(NSNumber::new_i32(1), RcTestObject::new());
|
||||
expected.alloc += 1;
|
||||
expected.init += 1;
|
||||
expected.retain += 2;
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
|
||||
drop(old);
|
||||
expected.release += 1;
|
||||
expected.dealloc += 1;
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let mut dict = sample_dict();
|
||||
assert_eq!(dict.len(), 3);
|
||||
assert!(dict.remove(&NSNumber::new_i32(1)).is_some());
|
||||
assert!(dict.remove(&NSNumber::new_i32(2)).is_some());
|
||||
assert!(dict.remove(&NSNumber::new_i32(1)).is_none());
|
||||
assert!(dict.remove(&NSNumber::new_i32(4)).is_none());
|
||||
assert_eq!(dict.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let mut dict = sample_dict();
|
||||
assert_eq!(dict.len(), 3);
|
||||
|
||||
dict.clear();
|
||||
assert!(dict.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_clear_release_dealloc() {
|
||||
let mut dict = NSMutableDictionary::new();
|
||||
for i in 0..4 {
|
||||
dict.insert(NSNumber::new_i32(i), RcTestObject::new());
|
||||
}
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let _obj = dict.remove(&NSNumber::new_i32(1));
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(dict.len(), 3);
|
||||
|
||||
let _obj = dict.remove(&NSNumber::new_i32(2));
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(dict.len(), 2);
|
||||
|
||||
dict.clear();
|
||||
expected.release += 2;
|
||||
expected.dealloc += 2;
|
||||
expected.assert_current();
|
||||
assert_eq!(dict.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_values_array() {
|
||||
let dict = sample_dict();
|
||||
let array = NSMutableDictionary::into_values_array(dict);
|
||||
assert_eq!(array.len(), 3);
|
||||
}
|
||||
}
|
||||
368
third-party/vendor/objc2/src/foundation/mutable_set.rs
vendored
Normal file
368
third-party/vendor/objc2/src/foundation/mutable_set.rs
vendored
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::set::with_objects;
|
||||
use super::{NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying, NSObject, NSSet};
|
||||
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
|
||||
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send_id};
|
||||
|
||||
__inner_extern_class!(
|
||||
/// A growable unordered collection of unique objects.
|
||||
///
|
||||
/// See the documentation for [`NSSet`] and/or [Apple's
|
||||
/// documentation][apple-doc] for more information.
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutableset?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableSet<T: Message, O: Ownership = Owned> {
|
||||
p: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableSet<T, O> {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSSet<T, O>;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Same as NSSet<T, O>
|
||||
unsafe impl<T: Message + Sync + Send> Sync for NSMutableSet<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync + Send> Send for NSMutableSet<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync> Sync for NSMutableSet<T, Owned> {}
|
||||
unsafe impl<T: Message + Send> Send for NSMutableSet<T, Owned> {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl<T: Message, O: Ownership> NSMutableSet<T, O> {
|
||||
/// Creates an empty [`NSMutableSet`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set = NSMutableSet::<NSString>::new();
|
||||
/// ```
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
// SAFETY:
|
||||
// Same as `NSSet::new`, except mutable sets are always unique.
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// Creates an [`NSMutableSet`] from a vector.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
|
||||
/// let set = NSMutableSet::from_vec(strs);
|
||||
/// ```
|
||||
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
|
||||
// SAFETY:
|
||||
// We always return `Id<NSMutableSet<T, O>, Owned>` because mutable
|
||||
// sets are always unique.
|
||||
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
|
||||
}
|
||||
|
||||
/// Clears the set, removing all values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut set = NSMutableSet::new();
|
||||
/// set.insert(NSString::from_str("one"));
|
||||
/// set.clear();
|
||||
/// assert!(set.is_empty());
|
||||
/// ```
|
||||
#[doc(alias = "removeAllObjects")]
|
||||
#[sel(removeAllObjects)]
|
||||
pub fn clear(&mut self);
|
||||
|
||||
/// Returns a [`Vec`] containing the set's elements, consuming the set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSMutableString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = vec![
|
||||
/// NSMutableString::from_str("one"),
|
||||
/// NSMutableString::from_str("two"),
|
||||
/// NSMutableString::from_str("three"),
|
||||
/// ];
|
||||
/// let set = NSMutableSet::from_vec(strs);
|
||||
/// let vec = NSMutableSet::into_vec(set);
|
||||
/// assert_eq!(vec.len(), 3);
|
||||
/// ```
|
||||
pub fn into_vec(set: Id<Self, Owned>) -> Vec<Id<T, O>> {
|
||||
set.into_iter()
|
||||
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> NSMutableSet<T, Shared> {
|
||||
/// Creates an [`NSMutableSet`] from a slice.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSMutableSet::from_slice(&strs);
|
||||
/// ```
|
||||
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
|
||||
// SAFETY:
|
||||
// Taking `&T` would not be sound, since the `&T` could come from
|
||||
// an `Id<T, Owned>` that would now no longer be owned!
|
||||
//
|
||||
// We always return `Id<NSMutableSet<T, Shared>, Owned>` because
|
||||
// the elements are shared and mutable sets are always unique.
|
||||
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
|
||||
}
|
||||
}
|
||||
|
||||
// We're explicit about `T` being `PartialEq` for these methods because the
|
||||
// set compares the input value with elements in the set
|
||||
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
|
||||
unsafe impl<T: Message + PartialEq, O: Ownership> NSMutableSet<T, O> {
|
||||
#[sel(addObject:)]
|
||||
fn add_object(&mut self, value: &T);
|
||||
|
||||
/// Adds a value to the set. Returns whether the value was
|
||||
/// newly inserted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut set = NSMutableSet::new();
|
||||
///
|
||||
/// assert_eq!(set.insert(NSString::from_str("one")), true);
|
||||
/// assert_eq!(set.insert(NSString::from_str("one")), false);
|
||||
/// assert_eq!(set.len(), 1);
|
||||
/// ```
|
||||
#[doc(alias = "addObject:")]
|
||||
pub fn insert(&mut self, value: Id<T, O>) -> bool {
|
||||
// SAFETY:
|
||||
// We take `Id<T, O>` instead of `&T` because `&T` could be a
|
||||
// reference to an owned object which would cause us to have a copy
|
||||
// of an owned object in our set. By taking `Id<T, O>`, we force the
|
||||
// caller to transfer ownership of the value to us, making it safe
|
||||
// to insert the owned object into the set.
|
||||
let contains_value = self.contains(&value);
|
||||
self.add_object(&*value);
|
||||
!contains_value
|
||||
}
|
||||
|
||||
#[sel(removeObject:)]
|
||||
fn remove_object(&mut self, value: &T);
|
||||
|
||||
/// Removes a value from the set. Returns whether the value was present
|
||||
/// in the set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableSet, NSString};
|
||||
/// use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let mut set = NSMutableSet::new();
|
||||
///
|
||||
/// set.insert(NSString::from_str("one"));
|
||||
/// assert_eq!(set.remove(ns_string!("one")), true);
|
||||
/// assert_eq!(set.remove(ns_string!("one")), false);
|
||||
/// ```
|
||||
#[doc(alias = "removeObject:")]
|
||||
pub fn remove(&mut self, value: &T) -> bool {
|
||||
let contains_value = self.contains(value);
|
||||
self.remove_object(value);
|
||||
contains_value
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl<T: Message> NSCopying for NSMutableSet<T, Shared> {
|
||||
type Ownership = Shared;
|
||||
type Output = NSSet<T, Shared>;
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> NSMutableCopying for NSMutableSet<T, Shared> {
|
||||
type Output = NSMutableSet<T, Shared>;
|
||||
}
|
||||
|
||||
impl<T: Message> alloc::borrow::ToOwned for NSMutableSet<T, Shared> {
|
||||
type Owned = Id<NSMutableSet<T, Shared>, Owned>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.mutable_copy()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableSet<T, O> {
|
||||
type Item = T;
|
||||
}
|
||||
|
||||
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableSet<T, O> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = NSFastEnumerator<'a, NSMutableSet<T, O>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_fast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message + PartialEq, O: Ownership> Extend<Id<T, O>> for NSMutableSet<T, O> {
|
||||
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
|
||||
for item in iter {
|
||||
self.insert(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> DefaultId for NSMutableSet<T, O> {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableSet<T, O> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::{NSMutableString, NSString};
|
||||
use crate::ns_string;
|
||||
use crate::rc::{RcTestObject, ThreadTestData};
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
let mut set = NSMutableSet::new();
|
||||
assert!(set.is_empty());
|
||||
|
||||
assert!(set.insert(NSString::from_str("one")));
|
||||
assert!(!set.insert(NSString::from_str("one")));
|
||||
assert!(set.insert(NSString::from_str("two")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
let mut set = NSMutableSet::from_slice(&strs);
|
||||
|
||||
assert!(set.remove(ns_string!("one")));
|
||||
assert!(!set.remove(ns_string!("one")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
let mut set = NSMutableSet::from_slice(&strs);
|
||||
assert_eq!(set.len(), 3);
|
||||
|
||||
set.clear();
|
||||
assert!(set.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_vec() {
|
||||
let strs = vec![
|
||||
NSMutableString::from_str("one"),
|
||||
NSMutableString::from_str("two"),
|
||||
NSMutableString::from_str("three"),
|
||||
];
|
||||
let set = NSMutableSet::from_vec(strs);
|
||||
|
||||
let mut vec = NSMutableSet::into_vec(set);
|
||||
for str in vec.iter_mut() {
|
||||
str.push_nsstring(ns_string!(" times zero is zero"));
|
||||
}
|
||||
|
||||
assert_eq!(vec.len(), 3);
|
||||
let suffix = ns_string!("zero");
|
||||
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend() {
|
||||
let mut set = NSMutableSet::new();
|
||||
assert!(set.is_empty());
|
||||
|
||||
set.extend(["one", "two", "three"].map(NSString::from_str));
|
||||
assert_eq!(set.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mutable_copy() {
|
||||
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
let mut set2 = set1.mutable_copy();
|
||||
set2.insert(NSString::from_str("four"));
|
||||
|
||||
assert!(set1.is_subset(&set2));
|
||||
assert_ne!(set1.mutable_copy(), set2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_retain_release() {
|
||||
let mut set = NSMutableSet::new();
|
||||
let obj1 = RcTestObject::new();
|
||||
let obj2 = RcTestObject::new();
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
set.insert(obj1);
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(set.len(), 1);
|
||||
assert_eq!(set.get_any(), set.get_any());
|
||||
|
||||
set.insert(obj2);
|
||||
expected.retain += 1;
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
assert_eq!(set.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_release_dealloc() {
|
||||
let mut set = NSMutableSet::new();
|
||||
for _ in 0..4 {
|
||||
set.insert(RcTestObject::new());
|
||||
}
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
set.clear();
|
||||
expected.release += 4;
|
||||
expected.dealloc += 4;
|
||||
expected.assert_current();
|
||||
assert_eq!(set.len(), 0);
|
||||
}
|
||||
}
|
||||
232
third-party/vendor/objc2/src/foundation/mutable_string.rs
vendored
Normal file
232
third-party/vendor/objc2/src/foundation/mutable_string.rs
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
use core::cmp;
|
||||
use core::fmt;
|
||||
use core::ops::AddAssign;
|
||||
use core::str;
|
||||
|
||||
use super::{NSCopying, NSMutableCopying, NSObject, NSString};
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A dynamic plain-text Unicode string object.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutablestring?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSMutableString;
|
||||
|
||||
unsafe impl ClassType for NSMutableString {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSString;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
/// Creating mutable strings.
|
||||
unsafe impl NSMutableString {
|
||||
/// Construct an empty [`NSMutableString`].
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// Creates a new [`NSMutableString`] by copying the given string slice.
|
||||
#[doc(alias = "initWithBytes:length:encoding:")]
|
||||
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
|
||||
pub fn from_str(string: &str) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = super::string::from_str(Self::class(), string);
|
||||
Id::new(obj.cast()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`NSMutableString`] from the given [`NSString`].
|
||||
#[doc(alias = "initWithString:")]
|
||||
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithString: string]
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "initWithCapacity:")]
|
||||
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithCapacity: capacity]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutating strings.
|
||||
unsafe impl NSMutableString {
|
||||
/// Appends the given [`NSString`] onto the end of this.
|
||||
#[doc(alias = "appendString:")]
|
||||
// SAFETY: The string is not nil
|
||||
#[sel(appendString:)]
|
||||
pub fn push_nsstring(&mut self, nsstring: &NSString);
|
||||
|
||||
/// Replaces the entire string.
|
||||
#[doc(alias = "setString:")]
|
||||
// SAFETY: The string is not nil
|
||||
#[sel(setString:)]
|
||||
pub fn replace(&mut self, nsstring: &NSString);
|
||||
|
||||
// TODO:
|
||||
// - deleteCharactersInRange:
|
||||
// - replaceCharactersInRange:withString:
|
||||
// - insertString:atIndex:
|
||||
// Figure out how these work on character boundaries
|
||||
}
|
||||
);
|
||||
|
||||
impl DefaultId for NSMutableString {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSCopying for NSMutableString {
|
||||
type Ownership = Shared;
|
||||
type Output = NSString;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSMutableString {
|
||||
type Output = NSMutableString;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSMutableString {
|
||||
type Owned = Id<NSMutableString, Owned>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.mutable_copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&NSString> for NSMutableString {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: &NSString) {
|
||||
self.push_nsstring(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NSString> for NSMutableString {
|
||||
#[inline]
|
||||
fn eq(&self, other: &NSString) -> bool {
|
||||
PartialEq::eq(&**self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NSMutableString> for NSString {
|
||||
#[inline]
|
||||
fn eq(&self, other: &NSMutableString) -> bool {
|
||||
PartialEq::eq(self, &**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for NSMutableString {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
PartialOrd::partial_cmp(&**self, &**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<NSString> for NSMutableString {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &NSString) -> Option<cmp::Ordering> {
|
||||
PartialOrd::partial_cmp(&**self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<NSMutableString> for NSString {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &NSMutableString) -> Option<cmp::Ordering> {
|
||||
PartialOrd::partial_cmp(self, &**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NSMutableString {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
Ord::cmp(&**self, &**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for NSMutableString {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
let nsstring = NSString::from_str(s);
|
||||
self.push_nsstring(&nsstring);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NSMutableString {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSMutableString {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn display_debug() {
|
||||
let s = NSMutableString::from_str("test\"123");
|
||||
assert_eq!(format!("{}", s), "test\"123");
|
||||
assert_eq!(format!("{:?}", s), r#""test\"123""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_nsstring() {
|
||||
let s = NSString::from_str("abc");
|
||||
let s = NSMutableString::from_nsstring(&s);
|
||||
assert_eq!(&s.to_string(), "abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_nsstring() {
|
||||
let mut s = NSMutableString::from_str("abc");
|
||||
s.push_nsstring(&NSString::from_str("def"));
|
||||
*s += &NSString::from_str("ghi");
|
||||
assert_eq!(&s.to_string(), "abcdefghi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace() {
|
||||
let mut s = NSMutableString::from_str("abc");
|
||||
s.replace(&NSString::from_str("def"));
|
||||
assert_eq!(&s.to_string(), "def");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_capacity() {
|
||||
let mut s = NSMutableString::with_capacity(3);
|
||||
*s += &NSString::from_str("abc");
|
||||
*s += &NSString::from_str("def");
|
||||
assert_eq!(&s.to_string(), "abcdef");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy() {
|
||||
let s1 = NSMutableString::from_str("abc");
|
||||
let s2 = s1.copy();
|
||||
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s2).cast());
|
||||
assert!(s2.is_kind_of::<NSString>());
|
||||
|
||||
let s3 = s1.mutable_copy();
|
||||
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
|
||||
assert!(s3.is_kind_of::<NSMutableString>());
|
||||
}
|
||||
}
|
||||
414
third-party/vendor/objc2/src/foundation/number.rs
vendored
Normal file
414
third-party/vendor/objc2/src/foundation/number.rs
vendored
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
use core::cmp::Ordering;
|
||||
use core::fmt;
|
||||
use core::hash;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use std::os::raw::{
|
||||
c_char, c_double, c_float, c_int, c_longlong, c_short, c_uchar, c_uint, c_ulonglong, c_ushort,
|
||||
};
|
||||
|
||||
use super::{
|
||||
CGFloat, NSComparisonResult, NSCopying, NSInteger, NSObject, NSString, NSUInteger, NSValue,
|
||||
};
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encoding};
|
||||
|
||||
extern_class!(
|
||||
/// An object wrapper for primitive scalars.
|
||||
///
|
||||
/// This is the Objective-C equivalant of a Rust enum containing the
|
||||
/// common scalar types `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`,
|
||||
/// `u64`, `f32`, `f64` and the two C types `c_long` and `c_ulong`.
|
||||
///
|
||||
/// All accessor methods are safe, though they may return unexpected
|
||||
/// results if the number was not created from said type. Consult [Apple's
|
||||
/// documentation][apple-doc] for details.
|
||||
///
|
||||
/// Note that due to limitations in Objective-C type encodings, it is not
|
||||
/// possible to distinguish between an `NSNumber` created from [`bool`],
|
||||
/// and one created from an [`i8`]/[`u8`]. You should use the getter
|
||||
/// methods that fit your use-case instead!
|
||||
///
|
||||
/// This does not implement [`Eq`] nor [`Ord`], since it may contain a
|
||||
/// floating point value. Beware that the implementation of [`PartialEq`]
|
||||
/// and [`PartialOrd`] does not properly handle NaNs either. Compare
|
||||
/// [`NSNumber::encoding`] with [`Encoding::Float`] or
|
||||
/// [`Encoding::Double`], and use [`NSNumber::as_f32`] or
|
||||
/// [`NSNumber::as_f64`] to get the desired floating point value directly.
|
||||
///
|
||||
/// See [Apple's documentation][apple-doc] for more information.
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
|
||||
pub struct NSNumber;
|
||||
|
||||
unsafe impl ClassType for NSNumber {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSValue;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: `NSNumber` is just a wrapper around an integer/float/bool, and it
|
||||
// is immutable.
|
||||
unsafe impl Sync for NSNumber {}
|
||||
unsafe impl Send for NSNumber {}
|
||||
|
||||
impl UnwindSafe for NSNumber {}
|
||||
impl RefUnwindSafe for NSNumber {}
|
||||
|
||||
macro_rules! def_new_fn {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
($fn_name:ident($fn_inp:ty); $method_name:ident: $method_inp:ty),
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $fn_name(val: $fn_inp) -> Id<Self, Shared> {
|
||||
let val = val as $method_inp;
|
||||
unsafe {
|
||||
msg_send_id![Self::class(), $method_name: val]
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
/// Creation methods.
|
||||
impl NSNumber {
|
||||
def_new_fn! {
|
||||
(new_bool(bool); numberWithBool: bool),
|
||||
(new_i8(i8); numberWithChar: c_char),
|
||||
(new_u8(u8); numberWithUnsignedChar: c_uchar),
|
||||
(new_i16(i16); numberWithShort: c_short),
|
||||
(new_u16(u16); numberWithUnsignedShort: c_ushort),
|
||||
(new_i32(i32); numberWithInt: c_int),
|
||||
(new_u32(u32); numberWithUnsignedInt: c_uint),
|
||||
(new_i64(i64); numberWithLongLong: c_longlong),
|
||||
(new_u64(u64); numberWithUnsignedLongLong: c_ulonglong),
|
||||
(new_isize(isize); numberWithInteger: NSInteger),
|
||||
(new_usize(usize); numberWithUnsignedInteger: NSUInteger),
|
||||
(new_f32(f32); numberWithFloat: c_float),
|
||||
(new_f64(f64); numberWithDouble: c_double),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_cgfloat(val: CGFloat) -> Id<Self, Shared> {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
Self::new_f64(val)
|
||||
}
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
{
|
||||
Self::new_f32(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! def_get_fn {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
($fn_name:ident -> $fn_ret:ty; $method_name:ident -> $method_ret:ty),
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $fn_name(&self) -> $fn_ret {
|
||||
let ret: $method_ret = unsafe { msg_send![self, $method_name] };
|
||||
ret as $fn_ret
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
/// Getter methods.
|
||||
impl NSNumber {
|
||||
def_get_fn! {
|
||||
(as_bool -> bool; boolValue -> bool),
|
||||
(as_i8 -> i8; charValue -> c_char),
|
||||
(as_u8 -> u8; unsignedCharValue -> c_uchar),
|
||||
(as_i16 -> i16; shortValue -> c_short),
|
||||
(as_u16 -> u16; unsignedShortValue -> c_ushort),
|
||||
(as_i32 -> i32; intValue -> c_int),
|
||||
(as_u32 -> u32; unsignedIntValue -> c_uint),
|
||||
// TODO: Getter methods for `long` and `unsigned long`
|
||||
(as_i64 -> i64; longLongValue -> c_longlong),
|
||||
(as_u64 -> u64; unsignedLongLongValue -> c_ulonglong),
|
||||
(as_isize -> isize; integerValue -> NSInteger),
|
||||
(as_usize -> usize; unsignedIntegerValue -> NSUInteger),
|
||||
(as_f32 -> f32; floatValue -> c_float),
|
||||
(as_f64 -> f64; doubleValue -> c_double),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cgfloat(&self) -> CGFloat {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
self.as_f64()
|
||||
}
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
{
|
||||
self.as_f32()
|
||||
}
|
||||
}
|
||||
|
||||
/// The Objective-C encoding of this `NSNumber`.
|
||||
///
|
||||
/// This is guaranteed to return one of:
|
||||
/// - [`Encoding::Char`]
|
||||
/// - [`Encoding::UChar`]
|
||||
/// - [`Encoding::Short`]
|
||||
/// - [`Encoding::UShort`]
|
||||
/// - [`Encoding::Int`]
|
||||
/// - [`Encoding::UInt`]
|
||||
/// - [`Encoding::Long`]
|
||||
/// - [`Encoding::ULong`]
|
||||
/// - [`Encoding::LongLong`]
|
||||
/// - [`Encoding::ULongLong`]
|
||||
/// - [`Encoding::Float`]
|
||||
/// - [`Encoding::Double`]
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Convert an `NSNumber` to/from an enumeration describing the different
|
||||
/// number properties.
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::Encoding;
|
||||
/// use objc2::foundation::NSNumber;
|
||||
/// use objc2::rc::{Id, Shared};
|
||||
///
|
||||
/// // Note: `bool` would convert to either `Signed` or `Unsigned`,
|
||||
/// // depending on platform
|
||||
/// #[derive(Copy, Clone)]
|
||||
/// pub enum Number {
|
||||
/// Signed(i64),
|
||||
/// Unsigned(u64),
|
||||
/// Floating(f64),
|
||||
/// }
|
||||
///
|
||||
/// impl Number {
|
||||
/// fn into_nsnumber(self) -> Id<NSNumber, Shared> {
|
||||
/// match self {
|
||||
/// Self::Signed(val) => NSNumber::new_i64(val),
|
||||
/// Self::Unsigned(val) => NSNumber::new_u64(val),
|
||||
/// Self::Floating(val) => NSNumber::new_f64(val),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<&NSNumber> for Number {
|
||||
/// fn from(n: &NSNumber) -> Self {
|
||||
/// match n.encoding() {
|
||||
/// Encoding::Char
|
||||
/// | Encoding::Short
|
||||
/// | Encoding::Int
|
||||
/// | Encoding::Long
|
||||
/// | Encoding::LongLong => Self::Signed(n.as_i64()),
|
||||
/// Encoding::UChar
|
||||
/// | Encoding::UShort
|
||||
/// | Encoding::UInt
|
||||
/// | Encoding::ULong
|
||||
/// | Encoding::ULongLong => Self::Unsigned(n.as_u64()),
|
||||
/// Encoding::Float
|
||||
/// | Encoding::Double => Self::Floating(n.as_f64()),
|
||||
/// _ => unreachable!(),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn encoding(&self) -> Encoding {
|
||||
// Use NSValue::encoding
|
||||
let enc = (**self)
|
||||
.encoding()
|
||||
.expect("NSNumber must have an encoding!");
|
||||
|
||||
// Guaranteed under "Subclassing Notes"
|
||||
// <https://developer.apple.com/documentation/foundation/nsnumber?language=objc#1776615>
|
||||
match enc {
|
||||
"c" => Encoding::Char,
|
||||
"C" => Encoding::UChar,
|
||||
"s" => Encoding::Short,
|
||||
"S" => Encoding::UShort,
|
||||
"i" => Encoding::Int,
|
||||
"I" => Encoding::UInt,
|
||||
"l" => Encoding::Long,
|
||||
"L" => Encoding::ULong,
|
||||
"q" => Encoding::LongLong,
|
||||
"Q" => Encoding::ULongLong,
|
||||
"f" => Encoding::Float,
|
||||
"d" => Encoding::Double,
|
||||
_ => unreachable!("invalid encoding for NSNumber"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSNumber {
|
||||
#[sel(compare:)]
|
||||
fn compare(&self, other: &Self) -> NSComparisonResult;
|
||||
|
||||
#[sel(isEqualToNumber:)]
|
||||
fn is_equal_to_number(&self, other: &Self) -> bool;
|
||||
|
||||
fn string(&self) -> Id<NSString, Shared> {
|
||||
unsafe { msg_send_id![self, stringValue] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSNumber {
|
||||
type Ownership = Shared;
|
||||
type Output = NSNumber;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSNumber {
|
||||
type Owned = Id<NSNumber, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Beware: This uses the Objective-C method "isEqualToNumber:", which has
|
||||
/// different floating point NaN semantics than Rust!
|
||||
impl PartialEq for NSNumber {
|
||||
#[doc(alias = "isEqualToNumber:")]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Use isEqualToNumber: instaed of isEqual: since it is faster
|
||||
self.is_equal_to_number(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for NSNumber {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// Delegate to NSObject
|
||||
(***self).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Beware: This uses the Objective-C method "compare:", which has different
|
||||
/// floating point NaN semantics than Rust!
|
||||
impl PartialOrd for NSNumber {
|
||||
#[doc(alias = "compare:")]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// Use Objective-C semantics for comparison
|
||||
Some(self.compare(other).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NSNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Delegate to -[NSObject description]
|
||||
// (happens to return the same as -[NSNumber stringValue])
|
||||
fmt::Debug::fmt(&***self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let val = NSNumber::new_u32(13);
|
||||
assert_eq!(val.as_u32(), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
assert!(NSNumber::new_bool(true).as_bool());
|
||||
assert!(!NSNumber::new_bool(false).as_bool());
|
||||
|
||||
fn assert_roundtrip_signed(val: i64) {
|
||||
assert_eq!(NSNumber::new_i8(val as i8).as_i8(), val as i8);
|
||||
assert_eq!(NSNumber::new_i16(val as i16).as_i16(), val as i16);
|
||||
assert_eq!(NSNumber::new_i32(val as i32).as_i32(), val as i32);
|
||||
assert_eq!(NSNumber::new_i64(val).as_i64(), val);
|
||||
assert_eq!(NSNumber::new_isize(val as isize).as_isize(), val as isize);
|
||||
}
|
||||
|
||||
assert_roundtrip_signed(i64::MIN);
|
||||
assert_roundtrip_signed(i32::MIN as i64);
|
||||
assert_roundtrip_signed(i16::MIN as i64);
|
||||
assert_roundtrip_signed(i8::MIN as i64);
|
||||
assert_roundtrip_signed(-1);
|
||||
assert_roundtrip_signed(0);
|
||||
assert_roundtrip_signed(1);
|
||||
assert_roundtrip_signed(i8::MAX as i64);
|
||||
assert_roundtrip_signed(i16::MAX as i64);
|
||||
assert_roundtrip_signed(i32::MAX as i64);
|
||||
assert_roundtrip_signed(i64::MAX);
|
||||
|
||||
fn assert_roundtrip_unsigned(val: u64) {
|
||||
assert_eq!(NSNumber::new_u8(val as u8).as_u8(), val as u8);
|
||||
assert_eq!(NSNumber::new_u16(val as u16).as_u16(), val as u16);
|
||||
assert_eq!(NSNumber::new_u32(val as u32).as_u32(), val as u32);
|
||||
assert_eq!(NSNumber::new_u64(val).as_u64(), val);
|
||||
assert_eq!(NSNumber::new_usize(val as usize).as_usize(), val as usize);
|
||||
}
|
||||
|
||||
assert_roundtrip_unsigned(0);
|
||||
assert_roundtrip_unsigned(1);
|
||||
assert_roundtrip_unsigned(u8::MAX as u64);
|
||||
assert_roundtrip_unsigned(u16::MAX as u64);
|
||||
assert_roundtrip_unsigned(u32::MAX as u64);
|
||||
assert_roundtrip_unsigned(u64::MAX);
|
||||
|
||||
fn assert_roundtrip_float(val: f64) {
|
||||
assert_eq!(NSNumber::new_f32(val as f32).as_f32(), val as f32);
|
||||
assert_eq!(NSNumber::new_f64(val).as_f64(), val);
|
||||
}
|
||||
|
||||
assert_roundtrip_float(0.0);
|
||||
assert_roundtrip_float(-1.0);
|
||||
assert_roundtrip_float(1.0);
|
||||
assert_roundtrip_float(f64::INFINITY);
|
||||
assert_roundtrip_float(-f64::INFINITY);
|
||||
assert_roundtrip_float(f64::MAX);
|
||||
assert_roundtrip_float(f64::MIN);
|
||||
assert_roundtrip_float(f64::MIN_POSITIVE);
|
||||
|
||||
assert!(NSNumber::new_f32(f32::NAN).as_f32().is_nan());
|
||||
assert!(NSNumber::new_f64(f64::NAN).as_f64().is_nan());
|
||||
assert!(NSNumber::new_f32(-f32::NAN).as_f32().is_nan());
|
||||
assert!(NSNumber::new_f64(-f64::NAN).as_f64().is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cast_between_types() {
|
||||
assert_eq!(NSNumber::new_bool(true).as_i8(), 1);
|
||||
assert_eq!(NSNumber::new_i32(i32::MAX).as_u32(), i32::MAX as u32);
|
||||
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
|
||||
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equality() {
|
||||
let val1 = NSNumber::new_u32(123);
|
||||
let val2 = NSNumber::new_u32(123);
|
||||
let val3 = NSNumber::new_u8(123);
|
||||
assert_eq!(val1, val1);
|
||||
assert_eq!(val1, val2);
|
||||
assert_eq!(val1, val3);
|
||||
|
||||
let val4 = NSNumber::new_u32(456);
|
||||
assert_ne!(val1, val4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_debug() {
|
||||
fn assert_display_debug<T: fmt::Debug + fmt::Display>(val: T, expected: &str) {
|
||||
// The two impls for these happen to be the same
|
||||
assert_eq!(format!("{}", val), expected);
|
||||
assert_eq!(format!("{:?}", val), expected);
|
||||
}
|
||||
assert_display_debug(NSNumber::new_u8(171), "171");
|
||||
assert_display_debug(NSNumber::new_i8(-12), "-12");
|
||||
assert_display_debug(NSNumber::new_u32(0xdeadbeef), "3735928559");
|
||||
assert_display_debug(NSNumber::new_f32(1.1), "1.1");
|
||||
assert_display_debug(NSNumber::new_f32(1.0), "1");
|
||||
assert_display_debug(NSNumber::new_bool(true), "1");
|
||||
assert_display_debug(NSNumber::new_bool(false), "0");
|
||||
}
|
||||
}
|
||||
205
third-party/vendor/objc2/src/foundation/object.rs
vendored
Normal file
205
third-party/vendor/objc2/src/foundation/object.rs
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use core::fmt;
|
||||
use core::hash;
|
||||
|
||||
use super::NSString;
|
||||
use crate::rc::{DefaultId, Id, Owned, Shared};
|
||||
use crate::runtime::{Class, Object};
|
||||
use crate::{ClassType, __inner_extern_class, class, extern_methods, msg_send_id};
|
||||
|
||||
__inner_extern_class! {
|
||||
@__inner
|
||||
pub struct NSObject () {}
|
||||
|
||||
unsafe impl () for NSObject {
|
||||
INHERITS = [Object];
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ClassType for NSObject {
|
||||
type Super = Object;
|
||||
const NAME: &'static str = "NSObject";
|
||||
|
||||
#[inline]
|
||||
fn class() -> &'static Class {
|
||||
class!(NSObject)
|
||||
}
|
||||
|
||||
fn as_super(&self) -> &Self::Super {
|
||||
&self.__inner
|
||||
}
|
||||
|
||||
fn as_super_mut(&mut self) -> &mut Self::Super {
|
||||
&mut self.__inner
|
||||
}
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSObject {
|
||||
pub fn new() -> Id<Self, Owned> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
#[sel(isKindOfClass:)]
|
||||
fn is_kind_of_inner(&self, cls: &Class) -> bool;
|
||||
|
||||
#[sel(isEqual:)]
|
||||
fn is_equal(&self, other: &Self) -> bool;
|
||||
|
||||
#[sel(hash)]
|
||||
fn hash_code(&self) -> usize;
|
||||
|
||||
/// Check if the object is an instance of the class, or one of it's
|
||||
/// subclasses.
|
||||
///
|
||||
/// See [Apple's documentation][apple-doc] for more details on what you
|
||||
/// may (and what you may not) do with this information.
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418511-iskindofclass
|
||||
#[doc(alias = "isKindOfClass:")]
|
||||
pub fn is_kind_of<T: ClassType>(&self) -> bool {
|
||||
self.is_kind_of_inner(T::class())
|
||||
}
|
||||
|
||||
// Note: We don't provide a method to convert `NSObject` to `T` based on
|
||||
// `is_kind_of`, since that is not possible to do in general!
|
||||
//
|
||||
// For example, something may have a return type of `NSString`, while
|
||||
// behind the scenes they really return `NSMutableString` and expect it to
|
||||
// not be modified.
|
||||
}
|
||||
);
|
||||
|
||||
/// Objective-C equality has approximately the same semantics as Rust
|
||||
/// equality (although less aptly specified).
|
||||
///
|
||||
/// At the very least, equality is _expected_ to be symmetric and
|
||||
/// transitive, and that's about the best we can do.
|
||||
///
|
||||
/// See also <https://nshipster.com/equality/>
|
||||
impl PartialEq for NSObject {
|
||||
#[inline]
|
||||
#[doc(alias = "isEqual:")]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.is_equal(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Most types' equality is reflexive.
|
||||
impl Eq for NSObject {}
|
||||
|
||||
/// Hashing in Objective-C has the exact same requirement as in Rust:
|
||||
///
|
||||
/// > If two objects are equal (as determined by the isEqual: method),
|
||||
/// > they must have the same hash value.
|
||||
///
|
||||
/// See <https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash>
|
||||
impl hash::Hash for NSObject {
|
||||
#[inline]
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
self.hash_code().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSObject {
|
||||
#[doc(alias = "description")]
|
||||
#[doc(alias = "debugDescription")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Get description
|
||||
let description: Option<Id<NSString, Shared>> = unsafe { msg_send_id![self, description] };
|
||||
|
||||
match description {
|
||||
// Attempt to format with description
|
||||
Some(description) => fmt::Display::fmt(&description, f),
|
||||
// If description was `NULL`, use `Object`'s `Debug` impl instead
|
||||
None => {
|
||||
let obj: &Object = self;
|
||||
fmt::Debug::fmt(obj, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSObject {
|
||||
type Ownership = Owned;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
|
||||
#[test]
|
||||
fn test_deref() {
|
||||
let mut obj: Id<NSObject, Owned> = NSObject::new();
|
||||
let _: &NSObject = &obj;
|
||||
let _: &mut NSObject = &mut obj;
|
||||
let _: &Object = &obj;
|
||||
let _: &mut Object = &mut obj;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_ref_borrow() {
|
||||
use core::borrow::{Borrow, BorrowMut};
|
||||
|
||||
fn impls_as_ref<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
|
||||
fn impls_as_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
|
||||
|
||||
let mut obj = NSObject::new();
|
||||
impls_as_ref::<Id<NSObject, Owned>, NSObject>(&obj);
|
||||
impls_as_mut::<Id<NSObject, Owned>, NSObject>(&mut obj);
|
||||
impls_as_ref::<NSObject, NSObject>(&obj);
|
||||
impls_as_mut::<NSObject, NSObject>(&mut obj);
|
||||
impls_as_ref::<NSObject, Object>(&obj);
|
||||
impls_as_mut::<NSObject, Object>(&mut obj);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let obj1 = NSObject::new();
|
||||
assert_eq!(obj1, obj1);
|
||||
|
||||
let obj2 = NSObject::new();
|
||||
assert_ne!(obj1, obj2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash() {
|
||||
use core::hash::Hasher;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
|
||||
let obj1 = NSObject::new();
|
||||
|
||||
let mut hashstate1 = DefaultHasher::new();
|
||||
let mut hashstate2 = DefaultHasher::new();
|
||||
|
||||
obj1.hash(&mut hashstate1);
|
||||
obj1.hash(&mut hashstate2);
|
||||
|
||||
assert_eq!(hashstate1.finish(), hashstate2.finish());
|
||||
|
||||
let obj2 = NSObject::new();
|
||||
let mut hashstate2 = DefaultHasher::new();
|
||||
obj2.hash(&mut hashstate2);
|
||||
assert_ne!(hashstate1.finish(), hashstate2.finish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let obj = NSObject::new();
|
||||
let expected = format!("<NSObject: {:p}>", &*obj);
|
||||
assert_eq!(format!("{:?}", obj), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_kind_of() {
|
||||
let obj = NSObject::new();
|
||||
assert!(obj.is_kind_of::<NSObject>());
|
||||
assert!(!obj.is_kind_of::<NSString>());
|
||||
}
|
||||
}
|
||||
65
third-party/vendor/objc2/src/foundation/process_info.rs
vendored
Normal file
65
third-party/vendor/objc2/src/foundation/process_info.rs
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSObject, NSString};
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A collection of information about the current process.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsprocessinfo?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSProcessInfo;
|
||||
|
||||
unsafe impl ClassType for NSProcessInfo {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: The documentation explicitly states:
|
||||
// > NSProcessInfo is thread-safe in macOS 10.7 and later.
|
||||
unsafe impl Send for NSProcessInfo {}
|
||||
unsafe impl Sync for NSProcessInfo {}
|
||||
|
||||
impl UnwindSafe for NSProcessInfo {}
|
||||
impl RefUnwindSafe for NSProcessInfo {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSProcessInfo {
|
||||
pub fn process_info() -> Id<NSProcessInfo, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), processInfo] }
|
||||
}
|
||||
|
||||
pub fn process_name(&self) -> Id<NSString, Shared> {
|
||||
unsafe { msg_send_id![self, processName] }
|
||||
}
|
||||
|
||||
// TODO: This contains a lot more important functionality!
|
||||
}
|
||||
);
|
||||
|
||||
impl fmt::Debug for NSProcessInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NSProcessInfo")
|
||||
.field("process_name", &self.process_name())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let info = NSProcessInfo::process_info();
|
||||
let expected = format!(
|
||||
"NSProcessInfo {{ process_name: {:?}, .. }}",
|
||||
info.process_name()
|
||||
);
|
||||
assert_eq!(format!("{:?}", info), expected);
|
||||
}
|
||||
}
|
||||
183
third-party/vendor/objc2/src/foundation/range.rs
vendored
Normal file
183
third-party/vendor/objc2/src/foundation/range.rs
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use core::ops::Range;
|
||||
|
||||
use super::NSUInteger;
|
||||
use crate::{Encode, Encoding, RefEncode};
|
||||
|
||||
/// TODO.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrange?language=objc).
|
||||
#[repr(C)]
|
||||
// PartialEq is same as NSEqualRanges
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct NSRange {
|
||||
/// The lower bound of the range (inclusive).
|
||||
pub location: NSUInteger,
|
||||
/// The number of items in the range, starting from `location`.
|
||||
pub length: NSUInteger,
|
||||
}
|
||||
|
||||
impl NSRange {
|
||||
/// Create a new range with the given values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::NSRange;
|
||||
/// assert_eq!(NSRange::new(3, 2), NSRange::from(3..5));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "NSMakeRange")]
|
||||
pub const fn new(location: usize, length: usize) -> Self {
|
||||
// Equivalent to NSMakeRange
|
||||
Self { location, length }
|
||||
}
|
||||
|
||||
/// Returns `true` if the range contains no items.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::NSRange;
|
||||
///
|
||||
/// assert!(!NSRange::from(3..5).is_empty());
|
||||
/// assert!( NSRange::from(3..3).is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.length == 0
|
||||
}
|
||||
|
||||
/// Returns `true` if the index is within the range.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::NSRange;
|
||||
///
|
||||
/// assert!(!NSRange::from(3..5).contains(2));
|
||||
/// assert!( NSRange::from(3..5).contains(3));
|
||||
/// assert!( NSRange::from(3..5).contains(4));
|
||||
/// assert!(!NSRange::from(3..5).contains(5));
|
||||
///
|
||||
/// assert!(!NSRange::from(3..3).contains(3));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[doc(alias = "NSLocationInRange")]
|
||||
pub fn contains(&self, index: usize) -> bool {
|
||||
// Same as NSLocationInRange
|
||||
if let Some(len) = index.checked_sub(self.location) {
|
||||
len < self.length
|
||||
} else {
|
||||
// index < self.location
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the upper bound of the range (exclusive).
|
||||
#[inline]
|
||||
#[doc(alias = "NSMaxRange")]
|
||||
pub fn end(&self) -> usize {
|
||||
self.location
|
||||
.checked_add(self.length)
|
||||
.expect("NSRange too large")
|
||||
}
|
||||
|
||||
// TODO: https://developer.apple.com/documentation/foundation/1408420-nsrangefromstring
|
||||
// TODO: NSUnionRange
|
||||
// TODO: NSIntersectionRange
|
||||
}
|
||||
|
||||
// Sadly, we can't do this:
|
||||
// impl RangeBounds<usize> for NSRange {
|
||||
// fn start_bound(&self) -> Bound<&usize> {
|
||||
// Bound::Included(&self.location)
|
||||
// }
|
||||
// fn end_bound(&self) -> Bound<&usize> {
|
||||
// Bound::Excluded(&(self.location + self.length))
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<Range<usize>> for NSRange {
|
||||
fn from(range: Range<usize>) -> Self {
|
||||
let length = range
|
||||
.end
|
||||
.checked_sub(range.start)
|
||||
.expect("Range end < start");
|
||||
Self {
|
||||
location: range.start,
|
||||
length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSRange> for Range<usize> {
|
||||
#[inline]
|
||||
fn from(nsrange: NSRange) -> Self {
|
||||
Self {
|
||||
start: nsrange.location,
|
||||
end: nsrange.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSRange {
|
||||
const ENCODING: Encoding = Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]);
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for NSRange {
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_from_range() {
|
||||
let cases: &[(Range<usize>, NSRange)] = &[
|
||||
(0..0, NSRange::new(0, 0)),
|
||||
(0..10, NSRange::new(0, 10)),
|
||||
(10..10, NSRange::new(10, 0)),
|
||||
(10..20, NSRange::new(10, 10)),
|
||||
];
|
||||
|
||||
for (range, expected) in cases {
|
||||
assert_eq!(NSRange::from(range.clone()), *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Range end < start"]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn test_from_range_inverted() {
|
||||
let _ = NSRange::from(10..0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let range = NSRange::from(10..20);
|
||||
assert!(!range.contains(0));
|
||||
assert!(!range.contains(9));
|
||||
assert!(range.contains(10));
|
||||
assert!(range.contains(11));
|
||||
assert!(!range.contains(20));
|
||||
assert!(!range.contains(21));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_end() {
|
||||
let range = NSRange::from(10..20);
|
||||
assert!(!range.contains(0));
|
||||
assert!(!range.contains(9));
|
||||
assert!(range.contains(10));
|
||||
assert!(range.contains(11));
|
||||
assert!(!range.contains(20));
|
||||
assert!(!range.contains(21));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "NSRange too large"]
|
||||
fn test_end_large() {
|
||||
let _ = NSRange::new(usize::MAX, usize::MAX).end();
|
||||
}
|
||||
}
|
||||
680
third-party/vendor/objc2/src/foundation/set.rs
vendored
Normal file
680
third-party/vendor/objc2/src/foundation/set.rs
vendored
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{
|
||||
NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
|
||||
NSMutableSet, NSObject,
|
||||
};
|
||||
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
|
||||
use crate::runtime::Class;
|
||||
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
|
||||
|
||||
__inner_extern_class!(
|
||||
/// An immutable unordered collection of unique objects.
|
||||
///
|
||||
/// See [Apple's documentation][apple-doc].
|
||||
///
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsset?language=objc
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSSet<T: Message, O: Ownership = Shared> {
|
||||
item: PhantomData<Id<T, O>>,
|
||||
notunwindsafe: PhantomData<&'static mut ()>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> ClassType for NSSet<T, O> {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Same as NSArray<T, O>
|
||||
unsafe impl<T: Message + Sync + Send> Sync for NSSet<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync + Send> Send for NSSet<T, Shared> {}
|
||||
unsafe impl<T: Message + Sync> Sync for NSSet<T, Owned> {}
|
||||
unsafe impl<T: Message + Send> Send for NSSet<T, Owned> {}
|
||||
|
||||
// SAFETY: Same as NSArray<T, O>
|
||||
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSSet<T, O> {}
|
||||
impl<T: Message + RefUnwindSafe> UnwindSafe for NSSet<T, Shared> {}
|
||||
impl<T: Message + UnwindSafe> UnwindSafe for NSSet<T, Owned> {}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
|
||||
cls: &Class,
|
||||
objects: &[&T],
|
||||
) -> Id<R, O> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![cls, alloc],
|
||||
initWithObjects: objects.as_ptr(),
|
||||
count: objects.len()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl<T: Message, O: Ownership> NSSet<T, O> {
|
||||
/// Creates an empty [`NSSet`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set = NSSet::<NSString>::new();
|
||||
/// ```
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
// SAFETY:
|
||||
// - `new` may not create a new object, but instead return a shared
|
||||
// instance. We remedy this by returning `Id<Self, Shared>`.
|
||||
// - `O` don't actually matter here! E.g. `NSSet<T, Owned>` is
|
||||
// perfectly legal, since the set doesn't have any elements, and
|
||||
// hence the notion of ownership over the elements is void.
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// Creates an [`NSSet`] from a vector.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
|
||||
/// let set = NSSet::from_vec(strs);
|
||||
/// ```
|
||||
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
|
||||
// SAFETY:
|
||||
// When we know that we have ownership over the variables, we also
|
||||
// know that there cannot be another set in existence with the same
|
||||
// objects, so `Id<NSSet<T, Owned>, Owned>` is safe to return when
|
||||
// we receive `Vec<Id<T, Owned>>`.
|
||||
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// assert_eq!(set.len(), 3);
|
||||
/// ```
|
||||
#[doc(alias = "count")]
|
||||
#[sel(count)]
|
||||
pub fn len(&self) -> usize;
|
||||
|
||||
/// Returns `true` if the set contains no elements.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set = NSSet::<NSString>::new();
|
||||
/// assert!(set.is_empty());
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns a reference to one of the objects in the set, or `None` if
|
||||
/// the set is empty.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// let any = set.get_any().unwrap();
|
||||
/// assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
|
||||
/// ```
|
||||
#[doc(alias = "anyObject")]
|
||||
#[sel(anyObject)]
|
||||
pub fn get_any(&self) -> Option<&T>;
|
||||
|
||||
/// An iterator visiting all elements in arbitrary order.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// for s in set.iter() {
|
||||
/// println!("{s}");
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(alias = "objectEnumerator")]
|
||||
pub fn iter(&self) -> NSEnumerator<'_, T> {
|
||||
unsafe {
|
||||
let result = msg_send![self, objectEnumerator];
|
||||
NSEnumerator::from_ptr(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`Vec`] containing the set's elements, consuming the set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSMutableString, NSSet};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = vec![
|
||||
/// NSMutableString::from_str("one"),
|
||||
/// NSMutableString::from_str("two"),
|
||||
/// NSMutableString::from_str("three"),
|
||||
/// ];
|
||||
/// let set = NSSet::from_vec(strs);
|
||||
/// let vec = NSSet::into_vec(set);
|
||||
/// assert_eq!(vec.len(), 3);
|
||||
/// ```
|
||||
pub fn into_vec(set: Id<Self, O>) -> Vec<Id<T, O>> {
|
||||
set.into_iter()
|
||||
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> NSSet<T, Shared> {
|
||||
/// Creates an [`NSSet`] from a slice.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// ```
|
||||
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
|
||||
// SAFETY:
|
||||
// Taking `&T` would not be sound, since the `&T` could come from
|
||||
// an `Id<T, Owned>` that would now no longer be owned!
|
||||
//
|
||||
// We always return `Id<NSSet<T, Shared>, Shared>` because the
|
||||
// elements are shared.
|
||||
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
|
||||
}
|
||||
|
||||
/// Returns an [`NSArray`] containing the set's elements, or an empty
|
||||
/// array if the set is empty.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSNumber, NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let nums = [1, 2, 3];
|
||||
/// let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
|
||||
///
|
||||
/// assert_eq!(set.to_array().len(), 3);
|
||||
/// assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
|
||||
/// ```
|
||||
#[doc(alias = "allObjects")]
|
||||
pub fn to_array(&self) -> Id<NSArray<T, Shared>, Shared> {
|
||||
// SAFETY:
|
||||
// We only define this method for sets with shared elements
|
||||
// because we can't return copies of owned elements.
|
||||
unsafe { msg_send_id![self, allObjects] }
|
||||
}
|
||||
}
|
||||
|
||||
// We're explicit about `T` being `PartialEq` for these methods because the
|
||||
// set compares the input value(s) with elements in the set
|
||||
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
|
||||
unsafe impl<T: Message + PartialEq, O: Ownership> NSSet<T, O> {
|
||||
/// Returns `true` if the set contains a value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// assert!(set.contains(ns_string!("one")));
|
||||
/// ```
|
||||
#[doc(alias = "containsObject:")]
|
||||
#[sel(containsObject:)]
|
||||
pub fn contains(&self, value: &T) -> bool;
|
||||
|
||||
/// Returns a reference to the value in the set, if any, that is equal
|
||||
/// to the given value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// use objc2::ns_string;
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
/// let set = NSSet::from_slice(&strs);
|
||||
/// assert_eq!(set.get(ns_string!("one")), Some(&*strs[0]));
|
||||
/// assert_eq!(set.get(ns_string!("four")), None);
|
||||
/// ```
|
||||
#[doc(alias = "member:")]
|
||||
#[sel(member:)]
|
||||
pub fn get(&self, value: &T) -> Option<&T>;
|
||||
|
||||
/// Returns `true` if the set is a subset of another, i.e., `other`
|
||||
/// contains at least all the values in `self`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
///
|
||||
/// assert!(set1.is_subset(&set2));
|
||||
/// assert!(!set2.is_subset(&set1));
|
||||
/// ```
|
||||
#[doc(alias = "isSubsetOfSet:")]
|
||||
#[sel(isSubsetOfSet:)]
|
||||
pub fn is_subset(&self, other: &NSSet<T, O>) -> bool;
|
||||
|
||||
/// Returns `true` if the set is a superset of another, i.e., `self`
|
||||
/// contains at least all the values in `other`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
///
|
||||
/// assert!(!set1.is_superset(&set2));
|
||||
/// assert!(set2.is_superset(&set1));
|
||||
/// ```
|
||||
pub fn is_superset(&self, other: &NSSet<T, O>) -> bool {
|
||||
other.is_subset(self)
|
||||
}
|
||||
|
||||
#[sel(intersectsSet:)]
|
||||
fn intersects_set(&self, other: &NSSet<T, O>) -> bool;
|
||||
|
||||
/// Returns `true` if `self` has no elements in common with `other`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSSet, NSString};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
///
|
||||
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
/// let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
|
||||
///
|
||||
/// assert!(!set1.is_disjoint(&set2));
|
||||
/// assert!(set1.is_disjoint(&set3));
|
||||
/// assert!(set2.is_disjoint(&set3));
|
||||
/// ```
|
||||
pub fn is_disjoint(&self, other: &NSSet<T, O>) -> bool {
|
||||
!self.intersects_set(other)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl<T: Message> NSCopying for NSSet<T, Shared> {
|
||||
type Ownership = Shared;
|
||||
type Output = NSSet<T, Shared>;
|
||||
}
|
||||
|
||||
unsafe impl<T: Message> NSMutableCopying for NSSet<T, Shared> {
|
||||
type Output = NSMutableSet<T, Shared>;
|
||||
}
|
||||
|
||||
impl<T: Message> alloc::borrow::ToOwned for NSSet<T, Shared> {
|
||||
type Owned = Id<NSSet<T, Shared>, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSSet<T, O> {
|
||||
type Item = T;
|
||||
}
|
||||
|
||||
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSSet<T, O> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = NSFastEnumerator<'a, NSSet<T, O>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_fast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Message, O: Ownership> DefaultId for NSSet<T, O> {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSSet<T, O> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_set().entries(self.iter_fast()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::{NSMutableString, NSNumber, NSString};
|
||||
use crate::ns_string;
|
||||
use crate::rc::{RcTestObject, ThreadTestData};
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert!(set.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_vec() {
|
||||
let set = NSSet::<NSString>::from_vec(Vec::new());
|
||||
assert!(set.is_empty());
|
||||
|
||||
let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
let set = NSSet::from_vec(strs.to_vec());
|
||||
assert!(strs.into_iter().all(|s| set.contains(&s)));
|
||||
|
||||
let nums = [1, 2, 3].map(NSNumber::new_i32);
|
||||
let set = NSSet::from_vec(nums.to_vec());
|
||||
assert!(nums.into_iter().all(|n| set.contains(&n)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_slice() {
|
||||
let set = NSSet::<NSString>::from_slice(&[]);
|
||||
assert!(set.is_empty());
|
||||
|
||||
let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
let set = NSSet::from_slice(&strs);
|
||||
assert!(strs.into_iter().all(|s| set.contains(&s)));
|
||||
|
||||
let nums = [1, 2, 3].map(NSNumber::new_i32);
|
||||
let set = NSSet::from_slice(&nums);
|
||||
assert!(nums.into_iter().all(|n| set.contains(&n)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert!(set.is_empty());
|
||||
|
||||
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
|
||||
assert_eq!(set.len(), 2);
|
||||
|
||||
let set = NSSet::from_vec(vec![NSObject::new(), NSObject::new(), NSObject::new()]);
|
||||
assert_eq!(set.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert!(set.get(ns_string!("one")).is_none());
|
||||
|
||||
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
|
||||
assert!(set.get(ns_string!("two")).is_some());
|
||||
assert!(set.get(ns_string!("three")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_return_lifetime() {
|
||||
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
|
||||
|
||||
let res = {
|
||||
let value = NSString::from_str("one");
|
||||
set.get(&value)
|
||||
};
|
||||
|
||||
assert_eq!(res, Some(ns_string!("one")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_any() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert!(set.get_any().is_none());
|
||||
|
||||
let strs = ["one", "two", "three"].map(NSString::from_str);
|
||||
let set = NSSet::from_slice(&strs);
|
||||
let any = set.get_any().unwrap();
|
||||
assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert!(!set.contains(ns_string!("one")));
|
||||
|
||||
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
|
||||
assert!(set.contains(ns_string!("one")));
|
||||
assert!(!set.contains(ns_string!("three")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_subset() {
|
||||
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
|
||||
assert!(set1.is_subset(&set2));
|
||||
assert!(!set2.is_subset(&set1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_superset() {
|
||||
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
|
||||
assert!(!set1.is_superset(&set2));
|
||||
assert!(set2.is_superset(&set1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_disjoint() {
|
||||
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
|
||||
|
||||
assert!(!set1.is_disjoint(&set2));
|
||||
assert!(set1.is_disjoint(&set3));
|
||||
assert!(set2.is_disjoint(&set3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_array() {
|
||||
let nums = [1, 2, 3];
|
||||
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
|
||||
|
||||
assert_eq!(set.to_array().len(), 3);
|
||||
assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let nums = [1, 2, 3];
|
||||
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
|
||||
|
||||
assert_eq!(set.iter().count(), 3);
|
||||
assert!(set.iter().all(|i| nums.contains(&i.as_i32())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_fast() {
|
||||
let nums = [1, 2, 3];
|
||||
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
|
||||
|
||||
assert_eq!(set.iter_fast().count(), 3);
|
||||
assert!(set.iter_fast().all(|i| nums.contains(&i.as_i32())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_iter() {
|
||||
let nums = [1, 2, 3];
|
||||
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
|
||||
|
||||
assert!(set.into_iter().all(|i| nums.contains(&i.as_i32())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_vec() {
|
||||
let strs = vec![
|
||||
NSMutableString::from_str("one"),
|
||||
NSMutableString::from_str("two"),
|
||||
NSMutableString::from_str("three"),
|
||||
];
|
||||
let set = NSSet::from_vec(strs);
|
||||
|
||||
let mut vec = NSSet::into_vec(set);
|
||||
for str in vec.iter_mut() {
|
||||
str.push_nsstring(ns_string!(" times zero is zero"));
|
||||
}
|
||||
|
||||
assert_eq!(vec.len(), 3);
|
||||
let suffix = ns_string!("zero");
|
||||
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let set1 = NSSet::<NSString>::new();
|
||||
let set2 = NSSet::<NSString>::new();
|
||||
assert_eq!(set1, set2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy() {
|
||||
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
|
||||
let set2 = set1.copy();
|
||||
assert_eq!(set1, set2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let set = NSSet::<NSString>::new();
|
||||
assert_eq!(format!("{:?}", set), "{}");
|
||||
|
||||
let set = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
|
||||
assert!(matches!(
|
||||
format!("{:?}", set).as_str(),
|
||||
"{\"one\", \"two\"}" | "{\"two\", \"one\"}"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retains_stored() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let input = [obj.clone(), obj.clone()];
|
||||
expected.retain += 2;
|
||||
expected.assert_current();
|
||||
|
||||
let set = NSSet::from_slice(&input);
|
||||
expected.retain += 1;
|
||||
expected.assert_current();
|
||||
|
||||
let _obj = set.get_any().unwrap();
|
||||
expected.assert_current();
|
||||
|
||||
drop(set);
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
|
||||
let set = NSSet::from_vec(Vec::from(input));
|
||||
expected.retain += 1;
|
||||
expected.release += 2;
|
||||
expected.assert_current();
|
||||
|
||||
drop(set);
|
||||
expected.release += 1;
|
||||
expected.assert_current();
|
||||
|
||||
drop(obj);
|
||||
expected.release += 1;
|
||||
expected.dealloc += 1;
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nscopying_uses_retain() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let set = NSSet::from_slice(&[obj]);
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let _copy = set.copy();
|
||||
expected.assert_current();
|
||||
|
||||
let _copy = set.mutable_copy();
|
||||
expected.retain += 1;
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
feature = "apple",
|
||||
ignore = "this works differently on different framework versions"
|
||||
)]
|
||||
fn test_iter_no_retain() {
|
||||
let obj = Id::into_shared(RcTestObject::new());
|
||||
let set = NSSet::from_slice(&[obj]);
|
||||
let mut expected = ThreadTestData::current();
|
||||
|
||||
let iter = set.iter();
|
||||
expected.retain += 0;
|
||||
expected.assert_current();
|
||||
|
||||
assert_eq!(iter.count(), 1);
|
||||
expected.autorelease += 0;
|
||||
expected.assert_current();
|
||||
|
||||
let iter = set.iter_fast();
|
||||
assert_eq!(iter.count(), 1);
|
||||
expected.assert_current();
|
||||
}
|
||||
}
|
||||
527
third-party/vendor/objc2/src/foundation/string.rs
vendored
Normal file
527
third-party/vendor/objc2/src/foundation/string.rs
vendored
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
use alloc::borrow::ToOwned;
|
||||
use core::cmp;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::panic::RefUnwindSafe;
|
||||
use core::panic::UnwindSafe;
|
||||
use core::ptr::NonNull;
|
||||
use core::slice;
|
||||
use core::str;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use super::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject};
|
||||
use crate::rc::{autoreleasepool, AutoreleasePool, DefaultId, Id, Shared};
|
||||
use crate::runtime::{Class, Object};
|
||||
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType};
|
||||
|
||||
#[cfg(feature = "apple")]
|
||||
const UTF8_ENCODING: usize = 4;
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
const UTF8_ENCODING: i32 = 4;
|
||||
|
||||
extern_class!(
|
||||
/// An immutable, plain-text Unicode string object.
|
||||
///
|
||||
/// Can be created statically using the [`ns_string!`] macro.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring?language=objc).
|
||||
///
|
||||
/// [`ns_string!`]: crate::ns_string
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSString;
|
||||
// TODO: Use isEqualToString: for comparison (instead of just isEqual:)
|
||||
// The former is more performant
|
||||
|
||||
// TODO: Check if performance of NSSelectorFromString is worthwhile
|
||||
|
||||
unsafe impl ClassType for NSString {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: `NSString` is immutable and `NSMutableString` can only be mutated
|
||||
// from `&mut` methods.
|
||||
unsafe impl Sync for NSString {}
|
||||
unsafe impl Send for NSString {}
|
||||
|
||||
// Even if an exception occurs inside a string method, the state of the string
|
||||
// (should) still be perfectly safe to access.
|
||||
impl UnwindSafe for NSString {}
|
||||
impl RefUnwindSafe for NSString {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSString {
|
||||
/// Construct an empty NSString.
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// Create a new string by appending the given string to self.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// use objc2::ns_string;
|
||||
/// let error_tag = ns_string!("Error: ");
|
||||
/// let error_string = ns_string!("premature end of file.");
|
||||
/// let error_message = error_tag.concat(error_string);
|
||||
/// assert_eq!(&*error_message, ns_string!("Error: premature end of file."));
|
||||
/// ```
|
||||
#[doc(alias = "stringByAppendingString")]
|
||||
#[doc(alias = "stringByAppendingString:")]
|
||||
pub fn concat(&self, other: &Self) -> Id<Self, Shared> {
|
||||
// SAFETY: The other string is non-null, and won't be retained
|
||||
// by the function.
|
||||
unsafe { msg_send_id![self, stringByAppendingString: other] }
|
||||
}
|
||||
|
||||
/// Create a new string by appending the given string, separated by
|
||||
/// a path separator.
|
||||
///
|
||||
/// This is similar to [`Path::join`][std::path::Path::join].
|
||||
///
|
||||
/// Note that this method only works with file paths (not, for
|
||||
/// example, string representations of URLs).
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// use objc2::ns_string;
|
||||
///
|
||||
/// let extension = ns_string!("scratch.tiff");
|
||||
/// assert_eq!(&*ns_string!("/tmp").join_path(extension), ns_string!("/tmp/scratch.tiff"));
|
||||
/// assert_eq!(&*ns_string!("/tmp/").join_path(extension), ns_string!("/tmp/scratch.tiff"));
|
||||
/// assert_eq!(&*ns_string!("/").join_path(extension), ns_string!("/scratch.tiff"));
|
||||
/// assert_eq!(&*ns_string!("").join_path(extension), ns_string!("scratch.tiff"));
|
||||
/// ```
|
||||
#[doc(alias = "stringByAppendingPathComponent")]
|
||||
#[doc(alias = "stringByAppendingPathComponent:")]
|
||||
pub fn join_path(&self, other: &Self) -> Id<Self, Shared> {
|
||||
// SAFETY: Same as `Self::concat`.
|
||||
unsafe { msg_send_id![self, stringByAppendingPathComponent: other] }
|
||||
}
|
||||
|
||||
/// The number of UTF-8 code units in `self`.
|
||||
#[doc(alias = "lengthOfBytesUsingEncoding")]
|
||||
#[doc(alias = "lengthOfBytesUsingEncoding:")]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] }
|
||||
}
|
||||
|
||||
/// The number of UTF-16 code units in the string.
|
||||
///
|
||||
/// See also [`NSString::len`].
|
||||
#[doc(alias = "length")]
|
||||
#[sel(length)]
|
||||
pub fn len_utf16(&self) -> usize;
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
// TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for
|
||||
// other reasons, so this is not really correct!
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Get the [`str`](`prim@str`) representation of this string if it can be
|
||||
/// done efficiently.
|
||||
///
|
||||
/// Returns [`None`] if the internal storage does not allow this to be
|
||||
/// done efficiently. Use [`NSString::as_str`] or `NSString::to_string`
|
||||
/// if performance is not an issue.
|
||||
#[doc(alias = "CFStringGetCStringPtr")]
|
||||
#[allow(unused)]
|
||||
// TODO: Finish this
|
||||
fn as_str_wip(&self) -> Option<&str> {
|
||||
type CFStringEncoding = u32;
|
||||
#[allow(non_upper_case_globals)]
|
||||
// https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings/kcfstringencodingutf8?language=objc
|
||||
const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
|
||||
extern "C" {
|
||||
// https://developer.apple.com/documentation/corefoundation/1542133-cfstringgetcstringptr?language=objc
|
||||
fn CFStringGetCStringPtr(s: &NSString, encoding: CFStringEncoding)
|
||||
-> *const c_char;
|
||||
}
|
||||
let bytes = unsafe { CFStringGetCStringPtr(self, kCFStringEncodingUTF8) };
|
||||
NonNull::new(bytes as *mut u8).map(|bytes| {
|
||||
let len = self.len();
|
||||
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes.as_ptr(), len) };
|
||||
str::from_utf8(bytes).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get an [UTF-16] string slice if it can be done efficiently.
|
||||
///
|
||||
/// Returns [`None`] if the internal storage of `self` does not allow this
|
||||
/// to be returned efficiently.
|
||||
///
|
||||
/// See [`as_str`](Self::as_str) for the UTF-8 equivalent.
|
||||
///
|
||||
/// [UTF-16]: https://en.wikipedia.org/wiki/UTF-16
|
||||
#[allow(unused)]
|
||||
// TODO: Finish this
|
||||
fn as_utf16(&self) -> Option<&[u16]> {
|
||||
extern "C" {
|
||||
// https://developer.apple.com/documentation/corefoundation/1542939-cfstringgetcharactersptr?language=objc
|
||||
fn CFStringGetCharactersPtr(s: &NSString) -> *const u16;
|
||||
}
|
||||
let ptr = unsafe { CFStringGetCharactersPtr(self) };
|
||||
NonNull::new(ptr as *mut u16)
|
||||
.map(|ptr| unsafe { slice::from_raw_parts(ptr.as_ptr(), self.len_utf16()) })
|
||||
}
|
||||
|
||||
/// Get the [`str`](`prim@str`) representation of this.
|
||||
///
|
||||
/// TODO: Further explain this.
|
||||
#[doc(alias = "UTF8String")]
|
||||
pub fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str {
|
||||
// NOTE: Please keep up to date with `objc2::exception`!
|
||||
|
||||
// This is necessary until `auto` types stabilizes.
|
||||
pool.__verify_is_inner();
|
||||
|
||||
// The documentation on `UTF8String` is a bit sparse, but with
|
||||
// educated guesses and testing I've determined that NSString stores
|
||||
// a pointer to the string data, sometimes with an UTF-8 encoding,
|
||||
// (usual for ascii data), sometimes in other encodings (UTF-16?).
|
||||
//
|
||||
// `UTF8String` then checks the internal encoding:
|
||||
// - If the data is UTF-8 encoded, it returns the internal pointer.
|
||||
// - If the data is in another encoding, it creates a new allocation,
|
||||
// writes the UTF-8 representation of the string into it,
|
||||
// autoreleases the allocation and returns a pointer to it.
|
||||
//
|
||||
// So the lifetime of the returned pointer is either the same as the
|
||||
// NSString OR the lifetime of the innermost @autoreleasepool.
|
||||
//
|
||||
// https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc
|
||||
let bytes: *const c_char = unsafe { msg_send![self, UTF8String] };
|
||||
let bytes: *const u8 = bytes.cast();
|
||||
let len = self.len();
|
||||
|
||||
// SAFETY:
|
||||
// The held AutoreleasePool is the innermost, and the reference is
|
||||
// constrained both by the pool and the NSString.
|
||||
//
|
||||
// `len` is the length of the string in the UTF-8 encoding.
|
||||
//
|
||||
// `bytes` is a null-terminated C string (with length = len + 1), so
|
||||
// it is never a NULL pointer.
|
||||
let bytes: &'r [u8] = unsafe { slice::from_raw_parts(bytes, len) };
|
||||
|
||||
// TODO: Always UTF-8, so should we use `from_utf8_unchecked`?
|
||||
str::from_utf8(bytes).unwrap()
|
||||
|
||||
// NOTE: Please keep up to date with `objc2::exception`!
|
||||
}
|
||||
|
||||
// TODO: Allow usecases where the NUL byte from `UTF8String` is kept?
|
||||
|
||||
/// Creates an immutable `NSString` by copying the given string slice.
|
||||
///
|
||||
/// Prefer using the [`ns_string!`] macro when possible.
|
||||
///
|
||||
/// [`ns_string!`]: crate::ns_string
|
||||
#[doc(alias = "initWithBytes")]
|
||||
#[doc(alias = "initWithBytes:length:encoding:")]
|
||||
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
|
||||
pub fn from_str(string: &str) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
let obj = from_str(Self::class(), string);
|
||||
Id::new(obj.cast()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: initWithBytesNoCopy:, maybe add lifetime parameter to NSString?
|
||||
// See https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L350-L381
|
||||
// Might be quite difficult, as Objective-C code might assume the NSString
|
||||
// is always alive?
|
||||
// See https://github.com/drewcrawford/foundationr/blob/b27683417a35510e8e5d78a821f081905b803de6/src/nsstring.rs
|
||||
|
||||
/// Whether the given string matches the beginning characters of this
|
||||
/// string.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1410309-hasprefix?language=objc).
|
||||
#[doc(alias = "hasPrefix")]
|
||||
#[doc(alias = "hasPrefix:")]
|
||||
#[sel(hasPrefix:)]
|
||||
pub fn has_prefix(&self, prefix: &NSString) -> bool;
|
||||
|
||||
/// Whether the given string matches the ending characters of this string.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1416529-hassuffix?language=objc).
|
||||
#[doc(alias = "hasSuffix")]
|
||||
#[doc(alias = "hasSuffix:")]
|
||||
#[sel(hasSuffix:)]
|
||||
pub fn has_suffix(&self, suffix: &NSString) -> bool;
|
||||
|
||||
// TODO: Other comparison methods:
|
||||
// - compare:options:
|
||||
// - compare:options:range:
|
||||
// - compare:options:range:locale:
|
||||
// - localizedCompare:
|
||||
// - caseInsensitiveCompare:
|
||||
// - localizedCaseInsensitiveCompare:
|
||||
// - localizedStandardCompare:
|
||||
#[sel(compare:)]
|
||||
fn compare(&self, other: &Self) -> NSComparisonResult;
|
||||
|
||||
// pub fn from_nsrange(range: NSRange) -> Id<Self, Shared>
|
||||
// https://developer.apple.com/documentation/foundation/1415155-nsstringfromrange?language=objc
|
||||
}
|
||||
);
|
||||
|
||||
pub(crate) fn from_str(cls: &Class, string: &str) -> *mut Object {
|
||||
let bytes: *const c_void = string.as_ptr().cast();
|
||||
unsafe {
|
||||
let obj: *mut Object = msg_send![cls, alloc];
|
||||
msg_send![
|
||||
obj,
|
||||
initWithBytes: bytes,
|
||||
length: string.len(),
|
||||
encoding: UTF8_ENCODING,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for NSString {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NSString {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.compare(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: PartialEq and PartialOrd against &str
|
||||
// See `fruity`'s implementation:
|
||||
// https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163
|
||||
|
||||
impl DefaultId for NSString {
|
||||
type Ownership = Shared;
|
||||
|
||||
#[inline]
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSCopying for NSString {
|
||||
type Ownership = Shared;
|
||||
type Output = NSString;
|
||||
}
|
||||
|
||||
unsafe impl NSMutableCopying for NSString {
|
||||
type Output = NSMutableString;
|
||||
}
|
||||
|
||||
impl ToOwned for NSString {
|
||||
type Owned = Id<NSString, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NSString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// The call to `to_owned` is unfortunate, but is required to work
|
||||
// around `f` not being AutoreleaseSafe.
|
||||
// TODO: Fix this!
|
||||
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
|
||||
fmt::Display::fmt(&s, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// The call to `to_owned` is unfortunate, but is required to work
|
||||
// around `f` not being AutoreleaseSafe.
|
||||
// TODO: Fix this!
|
||||
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
|
||||
fmt::Debug::fmt(&s, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::format;
|
||||
use core::ptr;
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let s1 = NSString::from_str("abc");
|
||||
let s2 = NSString::from_str("abc");
|
||||
assert_eq!(s1, s1);
|
||||
assert_eq!(s1, s2);
|
||||
|
||||
let s3 = NSString::from_str("def");
|
||||
assert_ne!(s1, s3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_debug() {
|
||||
let s = NSString::from_str("xyz\"123");
|
||||
assert_eq!(format!("{}", s), "xyz\"123");
|
||||
assert_eq!(format!("{:?}", s), r#""xyz\"123""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
let s1 = NSString::from_str("");
|
||||
let s2 = NSString::new();
|
||||
|
||||
assert_eq!(s1.len(), 0);
|
||||
assert_eq!(s2.len(), 0);
|
||||
|
||||
assert_eq!(s1, s2);
|
||||
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(s1.as_str(pool), "");
|
||||
assert_eq!(s2.as_str(pool), "");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf8() {
|
||||
let expected = "ประเทศไทย中华Việt Nam";
|
||||
let s = NSString::from_str(expected);
|
||||
assert_eq!(s.len(), expected.len());
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(s.as_str(pool), expected);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nul() {
|
||||
let expected = "\0";
|
||||
let s = NSString::from_str(expected);
|
||||
assert_eq!(s.len(), expected.len());
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(s.as_str(pool), expected);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interior_nul() {
|
||||
let expected = "Hello\0World";
|
||||
let s = NSString::from_str(expected);
|
||||
assert_eq!(s.len(), expected.len());
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(s.as_str(pool), expected);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy() {
|
||||
let s1 = NSString::from_str("abc");
|
||||
let s2 = s1.copy();
|
||||
// An optimization that NSString makes, since it is immutable
|
||||
assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
|
||||
assert!(s2.is_kind_of::<NSString>());
|
||||
|
||||
let s3 = s1.mutable_copy();
|
||||
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
|
||||
assert!(s3.is_kind_of::<NSMutableString>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_nsstring_is_same() {
|
||||
let string1 = NSString::from_str("Hello, world!");
|
||||
let string2 = string1.copy();
|
||||
assert!(
|
||||
ptr::eq(&*string1, &*string2),
|
||||
"Cloned NSString didn't have the same address"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Apparently NSString does this for some reason?
|
||||
fn test_strips_first_leading_zero_width_no_break_space() {
|
||||
let ns_string = NSString::from_str("\u{feff}");
|
||||
let expected = "";
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(ns_string.as_str(pool), expected);
|
||||
});
|
||||
assert_eq!(ns_string.len(), 0);
|
||||
|
||||
let s = "\u{feff}\u{feff}a\u{feff}";
|
||||
|
||||
// Huh, this difference might be a GNUStep bug?
|
||||
#[cfg(feature = "apple")]
|
||||
let expected = "\u{feff}a\u{feff}";
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
let expected = "a\u{feff}";
|
||||
|
||||
let ns_string = NSString::from_str(s);
|
||||
autoreleasepool(|pool| {
|
||||
assert_eq!(ns_string.as_str(pool), expected);
|
||||
});
|
||||
assert_eq!(ns_string.len(), expected.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash() {
|
||||
use core::hash::Hasher;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
|
||||
let s1 = NSString::from_str("example string goes here");
|
||||
let s2 = NSString::from_str("example string goes here");
|
||||
|
||||
let mut hashstate = DefaultHasher::new();
|
||||
let mut hashstate2 = DefaultHasher::new();
|
||||
|
||||
s1.hash(&mut hashstate);
|
||||
s2.hash(&mut hashstate2);
|
||||
|
||||
assert_eq!(hashstate.finish(), hashstate2.finish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_suffix() {
|
||||
let s = NSString::from_str("abcdef");
|
||||
let prefix = NSString::from_str("abc");
|
||||
let suffix = NSString::from_str("def");
|
||||
assert!(s.has_prefix(&prefix));
|
||||
assert!(s.has_suffix(&suffix));
|
||||
assert!(!s.has_prefix(&suffix));
|
||||
assert!(!s.has_suffix(&prefix));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
fn test_cmp() {
|
||||
let s1 = NSString::from_str("aa");
|
||||
assert!(s1 <= s1);
|
||||
assert!(s1 >= s1);
|
||||
let s2 = NSString::from_str("ab");
|
||||
assert!(s1 < s2);
|
||||
assert!(!(s1 > s2));
|
||||
assert!(s1 <= s2);
|
||||
assert!(!(s1 >= s2));
|
||||
let s3 = NSString::from_str("ba");
|
||||
assert!(s1 < s3);
|
||||
assert!(!(s1 > s3));
|
||||
assert!(s1 <= s3);
|
||||
assert!(!(s1 >= s3));
|
||||
assert!(s2 < s3);
|
||||
assert!(!(s2 > s3));
|
||||
assert!(s2 <= s3);
|
||||
assert!(!(s2 >= s3));
|
||||
|
||||
let s = NSString::from_str("abc");
|
||||
let shorter = NSString::from_str("a");
|
||||
let longer = NSString::from_str("abcdef");
|
||||
assert!(s > shorter);
|
||||
assert!(s < longer);
|
||||
}
|
||||
}
|
||||
237
third-party/vendor/objc2/src/foundation/thread.rs
vendored
Normal file
237
third-party/vendor/objc2/src/foundation/thread.rs
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSObject, NSString};
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// A thread of execution.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsthread?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSThread;
|
||||
|
||||
unsafe impl ClassType for NSThread {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Send for NSThread {}
|
||||
unsafe impl Sync for NSThread {}
|
||||
|
||||
impl UnwindSafe for NSThread {}
|
||||
impl RefUnwindSafe for NSThread {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSThread {
|
||||
/// Returns the [`NSThread`] object representing the current thread.
|
||||
pub fn current() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), currentThread] }
|
||||
}
|
||||
|
||||
/// Returns the [`NSThread`] object representing the main thread.
|
||||
pub fn main() -> Id<NSThread, Shared> {
|
||||
// The main thread static may not have been initialized
|
||||
// This can at least fail in GNUStep!
|
||||
let obj: Option<_> = unsafe { msg_send_id![Self::class(), mainThread] };
|
||||
obj.expect("Could not retrieve main thread.")
|
||||
}
|
||||
|
||||
/// Returns `true` if the thread is the main thread.
|
||||
#[sel(isMainThread)]
|
||||
pub fn is_main(&self) -> bool;
|
||||
|
||||
/// The name of the thread.
|
||||
pub fn name(&self) -> Option<Id<NSString, Shared>> {
|
||||
unsafe { msg_send_id![self, name] }
|
||||
}
|
||||
|
||||
unsafe fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
#[sel(start)]
|
||||
unsafe fn start(&self);
|
||||
|
||||
#[sel(isMainThread)]
|
||||
fn is_current_main() -> bool;
|
||||
|
||||
#[sel(isMultiThreaded)]
|
||||
fn is_global_multi() -> bool;
|
||||
}
|
||||
);
|
||||
|
||||
impl fmt::Debug for NSThread {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Use -[NSThread description] since that includes the thread number
|
||||
let obj: &NSObject = self;
|
||||
fmt::Debug::fmt(obj, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the application is multithreaded according to Cocoa.
|
||||
pub fn is_multi_threaded() -> bool {
|
||||
NSThread::is_global_multi()
|
||||
}
|
||||
|
||||
/// Whether the current thread is the main thread.
|
||||
pub fn is_main_thread() -> bool {
|
||||
NSThread::is_current_main()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn make_multithreaded() {
|
||||
let thread = unsafe { NSThread::new() };
|
||||
unsafe { thread.start() };
|
||||
// Don't bother waiting for it to complete!
|
||||
}
|
||||
|
||||
/// A marker type taken by functions that can only be executed on the main
|
||||
/// thread.
|
||||
///
|
||||
/// By design, this is neither [`Send`] nor [`Sync`], and can only be created
|
||||
/// on the main thread, meaning that if you're holding this, you know you're
|
||||
/// running on the main thread.
|
||||
///
|
||||
/// See the following links for more information on main-thread-only APIs:
|
||||
/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/)
|
||||
/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1)
|
||||
/// - [Are the Cocoa Frameworks Thread Safe?](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47)
|
||||
/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065)
|
||||
/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html)
|
||||
/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html)
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Use when designing APIs that are only safe to use on the main thread:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use objc2::foundation::MainThreadMarker;
|
||||
/// use objc2::runtime::Object;
|
||||
/// use objc2::msg_send;
|
||||
/// # let obj = 0 as *const Object;
|
||||
///
|
||||
/// // This action requires the main thread, so we take a marker as parameter.
|
||||
/// // It signals clearly to users "this requires the main thread".
|
||||
/// unsafe fn do_thing(obj: *const Object, _mtm: MainThreadMarker) {
|
||||
/// msg_send![obj, someActionThatRequiresTheMainThread]
|
||||
/// }
|
||||
///
|
||||
/// // Usage
|
||||
/// let mtm = MainThreadMarker::new().unwrap();
|
||||
/// unsafe { do_thing(obj, mtm) }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
// This is valid to Copy because it's still `!Send` and `!Sync`.
|
||||
pub struct MainThreadMarker {
|
||||
// No lifetime information needed; the main thread is static and available
|
||||
// throughout the entire program!
|
||||
_priv: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl MainThreadMarker {
|
||||
/// Construct a new [`MainThreadMarker`].
|
||||
///
|
||||
/// Returns [`None`] if the current thread was not the main thread.
|
||||
pub fn new() -> Option<Self> {
|
||||
if is_main_thread() {
|
||||
// SAFETY: We just checked that we are running on the main thread.
|
||||
Some(unsafe { Self::new_unchecked() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new [`MainThreadMarker`] without first checking whether
|
||||
/// the current thread is the main one.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The current thread must be the main thread.
|
||||
pub unsafe fn new_unchecked() -> Self {
|
||||
// SAFETY: Upheld by caller
|
||||
Self { _priv: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MainThreadMarker {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("MainThreadMarker").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
feature = "gnustep-1-7",
|
||||
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
|
||||
)]
|
||||
fn test_main_thread() {
|
||||
let current = NSThread::current();
|
||||
let main = NSThread::main();
|
||||
|
||||
assert!(main.is_main());
|
||||
|
||||
if main == current {
|
||||
assert!(current.is_main());
|
||||
assert!(is_main_thread());
|
||||
} else {
|
||||
assert!(!current.is_main());
|
||||
assert!(!is_main_thread());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_main_thread() {
|
||||
let res = std::thread::spawn(|| (is_main_thread(), NSThread::current().is_main()))
|
||||
.join()
|
||||
.unwrap();
|
||||
assert_eq!(res, (false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_main_thread_auto_traits() {
|
||||
fn assert_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized>() {}
|
||||
|
||||
assert_traits::<MainThreadMarker>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
feature = "gnustep-1-7",
|
||||
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
|
||||
)]
|
||||
fn test_debug() {
|
||||
let thread = NSThread::main();
|
||||
|
||||
let actual = format!("{:?}", thread);
|
||||
let expected = [
|
||||
// macOS 11
|
||||
format!("<NSThread: {:p}>{{number = 1, name = (null)}}", thread),
|
||||
format!("<NSThread: {:p}>{{number = 1, name = main}}", thread),
|
||||
// macOS 12
|
||||
format!("<_NSMainThread: {:p}>{{number = 1, name = (null)}}", thread),
|
||||
format!("<_NSMainThread: {:p}>{{number = 1, name = main}}", thread),
|
||||
];
|
||||
assert!(
|
||||
expected.contains(&actual),
|
||||
"Expected one of {:?}, got {:?}",
|
||||
expected,
|
||||
actual,
|
||||
);
|
||||
|
||||
// SAFETY: We don't use the marker for anything other than its Debug
|
||||
// impl, so this test doesn't actually need to run on the main thread!
|
||||
let marker = unsafe { MainThreadMarker::new_unchecked() };
|
||||
assert_eq!(format!("{:?}", marker), "MainThreadMarker");
|
||||
}
|
||||
}
|
||||
189
third-party/vendor/objc2/src/foundation/uuid.rs
vendored
Normal file
189
third-party/vendor/objc2/src/foundation/uuid.rs
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use super::{NSCopying, NSObject, NSString};
|
||||
use crate::rc::{DefaultId, Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send_id, ClassType, Encode, Encoding, RefEncode};
|
||||
|
||||
extern_class!(
|
||||
/// A universally unique value.
|
||||
///
|
||||
/// Can be used to identify types, interfaces, and other items.
|
||||
///
|
||||
/// Conversion methods to/from UUIDs from the `uuid` crate can be
|
||||
/// enabled with the `uuid` crate feature.
|
||||
///
|
||||
/// macOS: This is only available on 10.8 and above.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsuuid?language=objc).
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct NSUUID;
|
||||
|
||||
unsafe impl ClassType for NSUUID {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
/// The headers describe `initWithUUIDBytes:` and `getUUIDBytes:` as
|
||||
/// taking `uuid_t`, but something fishy is going on, in reality they
|
||||
/// expect a reference to these!
|
||||
///
|
||||
/// Hence we create this newtype to change the encoding.
|
||||
#[repr(transparent)]
|
||||
struct UuidBytes([u8; 16]);
|
||||
|
||||
unsafe impl RefEncode for UuidBytes {
|
||||
const ENCODING_REF: Encoding = Encoding::Array(16, &u8::ENCODING);
|
||||
}
|
||||
|
||||
// SAFETY: `NSUUID` is immutable.
|
||||
unsafe impl Sync for NSUUID {}
|
||||
unsafe impl Send for NSUUID {}
|
||||
|
||||
impl UnwindSafe for NSUUID {}
|
||||
impl RefUnwindSafe for NSUUID {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSUUID {
|
||||
pub fn new_v4() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
/// The 'nil UUID'.
|
||||
pub fn nil() -> Id<Self, Shared> {
|
||||
Self::from_bytes([0; 16])
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: [u8; 16]) -> Id<Self, Shared> {
|
||||
let bytes = UuidBytes(bytes);
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithUUIDBytes: &bytes]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(string: &NSString) -> Option<Id<Self, Shared>> {
|
||||
unsafe {
|
||||
let obj = msg_send_id![Self::class(), alloc];
|
||||
msg_send_id![obj, initWithUUIDString: string]
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(getUUIDBytes:)]
|
||||
fn get_bytes_raw(&self, bytes: &mut UuidBytes);
|
||||
|
||||
pub fn as_bytes(&self) -> [u8; 16] {
|
||||
let mut bytes = UuidBytes([0; 16]);
|
||||
self.get_bytes_raw(&mut bytes);
|
||||
bytes.0
|
||||
}
|
||||
|
||||
pub fn string(&self) -> Id<NSString, Shared> {
|
||||
unsafe { msg_send_id![self, UUIDString] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl fmt::Display for NSUUID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSUUID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// The `uuid` crate does `Debug` and `Display` equally, and so do we
|
||||
fmt::Display::fmt(&self.string(), f)
|
||||
}
|
||||
}
|
||||
|
||||
// UUID `compare:` is broken for some reason?
|
||||
|
||||
// impl PartialOrd for NSUUID {
|
||||
// #[inline]
|
||||
// fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
// Some(self.cmp(other))
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl Ord for NSUUID {
|
||||
// fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
// let res: NSComparisonResult = unsafe { msg_send![self, compare: other] };
|
||||
// res.into()
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Conversion methods to/from `uuid` crate.
|
||||
#[cfg(feature = "uuid")]
|
||||
impl NSUUID {
|
||||
pub fn from_uuid(uuid: uuid::Uuid) -> Id<Self, Shared> {
|
||||
Self::from_bytes(uuid.into_bytes())
|
||||
}
|
||||
|
||||
pub fn as_uuid(&self) -> uuid::Uuid {
|
||||
uuid::Uuid::from_bytes(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSUUID {
|
||||
type Ownership = Shared;
|
||||
fn default_id() -> Id<Self, Self::Ownership> {
|
||||
Self::nil()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSCopying for NSUUID {
|
||||
type Ownership = Shared;
|
||||
type Output = NSUUID;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSUUID {
|
||||
type Owned = Id<NSUUID, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let uuid1 = NSUUID::new_v4();
|
||||
let uuid2 = NSUUID::new_v4();
|
||||
assert_ne!(uuid1, uuid2, "Statistically very unlikely");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
let uuid = NSUUID::from_bytes([10; 16]);
|
||||
assert_eq!(uuid.as_bytes(), [10; 16]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_debug() {
|
||||
let uuid = NSUUID::from_bytes([10; 16]);
|
||||
let expected = "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A";
|
||||
assert_eq!(format!("{}", uuid), expected);
|
||||
assert_eq!(format!("{:?}", uuid), expected);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_compare() {
|
||||
// let uuid1 = NSUUID::from_bytes([10; 16]);
|
||||
// let uuid2 = NSUUID::from_bytes([9; 16]);
|
||||
// assert!(uuid1 > uuid2);
|
||||
// }
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
#[test]
|
||||
fn test_convert_roundtrip() {
|
||||
let nsuuid1 = NSUUID::new_v4();
|
||||
let uuid = nsuuid1.as_uuid();
|
||||
let nsuuid2 = NSUUID::from_uuid(uuid);
|
||||
assert_eq!(nsuuid1, nsuuid2);
|
||||
}
|
||||
}
|
||||
363
third-party/vendor/objc2/src/foundation/value.rs
vendored
Normal file
363
third-party/vendor/objc2/src/foundation/value.rs
vendored
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
use alloc::string::ToString;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::hash;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ptr::NonNull;
|
||||
use core::str;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use super::{NSCopying, NSObject, NSPoint, NSRange, NSRect, NSSize};
|
||||
use crate::rc::{Id, Shared};
|
||||
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encode};
|
||||
|
||||
extern_class!(
|
||||
/// A container wrapping any encodable type as an Obective-C object.
|
||||
///
|
||||
/// Since Objective-C collections like [`NSArray`] can only contain
|
||||
/// objects, it is common to wrap pointers or structures like [`NSRange`].
|
||||
///
|
||||
/// Note that creating `NSValue`s is not `unsafe`, but almost all usage of
|
||||
/// it is, since we cannot guarantee that the type that was used to
|
||||
/// construct it is the same as the expected output type.
|
||||
///
|
||||
/// See also the [`NSNumber`] subclass for when you want to wrap numbers.
|
||||
///
|
||||
/// See [Apple's documentation][apple-doc] for more information.
|
||||
///
|
||||
/// [`NSArray`]: super::NSArray
|
||||
/// [`NSRange`]: super::NSRange
|
||||
/// [`NSNumber`]: super::NSNumber
|
||||
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
|
||||
pub struct NSValue;
|
||||
|
||||
unsafe impl ClassType for NSValue {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// We can't implement any auto traits for NSValue, since it can contain an
|
||||
// arbitary object!
|
||||
|
||||
extern_methods!(
|
||||
/// Creation methods.
|
||||
unsafe impl NSValue {
|
||||
// Default / empty new is not provided because `-init` returns `nil` on
|
||||
// Apple and GNUStep throws an exception on all other messages to this
|
||||
// invalid instance.
|
||||
|
||||
/// Create a new `NSValue` containing the given type.
|
||||
///
|
||||
/// Be careful when using this since you may accidentally pass a reference
|
||||
/// when you wanted to pass a concrete type instead.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Create an `NSValue` containing an [`NSPoint`][super::NSPoint].
|
||||
///
|
||||
/// ```
|
||||
/// use objc2::foundation::{NSPoint, NSValue};
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let val = NSValue::new::<NSPoint>(NSPoint::new(1.0, 1.0));
|
||||
/// ```
|
||||
pub fn new<T: 'static + Copy + Encode>(value: T) -> Id<Self, Shared> {
|
||||
let bytes: *const T = &value;
|
||||
let bytes: *const c_void = bytes.cast();
|
||||
let encoding = CString::new(T::ENCODING.to_string()).unwrap();
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![Self::class(), alloc],
|
||||
initWithBytes: bytes,
|
||||
objCType: encoding.as_ptr(),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter methods.
|
||||
unsafe impl NSValue {
|
||||
/// Retrieve the data contained in the `NSValue`.
|
||||
///
|
||||
/// Note that this is broken on GNUStep for some types, see
|
||||
/// [gnustep/libs-base#216].
|
||||
///
|
||||
/// [gnustep/libs-base#216]: https://github.com/gnustep/libs-base/pull/216
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The type of `T` must be what the NSValue actually stores, and any
|
||||
/// safety invariants that the value has must be upheld.
|
||||
///
|
||||
/// Note that it may be, but is not always, enough to simply check whether
|
||||
/// [`contains_encoding`] returns `true`. For example, `NonNull<T>` have
|
||||
/// the same encoding as `*const T`, but `NonNull<T>` is clearly not
|
||||
/// safe to return from this function even if you've checked the encoding
|
||||
/// beforehand.
|
||||
///
|
||||
/// [`contains_encoding`]: Self::contains_encoding
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Store a pointer in `NSValue`, and retrieve it again afterwards.
|
||||
///
|
||||
/// ```
|
||||
/// use std::ffi::c_void;
|
||||
/// use std::ptr;
|
||||
/// use objc2::foundation::NSValue;
|
||||
///
|
||||
/// # #[cfg(feature = "gnustep-1-7")]
|
||||
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
|
||||
/// let val = NSValue::new::<*const c_void>(ptr::null());
|
||||
/// // SAFETY: The value was just created with a pointer
|
||||
/// let res = unsafe { val.get::<*const c_void>() };
|
||||
/// assert!(res.is_null());
|
||||
/// ```
|
||||
pub unsafe fn get<T: 'static + Copy + Encode>(&self) -> T {
|
||||
debug_assert!(
|
||||
self.contains_encoding::<T>(),
|
||||
"wrong encoding. NSValue tried to return something with encoding {}, but the encoding of the given type was {}",
|
||||
self.encoding().unwrap_or("(NULL)"),
|
||||
T::ENCODING,
|
||||
);
|
||||
let mut value = MaybeUninit::<T>::uninit();
|
||||
let ptr: *mut c_void = value.as_mut_ptr().cast();
|
||||
let _: () = unsafe { msg_send![self, getValue: ptr] };
|
||||
// SAFETY: We know that `getValue:` initialized the value, and user
|
||||
// ensures that it is safe to access.
|
||||
unsafe { value.assume_init() }
|
||||
}
|
||||
|
||||
pub fn get_range(&self) -> Option<NSRange> {
|
||||
if self.contains_encoding::<NSRange>() {
|
||||
// SAFETY: We just checked that this contains an NSRange
|
||||
Some(unsafe { msg_send![self, rangeValue] })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_point(&self) -> Option<NSPoint> {
|
||||
if self.contains_encoding::<NSPoint>() {
|
||||
// SAFETY: We just checked that this contains an NSPoint
|
||||
//
|
||||
// Note: The documentation says that `pointValue`, `sizeValue` and
|
||||
// `rectValue` is only available on macOS, but turns out that they
|
||||
// are actually available everywhere!
|
||||
let res = unsafe { msg_send![self, pointValue] };
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> Option<NSSize> {
|
||||
if self.contains_encoding::<NSSize>() {
|
||||
// SAFETY: We just checked that this contains an NSSize
|
||||
let res = unsafe { msg_send![self, sizeValue] };
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rect(&self) -> Option<NSRect> {
|
||||
if self.contains_encoding::<NSRect>() {
|
||||
// SAFETY: We just checked that this contains an NSRect
|
||||
let res = unsafe { msg_send![self, rectValue] };
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encoding(&self) -> Option<&str> {
|
||||
let result: Option<NonNull<c_char>> = unsafe { msg_send![self, objCType] };
|
||||
result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap())
|
||||
}
|
||||
|
||||
pub fn contains_encoding<T: 'static + Copy + Encode>(&self) -> bool {
|
||||
if let Some(encoding) = self.encoding() {
|
||||
T::ENCODING.equivalent_to_str(encoding)
|
||||
} else {
|
||||
panic!("missing NSValue encoding");
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(isEqualToValue:)]
|
||||
fn is_equal_to_value(&self, other: &Self) -> bool;
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSValue {
|
||||
type Ownership = Shared;
|
||||
type Output = NSValue;
|
||||
}
|
||||
|
||||
impl alloc::borrow::ToOwned for NSValue {
|
||||
type Owned = Id<NSValue, Shared>;
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.copy()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NSValue {
|
||||
#[doc(alias = "isEqualToValue:")]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Use isEqualToValue: instaed of isEqual: since it is faster
|
||||
self.is_equal_to_value(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for NSValue {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// Delegate to NSObject
|
||||
(**self).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let enc = self.encoding().unwrap_or("(NULL)");
|
||||
let bytes = &**self; // Delegate to -[NSObject description]
|
||||
f.debug_struct("NSValue")
|
||||
.field("encoding", &enc)
|
||||
.field("bytes", bytes)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::format;
|
||||
use core::{ptr, slice};
|
||||
|
||||
use super::*;
|
||||
use crate::rc::{RcTestObject, ThreadTestData};
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let val = NSValue::new(13u32);
|
||||
assert_eq!(unsafe { val.get::<u32>() }, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_retain() {
|
||||
let obj = RcTestObject::new();
|
||||
let expected = ThreadTestData::current();
|
||||
|
||||
let val = NSValue::new::<*const RcTestObject>(&*obj);
|
||||
expected.assert_current();
|
||||
|
||||
assert!(ptr::eq(unsafe { val.get::<*const RcTestObject>() }, &*obj));
|
||||
expected.assert_current();
|
||||
|
||||
let _clone = val.clone();
|
||||
expected.assert_current();
|
||||
|
||||
let _copy = val.copy();
|
||||
expected.assert_current();
|
||||
|
||||
drop(val);
|
||||
expected.assert_current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let val1 = NSValue::new(123u32);
|
||||
let val2 = NSValue::new(123u32);
|
||||
assert_eq!(val1, val1);
|
||||
assert_eq!(val1, val2);
|
||||
|
||||
let val3 = NSValue::new(456u32);
|
||||
assert_ne!(val1, val3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality_across_types() {
|
||||
let val1 = NSValue::new(123i32);
|
||||
let val2 = NSValue::new(123u32);
|
||||
|
||||
// Test that `objCType` is checked when comparing equality
|
||||
assert_ne!(val1, val2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "the debug output changes depending on OS version"]
|
||||
fn test_debug() {
|
||||
let expected = if cfg!(feature = "gnustep-1-7") {
|
||||
r#"NSValue { encoding: "C", bytes: (C) <ab> }"#
|
||||
} else if cfg!(newer_apple) {
|
||||
r#"NSValue { encoding: "C", bytes: {length = 1, bytes = 0xab} }"#
|
||||
} else {
|
||||
r#"NSValue { encoding: "C", bytes: <ab> }"#
|
||||
};
|
||||
assert_eq!(format!("{:?}", NSValue::new(171u8)), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nsrange() {
|
||||
let range = NSRange::from(1..2);
|
||||
let val = NSValue::new(range);
|
||||
assert_eq!(val.get_range(), Some(range));
|
||||
assert_eq!(val.get_point(), None);
|
||||
assert_eq!(val.get_size(), None);
|
||||
assert_eq!(val.get_rect(), None);
|
||||
// NSValue -getValue is broken on GNUStep for some types
|
||||
#[cfg(not(feature = "gnustep-1-7"))]
|
||||
assert_eq!(unsafe { val.get::<NSRange>() }, range);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nspoint() {
|
||||
let point = NSPoint::new(1.0, 2.0);
|
||||
let val = NSValue::new(point);
|
||||
assert_eq!(val.get_point(), Some(point));
|
||||
#[cfg(not(feature = "gnustep-1-7"))]
|
||||
assert_eq!(unsafe { val.get::<NSPoint>() }, point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nssize() {
|
||||
let point = NSSize::new(1.0, 2.0);
|
||||
let val = NSValue::new(point);
|
||||
assert_eq!(val.get_size(), Some(point));
|
||||
#[cfg(not(feature = "gnustep-1-7"))]
|
||||
assert_eq!(unsafe { val.get::<NSSize>() }, point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nsrect() {
|
||||
let rect = NSRect::new(NSPoint::new(1.0, 2.0), NSSize::new(3.0, 4.0));
|
||||
let val = NSValue::new(rect);
|
||||
assert_eq!(val.get_rect(), Some(rect));
|
||||
#[cfg(not(feature = "gnustep-1-7"))]
|
||||
assert_eq!(unsafe { val.get::<NSRect>() }, rect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_str() {
|
||||
let s = "abc";
|
||||
let val = NSValue::new(s.as_ptr());
|
||||
assert!(val.contains_encoding::<*const u8>());
|
||||
let slice = unsafe { slice::from_raw_parts(val.get(), s.len()) };
|
||||
let s2 = str::from_utf8(slice).unwrap();
|
||||
assert_eq!(s2, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_cstr() {
|
||||
// The following Apple article says that NSValue can't easily store
|
||||
// C-strings, but apparently that doesn't apply to us!
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/NumbersandValues/Articles/Values.html#//apple_ref/doc/uid/20000174-BAJJHDEG>
|
||||
let s = CStr::from_bytes_with_nul(b"test123\0").unwrap();
|
||||
let val = NSValue::new(s.as_ptr());
|
||||
assert!(val.contains_encoding::<*const c_char>());
|
||||
let s2 = unsafe { CStr::from_ptr(val.get()) };
|
||||
assert_eq!(s2, s);
|
||||
}
|
||||
}
|
||||
95
third-party/vendor/objc2/src/foundation/zone.rs
vendored
Normal file
95
third-party/vendor/objc2/src/foundation/zone.rs
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
use core::fmt;
|
||||
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use crate::ffi;
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
use crate::Encode;
|
||||
use crate::{Encoding, RefEncode};
|
||||
|
||||
/// A type used to identify and manage memory zones.
|
||||
///
|
||||
/// Zones are ignored on all newer platforms, you should very rarely need to
|
||||
/// use this, but may be useful if you need to implement `copyWithZone:` or
|
||||
/// `allocWithZone:`.
|
||||
///
|
||||
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nszone?language=objc).
|
||||
pub struct NSZone {
|
||||
// Use `objc_object` to mark the types as !Send, !Sync and UnsafeCell.
|
||||
//
|
||||
// This works since `objc_object` is a ZST
|
||||
_inner: ffi::objc_object,
|
||||
}
|
||||
|
||||
impl fmt::Debug for NSZone {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<NSZone {:p}>", self)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't know anything about the internals of `NSZone`, so best not
|
||||
// to make it `Send` and `Sync` for now.
|
||||
|
||||
impl UnwindSafe for NSZone {}
|
||||
impl RefUnwindSafe for NSZone {}
|
||||
|
||||
unsafe impl RefEncode for NSZone {
|
||||
#[cfg(feature = "apple")]
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct("_NSZone", &[]));
|
||||
#[cfg(feature = "gnustep-1-7")]
|
||||
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct(
|
||||
"_NSZone",
|
||||
&[
|
||||
// Functions
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
// Stats
|
||||
Encoding::Pointer(&Encoding::Unknown),
|
||||
// Zone granularity
|
||||
usize::ENCODING,
|
||||
// Name of zone
|
||||
Encoding::Object,
|
||||
// Next zone - note that the contents of this doesn't matter,
|
||||
// since this is nested far enough that the encoding string just
|
||||
// ends up ignoring it.
|
||||
Encoding::Pointer(&Encoding::Struct("_NSZone", &[])),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
use core::ptr;
|
||||
|
||||
use super::*;
|
||||
use crate::foundation::NSObject;
|
||||
use crate::msg_send_id;
|
||||
use crate::rc::{Allocated, Id, Owned};
|
||||
use crate::ClassType;
|
||||
|
||||
#[test]
|
||||
fn alloc_with_zone() {
|
||||
let zone: *const NSZone = ptr::null();
|
||||
let _obj: Id<Allocated<NSObject>, Owned> =
|
||||
unsafe { msg_send_id![NSObject::class(), allocWithZone: zone] };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_encoding() {
|
||||
let expected = if cfg!(all(feature = "gnustep-1-7", target_pointer_width = "64")) {
|
||||
"^{_NSZone=^?^?^?^?^?^?^?Q@^{_NSZone}}"
|
||||
} else if cfg!(all(
|
||||
feature = "gnustep-1-7",
|
||||
not(target_pointer_width = "64")
|
||||
)) {
|
||||
"^{_NSZone=^?^?^?^?^?^?^?I@^{_NSZone}}"
|
||||
} else {
|
||||
"^{_NSZone=}"
|
||||
};
|
||||
assert_eq!(NSZone::ENCODING_REF.to_string(), expected);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue