From d74d7cbd9a8964530087cdd839a8449f563e1340 Mon Sep 17 00:00:00 2001 From: Zach Leytus <22868934+zatchl@users.noreply.github.com> Date: Tue, 16 Aug 2022 17:26:24 -0700 Subject: [PATCH] Add `NSSet` and `NSMutableSet` --- objc2/src/foundation/mod.rs | 11 + objc2/src/foundation/mutable_set.rs | 368 ++++++++++ objc2/src/foundation/set.rs | 680 ++++++++++++++++++ test-ui/ui/msg_send_id_invalid_return.stderr | 6 +- .../ui/msg_send_super_not_classtype.stderr | 4 +- 5 files changed, 1064 insertions(+), 5 deletions(-) create mode 100644 objc2/src/foundation/mutable_set.rs create mode 100644 objc2/src/foundation/set.rs diff --git a/objc2/src/foundation/mod.rs b/objc2/src/foundation/mod.rs index faf186b86..740df61ae 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,12 @@ mod tests { assert_auto_traits::(); assert_auto_traits::(); assert_auto_traits::>(); + assert_auto_traits::>(); + assert_auto_traits::>(); + assert_auto_traits::, Shared>>(); + assert_auto_traits::, Shared>>(); + assert_auto_traits::, Owned>>(); + assert_auto_traits::, Owned>>(); // TODO: Figure out if Send + Sync is safe? // assert_auto_traits::>(); // assert_auto_traits::>>(); @@ -170,6 +180,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..2df2a161b --- /dev/null +++ b/objc2/src/foundation/mutable_set.rs @@ -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 { + p: PhantomData<*mut ()>, + } + + unsafe impl ClassType for NSMutableSet { + #[inherits(NSObject)] + type Super = NSSet; + } +); + +// SAFETY: Same as NSSet +unsafe impl Sync for NSMutableSet {} +unsafe impl Send for NSMutableSet {} +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}; + /// # #[cfg(feature = "gnustep-1-7")] + /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; + /// + /// 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}; + /// # #[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 { + // SAFETY: + // We always return `Id, 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) -> Vec> { + set.into_iter() + .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) + .collect() + } + } + + unsafe impl NSMutableSet { + /// 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]) -> 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 + // 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 NSMutableSet { + #[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) -> bool { + // SAFETY: + // We take `Id` 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`, 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 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, O: Ownership> 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 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); + } +} diff --git a/objc2/src/foundation/set.rs b/objc2/src/foundation/set.rs new file mode 100644 index 000000000..f13f772df --- /dev/null +++ b/objc2/src/foundation/set.rs @@ -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 { + item: PhantomData>, + notunwindsafe: PhantomData<&'static mut ()>, + } + + unsafe impl ClassType for NSSet { + type Super = NSObject; + } +); + +// SAFETY: Same as NSArray +unsafe impl Sync for NSSet {} +unsafe impl Send for NSSet {} +unsafe impl Sync for NSSet {} +unsafe impl Send for NSSet {} + +// SAFETY: Same as NSArray +impl RefUnwindSafe for NSSet {} +impl UnwindSafe 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}; + /// # #[cfg(feature = "gnustep-1-7")] + /// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; + /// + /// 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`. + // - `O` don't actually matter here! E.g. `NSSet` 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 { + // 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, Owned>` is safe to return when + // we receive `Vec>`. + 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::::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) -> Vec> { + set.into_iter() + .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) + .collect() + } + } + + unsafe impl NSSet { + /// 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]) -> 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 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, 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 NSSet { + /// 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) -> 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) -> bool { + other.is_subset(self) + } + + #[sel(intersectsSet:)] + fn intersects_set(&self, other: &NSSet) -> 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) -> bool { + !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, O: Ownership> 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_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::::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(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::::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::::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::::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] + #[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(); + } +} diff --git a/test-ui/ui/msg_send_id_invalid_return.stderr b/test-ui/ui/msg_send_id_invalid_return.stderr index 78858c967..739912a40 100644 --- a/test-ui/ui/msg_send_id_invalid_return.stderr +++ b/test-ui/ui/msg_send_id_invalid_return.stderr @@ -32,7 +32,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSError NSException NSMutableArray - and 11 others + and 13 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, objc2::runtime::Class, Shared>` error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied @@ -53,7 +53,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSError NSException NSMutableArray - and 11 others + and 13 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, objc2::runtime::Class, Shared>` error[E0277]: the trait bound `&objc2::runtime::Object: MaybeUnwrap, _>` is not satisfied @@ -90,7 +90,7 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied NSError NSException NSMutableArray - and 11 others + and 13 others = note: required for `RetainSemantics` to implement `MsgSendId<&objc2::runtime::Class, Allocated, Shared>` error[E0277]: the trait bound `Id: MaybeUnwrap, _>` is not satisfied diff --git a/test-ui/ui/msg_send_super_not_classtype.stderr b/test-ui/ui/msg_send_super_not_classtype.stderr index 25040fad1..66ea1422c 100644 --- a/test-ui/ui/msg_send_super_not_classtype.stderr +++ b/test-ui/ui/msg_send_super_not_classtype.stderr @@ -16,7 +16,7 @@ error[E0277]: the trait bound `objc2::runtime::Object: ClassType` is not satisfi NSException NSMutableArray NSMutableAttributedString - and 9 others + and 11 others note: required by a bound in `__send_super_message_static` --> $WORKSPACE/objc2/src/message/mod.rs | @@ -41,7 +41,7 @@ error[E0277]: the trait bound `objc2::runtime::Object: ClassType` is not satisfi NSException NSMutableArray NSMutableAttributedString - and 9 others + and 11 others note: required by a bound in `__send_super_message_static` --> $WORKSPACE/objc2/src/message/mod.rs |