Skip to content

Commit

Permalink
Merge pull request #641 from madsmtm/no-eq-hash
Browse files Browse the repository at this point in the history
Don't require `Eq + Hash` for `NSSet` values / `NSDictionary` keys
  • Loading branch information
madsmtm authored Aug 30, 2024
2 parents d67142c + 364fa49 commit 2e45639
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 130 deletions.
5 changes: 5 additions & 0 deletions crates/objc2/src/topics/about_generated/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* `objc2-foundation`: `MainThreadMarker::from` now debug-asserts that it is
actually running on the main thread.
* `objc2-foundation`: Added `NSData::to_vec` and `NSData::iter` helper methods.
* `objc2-foundation`: Added `Eq` implementation for `NSValue` and subclasses.

### Changed
* `objc2-foundation`: Allow using `MainThreadBound` without the `NSThread`
Expand All @@ -41,6 +42,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* `objc2-foundation` **BREAKING**: Renamed the `bytes[_mut]` methods on
`NSData` to `as[_mut]_slice_unchecked`, and made them `unsafe`, since the
data can no longer ensure that it is not mutated while the bytes are in use.
* No longer require `Eq + Hash` for `NSDictionary` keys and `NSSet` values,
since it was overly restrictive.
* Removed `NSSet` methods `contains`, `is_subset`, `is_superset` and
`is_disjoint` that were simple wrappers over the original methods.

### Deprecated
* `objc2-foundation`: Moved `MainThreadMarker` to `objc2`.
Expand Down
19 changes: 8 additions & 11 deletions framework-crates/objc2-foundation/src/dictionary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use alloc::vec::Vec;
#[cfg(feature = "NSObject")]
use core::cmp::min;
use core::fmt;
use core::hash::Hash;
use core::mem;
use core::ops::{Index, IndexMut};
use core::ptr::{self, NonNull};
Expand Down Expand Up @@ -38,7 +37,7 @@ where
}

