Skip to content

Commit 060ddc3

Browse files
committed
Add bounds in macro to fix NSValue<f32> being Eq
1 parent d6f7096 commit 060ddc3

File tree

7 files changed

+97
-8
lines changed

7 files changed

+97
-8
lines changed

objc2-foundation/src/array.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ mod tests {
359359
use objc2::rc::{autoreleasepool, Id, Owned, Shared};
360360

361361
use super::{INSArray, INSMutableArray, NSArray, NSMutableArray};
362-
use crate::{INSObject, INSString, NSObject, NSString};
362+
use crate::{INSObject, INSString, INSValue, NSObject, NSString, NSValue};
363363

364364
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
365365
let mut vec = Vec::with_capacity(len);
@@ -369,6 +369,14 @@ mod tests {
369369
NSArray::from_vec(vec)
370370
}
371371

372+
fn sample_number_array(len: u8) -> Id<NSArray<NSValue<u8>, Shared>, Shared> {
373+
let mut vec = Vec::with_capacity(len as usize);
374+
for i in 0..len {
375+
vec.push(NSValue::new(i));
376+
}
377+
NSArray::from_vec(vec)
378+
}
379+
372380
fn retain_count<T: INSObject>(obj: &T) -> usize {
373381
unsafe { msg_send![obj, retainCount] }
374382
}
@@ -382,6 +390,21 @@ mod tests {
382390
assert_eq!(array.len(), 4);
383391
}
384392

393+
#[test]
394+
fn test_equality() {
395+
let array1 = sample_array(3);
396+
let array2 = sample_array(3);
397+
assert_ne!(array1, array2);
398+
399+
let array1 = sample_number_array(3);
400+
let array2 = sample_number_array(3);
401+
assert_eq!(array1, array2);
402+
403+
let array1 = sample_number_array(3);
404+
let array2 = sample_number_array(4);
405+
assert_ne!(array1, array2);
406+
}
407+
385408
#[test]
386409
fn test_get() {
387410
let array = sample_array(4);

objc2-foundation/src/macros.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,45 @@ macro_rules! object {
3939
}
4040
}
4141

42-
impl<$($t $(: $b)?),*> ::core::cmp::PartialEq for $name<$($t),*> {
42+
// Objective-C equality has approximately the same semantics as Rust
43+
// equality (although less aptly specified).
44+
//
45+
// At the very least, equality is _expected_ to be symmetric and
46+
// transitive, and that's about the best we can do.
47+
//
48+
// `T: PartialEq` bound added because e.g. `NSArray` does deep
49+
// (instead of shallow) equality comparisons.
50+
//
51+
// See also https://nshipster.com/equality/
52+
impl<$($t: ::core::cmp::PartialEq $(+ $b)?),*> ::core::cmp::PartialEq for $name<$($t),*> {
53+
#[inline]
4354
fn eq(&self, other: &Self) -> bool {
4455
use $crate::INSObject;
4556
self.is_equal(other)
4657
}
4758
}
4859

49-
impl<$($t $(: $b)?),*> ::core::cmp::Eq for $name<$($t),*> {}
60+
// Most types' equality is reflexive.
61+
//
62+
// `T: Eq` bound added to prevent e.g. `NSValue<f32>` from being `Eq`
63+
// (even though `[NAN isEqual: NAN]` is true in Objective-C).
64+
impl<$($t: ::core::cmp::Eq $(+ $b)?),*> ::core::cmp::Eq for $name<$($t),*> {}
5065

51-
impl<$($t $(: $b)?),*> ::core::hash::Hash for $name<$($t),*> {
66+
// Hashing in Objective-C has the exact same requirement as in Rust:
67+
//
68+
// > If two objects are equal (as determined by the isEqual: method),
69+
// > they must have the same hash value.
70+
//
71+
// See https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash
72+
impl<$($t: ::core::hash::Hash $(+ $b)?),*> ::core::hash::Hash for $name<$($t),*> {
73+
#[inline]
5274
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
5375
use $crate::INSObject;
5476
self.hash_code().hash(state);
5577
}
5678
}
5779

80+
// TODO: Consider T: Debug bound
5881
impl<$($t $(: $b)?),*> ::core::fmt::Debug for $name<$($t),*> {
5982
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
6083
use $crate::{INSObject, INSString};

objc2-foundation/src/object.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,11 @@ mod tests {
5151
use objc2::rc::autoreleasepool;
5252

5353
#[test]
54-
fn test_is_equal() {
54+
fn test_equality() {
5555
let obj1 = NSObject::new();
56-
assert!(obj1.is_equal(&*obj1));
57-
assert_eq!(obj1, obj1); // Using forwarding impl on Id
56+
assert_eq!(obj1, obj1);
5857

5958
let obj2 = NSObject::new();
60-
assert!(!obj1.is_equal(&*obj2));
6159
assert_ne!(obj1, obj2);
6260
}
6361

objc2-foundation/src/string.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ mod tests {
129129
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
130130
}
131131

132+
#[test]
133+
fn test_equality() {
134+
let s1 = NSString::from_str("abc");
135+
let s2 = NSString::from_str("abc");
136+
assert_eq!(s1, s1);
137+
assert_eq!(s1, s2);
138+
139+
let s3 = NSString::from_str("def");
140+
assert_ne!(s1, s3);
141+
}
142+
132143
#[test]
133144
fn test_empty() {
134145
let s1 = NSString::from_str("");

objc2-foundation/src/value.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,18 @@ unsafe impl<T: 'static> INSCopying for NSValue<T> {
8484
type Output = NSValue<T>;
8585
}
8686

87+
/// ```compile_fail
88+
/// use objc2_foundation::NSValue;
89+
/// fn needs_eq<T: Eq>() {}
90+
/// needs_eq::<NSValue<f32>>();
91+
/// ```
92+
#[cfg(doctest)]
93+
pub struct NSValueFloatNotEq;
94+
8795
#[cfg(test)]
8896
mod tests {
8997
use crate::{INSValue, NSRange, NSValue};
98+
use objc2::rc::{Id, Shared};
9099
use objc2::Encode;
91100

92101
#[test]
@@ -96,6 +105,27 @@ mod tests {
96105
assert!(&u32::ENCODING == val.encoding().unwrap());
97106
}
98107

108+
#[test]
109+
fn test_equality() {
110+
let val1 = NSValue::new(123u32);
111+
let val2 = NSValue::new(123u32);
112+
assert_eq!(val1, val1);
113+
assert_eq!(val1, val2);
114+
115+
let val3 = NSValue::new(456u32);
116+
assert_ne!(val1, val3);
117+
}
118+
119+
#[test]
120+
fn test_equality_across_types() {
121+
let val1 = NSValue::new(123);
122+
let val2: Id<NSValue<u32>, Shared> = NSValue::new(123);
123+
let val2: Id<NSValue<u8>, Shared> = unsafe { core::mem::transmute(val2) };
124+
125+
// Test that `objCType` is checked when comparing equality
126+
assert_ne!(val1, val2);
127+
}
128+
99129
#[test]
100130
fn test_value_nsrange() {
101131
let val = NSValue::new(NSRange::from(1..2));

objc2/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88

99
### Added
1010
* Export `objc-sys` as `ffi` module.
11+
* Added common trait impls on `rc::Owned` and `rc::Shared` (useful in generic
12+
contexts).
1113

1214
### Changed
1315
* Deprecated `runtime::BOOL`, `runtime::YES` and `runtime::NO`. Use the

objc2/src/rc/ownership.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/// A type used to mark that a struct owns the object(s) it contains,
22
/// so it has the sole references to them.
3+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
34
pub enum Owned {}
45

56
/// A type used to mark that the object(s) a struct contains are shared,
67
/// so there may be other references to them.
8+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
79
pub enum Shared {}
810

911
mod private {

0 commit comments

Comments
 (0)