diff --git a/objc2/src/foundation/mod.rs b/objc2/src/foundation/mod.rs index faf186b86..0739748c8 100644 --- a/objc2/src/foundation/mod.rs +++ b/objc2/src/foundation/mod.rs @@ -64,11 +64,13 @@ pub use self::geometry::{CGFloat, 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_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 @@ -104,11 +106,13 @@ mod geometry; mod mutable_array; mod mutable_attributed_string; mod mutable_data; +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 @@ -158,6 +162,7 @@ mod tests { assert_auto_traits::(); assert_auto_traits::(); assert_auto_traits::>(); + assert_auto_traits::>(); // TODO: Figure out if Send + Sync is safe? // assert_auto_traits::>(); // assert_auto_traits::>>(); @@ -170,6 +175,7 @@ mod tests { assert_auto_traits::>(); assert_auto_traits::(); assert_auto_traits::(); + assert_auto_traits::>(); assert_auto_traits::(); assert_auto_traits::(); // assert_auto_traits::(); // Intentional diff --git a/objc2/src/foundation/mutable_set.rs b/objc2/src/foundation/mutable_set.rs new file mode 100644 index 000000000..a9650b793 --- /dev/null +++ b/objc2/src/foundation/mutable_set.rs @@ -0,0 +1,291 @@ +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, 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 { + item: PhantomData>, + } + + unsafe impl ClassType for NSMutableSet { + #[inherits(NSObject)] + type Super = NSSet; + } +); + +// SAFETY: Same as NSSet +unsafe impl Sync for NSMutableSet {} +unsafe impl Send for NSMutableSet {} + +extern_methods!( + unsafe impl NSMutableSet { + /// Creates an empty `NSMutableSet`. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSMutableSet, NSString}; + /// + /// let set = NSMutableSet::::new(); + /// ``` + pub fn new() -> Id { + // 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}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec(); + /// let set = NSMutableSet::from_vec(strs); + /// ``` + pub fn from_vec(vec: Vec>) -> Id { + // SAFETY: + // We always return `Id, Owned>` because + // mutable sets are always unique and allow adding/removing elements + // but prevent modifying elements. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } + } + + /// Creates an `NSMutableSet` from a slice. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSMutableSet, NSString}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str); + /// let set = NSMutableSet::from_slice(&strs); + /// ``` + pub fn from_slice(slice: &[Id]) -> Id { + // SAFETY: + // Taking `&T` would not be sound, since the `&T` could come + // from an `Id` that would now no longer be owned! + // + // We always return `Id, Owned>` because + // mutable sets are always unique and allow adding/removing elements + // but prevent modifying elements. + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } + } + + /// Adds a value to the set. Returns whether the value was + /// newly inserted. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSMutableSet, NSString}; + /// + /// 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) -> bool { + let contains_value = self.contains(&value); + // SAFETY: The object is not nil + unsafe { msg_send![self, addObject: &*value] } + !contains_value + } + + /// Removes a value from the set. Returns whether the value was + /// present in the set. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSMutableSet, NSString}; + /// + /// let mut set = NSMutableSet::new(); + /// + /// set.insert(NSString::from_str("one")); + /// assert_eq!(set.remove(&NSString::from_str("one")), true); + /// assert_eq!(set.remove(&NSString::from_str("one")), false); + /// ``` + #[doc(alias = "removeObject:")] + pub fn remove(&mut self, value: &T) -> bool { + let contains_value = self.contains(value); + unsafe { msg_send![self, removeObject: value] } + contains_value + } + + /// Clears the set, removing all values. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSMutableSet, NSString}; + /// + /// 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); + } +); + +unsafe impl NSCopying for NSMutableSet { + type Ownership = Shared; + type Output = NSSet; +} + +unsafe impl NSMutableCopying for NSMutableSet { + type Output = NSMutableSet; +} + +impl alloc::borrow::ToOwned for NSMutableSet { + type Owned = Id, Owned>; + fn to_owned(&self) -> Self::Owned { + self.mutable_copy() + } +} + +unsafe impl NSFastEnumeration for NSMutableSet { + type Item = T; +} + +impl<'a, T: Message> IntoIterator for &'a NSMutableSet { + type Item = &'a T; + type IntoIter = NSFastEnumerator<'a, NSMutableSet>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_fast() + } +} + +impl Extend> for NSMutableSet { + fn extend>>(&mut self, iter: I) { + for item in iter { + self.insert(item); + } + } +} + +impl DefaultId for NSMutableSet { + type Ownership = Owned; + + #[inline] + fn default_id() -> Id { + Self::new() + } +} + +impl fmt::Debug for NSMutableSet { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::foundation::NSString; + 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(&NSString::from_str("one"))); + assert!(!set.remove(&NSString::from_str("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_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); + } +} diff --git a/objc2/src/foundation/set.rs b/objc2/src/foundation/set.rs new file mode 100644 index 000000000..ffc2de7f8 --- /dev/null +++ b/objc2/src/foundation/set.rs @@ -0,0 +1,575 @@ +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, 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 { + item: PhantomData>, + } + + unsafe impl ClassType for NSSet { + type Super = NSObject; + } +); + +// SAFETY: Same as NSArray +unsafe impl Sync for NSSet {} +unsafe impl Send for NSSet {} + +// SAFETY: Same as NSArray +impl RefUnwindSafe for NSSet {} +impl UnwindSafe for NSSet {} + +#[track_caller] +pub(crate) unsafe fn with_objects( + cls: &Class, + objects: &[&T], +) -> Id { + unsafe { + msg_send_id![ + msg_send_id![cls, alloc], + initWithObjects: objects.as_ptr(), + count: objects.len() + ] + } +} + +extern_methods!( + unsafe impl NSSet { + /// Creates an empty `NSSet`. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// let set = NSSet::::new(); + /// ``` + pub fn new() -> Id { + // SAFETY: + // - `new` may not create a new object, but instead return a shared + // instance. We remedy this by returning `Id`. + unsafe { msg_send_id![Self::class(), new] } + } + + /// Creates an `NSSet` from a vector. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec(); + /// let set = NSSet::from_vec(strs); + /// ``` + pub fn from_vec(vec: Vec>) -> Id { + // SAFETY: + // We always return `Id, Shared>`, even when we + // receive a `Vec>, because both the `NSSet` itself + // and its elements should be immutable. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } + } + + /// Creates an `NSSet` from a slice. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str); + /// let set = NSSet::from_slice(&strs); + /// ``` + pub fn from_slice(slice: &[Id]) -> Id { + // SAFETY: + // Taking `&T` would not be sound, since the `&T` could come + // from an `Id` that would now no longer be owned! + // + // We always return `Id, Shared>` because both + // the `NSSet` itself and its elements should be immutable. + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } + } + + /// Returns the number of elements in the set. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// 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}; + /// + /// let set = NSSet::::new(); + /// assert!(set.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns a reference to the value in the set, if any, that is equal to the given value. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str); + /// let set = NSSet::from_slice(&strs); + /// assert_eq!(set.get(&NSString::from_str("one")), Some(strs[0].as_ref())); + /// assert_eq!(set.get(&NSString::from_str("four")), None); + /// ``` + #[doc(alias = "member:")] + #[sel(member:)] + pub fn get(&self, value: &T) -> Option<&T>; + + /// Returns a reference to one of the objects in the set, or `None` if the set is empty. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// 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].as_ref() || any == strs[1].as_ref() || any == strs[2].as_ref()); + /// ``` + #[doc(alias = "anyObject")] + #[sel(anyObject)] + pub fn get_any(&self) -> Option<&T>; + + /// Returns `true` if the set contains a value. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// let strs = ["one", "two", "three"].map(NSString::from_str); + /// let set = NSSet::from_slice(&strs); + /// assert!(set.contains(&NSString::from_str("one"))); + /// ``` + #[doc(alias = "containsObject:")] + #[sel(containsObject:)] + pub fn contains(&self, value: &T) -> bool; + + /// An iterator visiting all elements in arbitrary order. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// 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 an `NSArray` containing the set's elements, or an empty array + /// if the set is empty. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSNumber, NSSet, NSString}; + /// + /// 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, Shared> { + // SAFETY: + // We return an `Id, Shared> to make sure + // the elements of the `NSSet` cannot be modified. + unsafe { msg_send_id![self, allObjects] } + } + + /// 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}; + /// + /// 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) -> 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}; + /// + /// 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) -> bool { + other.is_subset(self) + } + + #[sel(intersectsSet:)] + unsafe fn intersects_set(&self, other: &NSSet) -> bool; + + /// Returns `true` if `self` has no elements in common with `other`. + /// + /// # Examples + /// + /// ``` + /// use objc2::foundation::{NSSet, NSString}; + /// + /// 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) -> bool { + !unsafe { self.intersects_set(other) } + } + } +); + +unsafe impl NSCopying for NSSet { + type Ownership = Shared; + type Output = NSSet; +} + +unsafe impl NSMutableCopying for NSSet { + type Output = NSMutableSet; +} + +impl alloc::borrow::ToOwned for NSSet { + type Owned = Id, Shared>; + fn to_owned(&self) -> Self::Owned { + self.copy() + } +} + +unsafe impl NSFastEnumeration for NSSet { + type Item = T; +} + +impl<'a, T: Message> IntoIterator for &'a NSSet { + type Item = &'a T; + type IntoIter = NSFastEnumerator<'a, NSSet>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_fast() + } +} + +impl DefaultId for NSSet { + type Ownership = Shared; + + #[inline] + fn default_id() -> Id { + Self::new() + } +} + +impl fmt::Debug for NSSet { + #[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; + + use super::*; + use crate::foundation::{NSNumber, NSString}; + use crate::rc::{Owned, RcTestObject, ThreadTestData}; + + #[test] + fn test_new() { + let set = NSSet::::new(); + assert!(set.is_empty()); + } + + #[test] + fn test_from_vec() { + let set = NSSet::::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::::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::::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::::new(); + assert!(set.get(&NSString::from_str("one")).is_none()); + + let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str)); + assert!(set.get(&NSString::from_str("two")).is_some()); + assert!(set.get(&NSString::from_str("three")).is_none()); + } + + #[test] + fn test_get_any() { + let set = NSSet::::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].as_ref() || any == strs[1].as_ref() || any == strs[2].as_ref()); + } + + #[test] + fn test_contains() { + let set = NSSet::::new(); + assert!(!set.contains(&NSString::from_str("one"))); + + let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str)); + assert!(set.contains(&NSString::from_str("one"))); + assert!(!set.contains(&NSString::from_str("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_equality() { + let set1 = NSSet::::new(); + let set2 = NSSet::::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::::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] + 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 += if cfg!(feature = "gnustep-1-7") { 0 } else { 1 }; + expected.assert_current(); + + assert_eq!(iter.count(), 1); + expected.autorelease += if cfg!(feature = "gnustep-1-7") { 0 } else { 1 }; + expected.assert_current(); + + let iter = set.iter_fast(); + assert_eq!(iter.count(), 1); + expected.assert_current(); + } +}