#[cfg(feature = "NSObject")]
impl<K: Message + Eq + Hash, V: Message> NSDictionary<K, V> {
impl<K: Message, V: Message> NSDictionary<K, V> {
// The dictionary copies its keys, which is why we require `NSCopying` and
// use `CounterpartOrSelf` on all input data - we want to ensure that the
// type-system knows that it's not actually `NSMutableString` that is
Expand Down Expand Up @@ -102,7 +101,7 @@ impl<K: Message + Eq + Hash, V: Message> NSDictionary<K, V> {
}

#[cfg(feature = "NSObject")]
impl<K: Message + Eq + Hash, V: Message> NSMutableDictionary<K, V> {
impl<K: Message, V: Message> NSMutableDictionary<K, V> {
pub fn from_vec<Q>(keys: &[&Q], mut objects: Vec<Retained<V>>) -> Retained<Self>
where
Q: Message + NSCopying + CounterpartOrSelf<Immutable = K>,
Expand Down Expand Up @@ -159,7 +158,7 @@ impl<K: Message + Eq + Hash, V: Message> NSMutableDictionary<K, V> {
// let's just use a simple normal reference.

extern_methods!(
unsafe impl<K: Message + Eq + Hash, V: Message> NSDictionary<K, V> {
unsafe impl<K: Message, V: Message> NSDictionary<K, V> {
/// Returns a reference to the value corresponding to the key.
///
/// # Examples
Expand Down Expand Up @@ -289,7 +288,7 @@ impl<K: Message, V: Message> NSDictionary<K, V> {
}
}

impl<K: Message + Eq + Hash, V: Message> NSMutableDictionary<K, V> {
impl<K: Message, V: Message> NSMutableDictionary<K, V> {
/// Inserts a key-value pair into the dictionary.
///
/// If the dictionary did not have this key present, None is returned.
Expand Down Expand Up @@ -557,31 +556,29 @@ mod iter_helpers {
#[cfg(feature = "NSEnumerator")]
pub use self::iter_helpers::*;

impl<'a, K: Message + Eq + Hash, V: Message> Index<&'a K> for NSDictionary<K, V> {
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<'a, K: Message + Eq + Hash, V: Message> Index<&'a K> for NSMutableDictionary<K, V> {
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 + Eq + Hash, V: Message + IsMutable> IndexMut<&'a K> for NSDictionary<K, V> {
impl<'a, K: Message, V: Message + IsMutable> IndexMut<&'a K> for NSDictionary<K, V> {
fn index_mut<'s>(&'s mut self, index: &'a K) -> &'s mut V {
self.get_mut(index).unwrap()
}
}

impl<'a, K: Message + Eq + Hash, V: Message + IsMutable> IndexMut<&'a K>
for NSMutableDictionary<K, V>
{
impl<'a, K: Message, V: Message + IsMutable> 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()
}
Expand Down
104 changes: 13 additions & 91 deletions framework-crates/objc2-foundation/src/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use alloc::vec::Vec;
#[cfg(feature = "NSEnumerator")]
use core::fmt;
use core::hash::Hash;

use objc2::mutability::{IsIdCloneable, IsRetainable};
use objc2::rc::{Retained, RetainedFromIterator};
Expand Down Expand Up @@ -45,7 +44,7 @@ impl<T: Message> NSSet<T> {
}
}

impl<T: Message + Eq + Hash> NSSet<T> {
impl<T: Message> NSSet<T> {
/// Creates an [`NSSet`] from a vector.
///
/// # Examples
Expand Down Expand Up @@ -157,7 +156,7 @@ impl<T: Message + Eq + Hash> NSSet<T> {
}
}

impl<T: Message + Eq + Hash> NSMutableSet<T> {
impl<T: Message> NSMutableSet<T> {
/// Creates an [`NSMutableSet`] from a vector.
///
/// # Examples
Expand Down Expand Up @@ -247,23 +246,7 @@ extern_methods!(
pub fn get_any(&self) -> Option<&T>;
}

unsafe impl<T: Message + Eq + Hash> NSSet<T> {
/// Returns `true` if the set contains a value.
///
/// # Examples
///
/// ```
/// use objc2_foundation::{ns_string, NSSet};
///
/// let strs = [ns_string!("one"), ns_string!("two"), ns_string!("three")];
/// let set = NSSet::from_slice(&strs);
/// assert!(set.contains(ns_string!("one")));
/// ```
#[doc(alias = "containsObject:")]
pub fn contains(&self, value: &T) -> bool {
unsafe { self.containsObject(value) }
}

unsafe impl<T: Message> NSSet<T> {
/// Returns a reference to the value in the set, if any, that is equal
/// to the given value.
///
Expand Down Expand Up @@ -292,66 +275,10 @@ extern_methods!(

// Note: No `get_mut` method exposed on sets, since their objects'
// hashes are immutable.

/// 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::{ns_string, NSSet};
///
/// let set1 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two")]);
/// let set2 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two"), ns_string!("three")]);
///
/// assert!(set1.is_subset(&set2));
/// assert!(!set2.is_subset(&set1));
/// ```
#[doc(alias = "isSubsetOfSet:")]
pub fn is_subset(&self, other: &NSSet<T>) -> bool {
unsafe { self.isSubsetOfSet(other) }
}

/// 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::{ns_string, NSSet};
///
/// let set1 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two")]);
/// let set2 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two"), ns_string!("three")]);
///
/// assert!(!set1.is_superset(&set2));
/// assert!(set2.is_superset(&set1));
/// ```
pub fn is_superset(&self, other: &NSSet<T>) -> bool {
other.is_subset(self)
}

/// Returns `true` if `self` has no elements in common with `other`.
///
/// # Examples
///
/// ```
/// use objc2_foundation::{ns_string, NSSet};
///
/// let set1 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two")]);
/// let set2 = NSSet::from_slice(&[ns_string!("one"), ns_string!("two"), ns_string!("three")]);
/// let set3 = NSSet::from_slice(&[ns_string!("four"), ns_string!("five"), ns_string!("six")]);
///
/// assert!(!set1.is_disjoint(&set2));
/// assert!(set1.is_disjoint(&set3));
/// assert!(set2.is_disjoint(&set3));
/// ```
pub fn is_disjoint(&self, other: &NSSet<T>) -> bool {
!unsafe { self.intersectsSet(other) }
}
}
);

impl<T: Message + Eq + Hash> NSMutableSet<T> {
impl<T: Message> NSMutableSet<T> {
/// Add a value to the set. Returns whether the value was
/// newly inserted.
///
Expand All @@ -372,7 +299,7 @@ impl<T: Message + Eq + Hash> NSMutableSet<T> {
where
T: IsRetainable,
{
let contains_value = self.contains(value);
let contains_value = self.containsObject(value);
// SAFETY: Because of the `T: IsRetainable` bound, it is safe for the
// set to retain the object here.
unsafe { self.addObject(value) };
Expand All @@ -395,7 +322,7 @@ impl<T: Message + Eq + Hash> NSMutableSet<T> {
/// ```
#[doc(alias = "addObject:")]
pub fn insert_id(&mut self, value: Retained<T>) -> bool {
let contains_value = self.contains(&value);
let contains_value = self.containsObject(&value);
// SAFETY: We've consumed ownership of the object.
unsafe { self.addObject(&value) };
!contains_value
Expand All @@ -417,15 +344,12 @@ impl<T: Message + Eq + Hash> NSMutableSet<T> {
/// ```
#[doc(alias = "removeObject:")]
pub fn remove(&mut self, value: &T) -> bool {
let contains_value = self.contains(value);
let contains_value = self.containsObject(value);
unsafe { self.removeObject(value) };
contains_value
}
}

// Iteration is not supposed to touch the elements, not even do comparisons.
//
// TODO: Verify that this is actually the case.
impl<T: Message> NSSet<T> {
/// An iterator visiting all elements in arbitrary order.
///
Expand Down Expand Up @@ -550,46 +474,44 @@ impl<T: fmt::Debug + Message> fmt::Debug for crate::Foundation::NSCountedSet<T>
}
}

impl<T: Message + Eq + Hash> Extend<Retained<T>> for NSMutableSet<T> {
impl<T: Message> Extend<Retained<T>> for NSMutableSet<T> {
fn extend<I: IntoIterator<Item = Retained<T>>>(&mut self, iter: I) {
iter.into_iter().for_each(move |item| {
self.insert_id(item);
});
}
}

impl<'a, T: Message + Eq + Hash + IsRetainable> Extend<&'a T> for NSMutableSet<T> {
impl<'a, T: Message + IsRetainable> Extend<&'a T> for NSMutableSet<T> {
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
iter.into_iter().for_each(move |item| {
self.insert(item);
});
}
}

impl<'a, T: Message + Eq + Hash + IsRetainable + 'a> RetainedFromIterator<&'a T> for NSSet<T> {
impl<'a, T: Message + IsRetainable + 'a> RetainedFromIterator<&'a T> for NSSet<T> {
fn id_from_iter<I: IntoIterator<Item = &'a T>>(iter: I) -> Retained<Self> {
let vec = Vec::from_iter(iter);
Self::from_slice(&vec)
}
}

impl<T: Message + Eq + Hash> RetainedFromIterator<Retained<T>> for NSSet<T> {
impl<T: Message> RetainedFromIterator<Retained<T>> for NSSet<T> {
fn id_from_iter<I: IntoIterator<Item = Retained<T>>>(iter: I) -> Retained<Self> {
let vec = Vec::from_iter(iter);
Self::from_vec(vec)
}
}

impl<'a, T: Message + Eq + Hash + IsRetainable + 'a> RetainedFromIterator<&'a T>
for NSMutableSet<T>
{
impl<'a, T: Message + IsRetainable + 'a> RetainedFromIterator<&'a T> for NSMutableSet<T> {
fn id_from_iter<I: IntoIterator<Item = &'a T>>(iter: I) -> Retained<Self> {
let vec = Vec::from_iter(iter);
Self::from_slice(&vec)
}
}

impl<T: Message + Eq + Hash> RetainedFromIterator<Retained<T>> for NSMutableSet<T> {
impl<T: Message> RetainedFromIterator<Retained<T>> for NSMutableSet<T> {
fn id_from_iter<I: IntoIterator<Item = Retained<T>>>(iter: I) -> Retained<Self> {
let vec = Vec::from_iter(iter);
Self::from_vec(vec)
Expand Down
Loading

0 comments on commit 2e45639

Please sign in to comment.