Skip to content

Commit b47508f

Browse files
authored
Merge pull request #92 from madsmtm/refactor-foundation-macros
Refactor `objc2-foundation` macros
2 parents f921afa + 98e155b commit b47508f

File tree

10 files changed

+214
-108
lines changed

10 files changed

+214
-108
lines changed

objc2-foundation/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
* **BREAKING**: Added associated `Ownership` type to `NSCopying`.
1111
* **BREAKING**: Added associated `Ownership` type to `INSData`.
1212
* **BREAKING**: Added associated `Ownership` type to `INSArray`.
13+
* Added common trait impls (`PartialEq`, `Eq`, `Hash` and `Debug`) to
14+
`NSValue`, `NSDictionary`, `NSArray` and `NSMutableArray`.
1315

1416
### Changed
1517
* **BREAKING**: Made some creation methods a bit less generic (e.g.
1618
`INSDictionary::from_keys_and_objects` now always returns `Id<_, Shared>`).
19+
* Relax bounds on generic `INSObject` impls.
1720

1821
### Removed
1922
* **BREAKING**: Removed associated `Ownership` type from `INSObject`; instead,
2023
it is present on the types that actually need it (for example `NSCopying`).
2124

25+
### Fixed
26+
* Soundness issue with `NSValue`, `NSDictionary`, `NSArray` and
27+
`NSMutableArray` not being `#[repr(C)]`.
28+
2229
## 0.2.0-alpha.2 - 2021-11-22
2330

2431
### Added

objc2-foundation/src/array.rs

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use core::marker::PhantomData;
55
use core::ops::{Index, Range};
66
use core::ptr::NonNull;
77

8+
use objc2::msg_send;
89
use objc2::rc::{Id, Owned, Ownership, Shared, SliceId};
9-
use objc2::runtime::{Class, Object};
10-
use objc2::{class, msg_send};
10+
use objc2::runtime::Object;
1111

1212
use super::{
1313
INSCopying, INSFastEnumeration, INSMutableCopying, INSObject, NSComparisonResult, NSEnumerator,
@@ -158,27 +158,21 @@ pub unsafe trait INSArray: INSObject {
158158
}
159159
}
160160

161-
/// TODO
162-
///
163-
/// You can have a `Id<NSArray<T, Owned>, Owned>`, which allows mutable access
164-
/// to the elements (without modifying the array itself), and
165-
/// `Id<NSArray<T, Shared>, Shared>` which allows sharing the array.
166-
///
167-
/// `Id<NSArray<T, Owned>, Shared>` is possible, but pretty useless.
168-
/// TODO: Can we make it impossible? Should we?
169-
///
170-
/// What about `Id<NSArray<T, Shared>, Owned>`?
171-
pub struct NSArray<T, O: Ownership> {
172-
item: PhantomData<Id<T, O>>,
173-
}
174-
175-
object_impl!(unsafe NSArray<T, O: Ownership>);
176-
177-
unsafe impl<T: INSObject, O: Ownership> INSObject for NSArray<T, O> {
178-
fn class() -> &'static Class {
179-
class!(NSArray)
161+
object!(
162+
/// TODO
163+
///
164+
/// You can have a `Id<NSArray<T, Owned>, Owned>`, which allows mutable access
165+
/// to the elements (without modifying the array itself), and
166+
/// `Id<NSArray<T, Shared>, Shared>` which allows sharing the array.
167+
///
168+
/// `Id<NSArray<T, Owned>, Shared>` is possible, but pretty useless.
169+
/// TODO: Can we make it impossible? Should we?
170+
///
171+
/// What about `Id<NSArray<T, Shared>, Owned>`?
172+
unsafe pub struct NSArray<T, O: Ownership> {
173+
item: PhantomData<Id<T, O>>,
180174
}
181-
}
175+
);
182176

183177
unsafe impl<T: INSObject, O: Ownership> INSArray for NSArray<T, O> {
184178
/// The `NSArray` itself (length and number of items) is always immutable,
@@ -319,17 +313,11 @@ pub unsafe trait INSMutableArray: INSArray {
319313
}
320314
}
321315

322-
pub struct NSMutableArray<T, O: Ownership> {
323-
item: PhantomData<Id<T, O>>,
324-
}
325-
326-
object_impl!(unsafe NSMutableArray<T, O: Ownership>);
327-
328-
unsafe impl<T: INSObject, O: Ownership> INSObject for NSMutableArray<T, O> {
329-
fn class() -> &'static Class {
330-
class!(NSMutableArray)
316+
object!(
317+
unsafe pub struct NSMutableArray<T, O: Ownership> {
318+
item: PhantomData<Id<T, O>>,
331319
}
332-
}
320+
);
333321

334322
unsafe impl<T: INSObject, O: Ownership> INSArray for NSMutableArray<T, O> {
335323
type Ownership = Owned;
@@ -364,14 +352,15 @@ impl<T: INSObject, O: Ownership> Index<usize> for NSMutableArray<T, O> {
364352

365353
#[cfg(test)]
366354
mod tests {
355+
use alloc::format;
367356
use alloc::vec;
368357
use alloc::vec::Vec;
369358

370359
use objc2::msg_send;
371360
use objc2::rc::{autoreleasepool, Id, Owned, Shared};
372361

373362
use super::{INSArray, INSMutableArray, NSArray, NSMutableArray};
374-
use crate::{INSObject, INSString, NSObject, NSString};
363+
use crate::{INSObject, INSString, INSValue, NSObject, NSString, NSValue};
375364

376365
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
377366
let mut vec = Vec::with_capacity(len);
@@ -381,6 +370,14 @@ mod tests {
381370
NSArray::from_vec(vec)
382371
}
383372

373+
fn sample_number_array(len: u8) -> Id<NSArray<NSValue<u8>, Shared>, Shared> {
374+
let mut vec = Vec::with_capacity(len as usize);
375+
for i in 0..len {
376+
vec.push(NSValue::new(i));
377+
}
378+
NSArray::from_vec(vec)
379+
}
380+
384381
fn retain_count<T: INSObject>(obj: &T) -> usize {
385382
unsafe { msg_send![obj, retainCount] }
386383
}
@@ -394,6 +391,33 @@ mod tests {
394391
assert_eq!(array.len(), 4);
395392
}
396393

394+
#[test]
395+
fn test_equality() {
396+
let array1 = sample_array(3);
397+
let array2 = sample_array(3);
398+
assert_ne!(array1, array2);
399+
400+
let array1 = sample_number_array(3);
401+
let array2 = sample_number_array(3);
402+
assert_eq!(array1, array2);
403+
404+
let array1 = sample_number_array(3);
405+
let array2 = sample_number_array(4);
406+
assert_ne!(array1, array2);
407+
}
408+
409+
#[test]
410+
#[ignore = "Output is different depending on OS version and runtime"]
411+
fn test_debug() {
412+
let obj = sample_number_array(3);
413+
let expected = r#"(
414+
"<00>",
415+
"<01>",
416+
"<02>"
417+
)"#;
418+
assert_eq!(format!("{:?}", obj), format!("{:?}", expected));
419+
}
420+
397421
#[test]
398422
fn test_get() {
399423
let array = sample_array(4);

objc2-foundation/src/data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub unsafe trait INSData: INSObject {
9292
}
9393
}
9494

95-
object_struct!(unsafe NSData);
95+
object!(unsafe pub struct NSData);
9696

9797
unsafe impl INSData for NSData {
9898
type Ownership = Shared;
@@ -155,7 +155,7 @@ pub unsafe trait INSMutableData: INSData {
155155
}
156156
}
157157

158-
object_struct!(unsafe NSMutableData);
158+
object!(unsafe pub struct NSMutableData);
159159

160160
unsafe impl INSData for NSMutableData {
161161
type Ownership = Owned;

objc2-foundation/src/dictionary.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ use core::marker::PhantomData;
44
use core::ops::Index;
55
use core::ptr::{self, NonNull};
66

7+
use objc2::msg_send;
78
use objc2::rc::{Id, Owned, Ownership, Shared, SliceId};
8-
use objc2::runtime::Class;
9-
use objc2::{class, msg_send};
109

1110
use super::{INSCopying, INSFastEnumeration, INSObject, NSArray, NSEnumerator};
1211

@@ -139,23 +138,17 @@ pub unsafe trait INSDictionary: INSObject {
139138
}
140139
}
141140

142-
pub struct NSDictionary<K, V> {
143-
key: PhantomData<Id<K, Shared>>,
144-
obj: PhantomData<Id<V, Owned>>,
145-
}
146-
147-
object_impl!(unsafe NSDictionary<K, V>);
141+
object!(
142+
unsafe pub struct NSDictionary<K, V> {
143+
key: PhantomData<Id<K, Shared>>,
144+
obj: PhantomData<Id<V, Owned>>,
145+
}
146+
);
148147

149148
impl<K: INSObject, V: INSObject> NSDictionary<K, V> {
150149
unsafe_def_fn!(pub fn new -> Shared);
151150
}
152151

153-
unsafe impl<K: INSObject, V: INSObject> INSObject for NSDictionary<K, V> {
154-
fn class() -> &'static Class {
155-
class!(NSDictionary)
156-
}
157-
}
158-
159152
unsafe impl<K: INSObject, V: INSObject> INSDictionary for NSDictionary<K, V> {
160153
type Key = K;
161154
type Value = V;

objc2-foundation/src/macros.rs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,79 @@
66
/// and it's instances must have the raw encoding `Encoding::Object` (an
77
/// example: `NSAutoreleasePool` does not have this). Finally the ownership
88
/// must be correct for this class.
9-
macro_rules! object_struct {
10-
(unsafe $name:ident) => {
9+
macro_rules! object {
10+
(
11+
$(#[$m:meta])*
12+
unsafe $v:vis struct $name:ident
13+
) => {
14+
object!($(#[$m])* unsafe $v struct $name<> {});
15+
};
16+
(
17+
$(#[$m:meta])*
18+
unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident)?),*> {
19+
$($p:ident: $pty:ty,)*
20+
}
21+
) => {
1122
// TODO: `extern type`
23+
$(#[$m])*
1224
#[repr(C)]
13-
pub struct $name {
25+
$v struct $name<$($t $(: $b)?),*> {
1426
_private: [u8; 0],
27+
$($p: $pty),*
1528
}
1629

17-
unsafe impl ::objc2::Message for $name {}
30+
unsafe impl<$($t $(: $b)?),*> ::objc2::Message for $name<$($t),*> { }
1831

19-
unsafe impl ::objc2::RefEncode for $name {
32+
unsafe impl<$($t $(: $b)?),*> ::objc2::RefEncode for $name<$($t),*> {
2033
const ENCODING_REF: ::objc2::Encoding<'static> = ::objc2::Encoding::Object;
2134
}
2235

23-
unsafe impl $crate::INSObject for $name {
36+
unsafe impl<$($t $(: $b)?),*> $crate::INSObject for $name<$($t),*> {
2437
fn class() -> &'static ::objc2::runtime::Class {
2538
::objc2::class!($name)
2639
}
2740
}
2841

29-
impl ::core::cmp::PartialEq for $name {
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]
3054
fn eq(&self, other: &Self) -> bool {
3155
use $crate::INSObject;
3256
self.is_equal(other)
3357
}
3458
}
3559

36-
impl ::core::cmp::Eq for $name {}
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),*> {}
3765

38-
impl ::core::hash::Hash for $name {
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]
3974
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
4075
use $crate::INSObject;
4176
self.hash_code().hash(state);
4277
}
4378
}
4479

45-
impl ::core::fmt::Debug for $name {
80+
// TODO: Consider T: Debug bound
81+
impl<$($t $(: $b)?),*> ::core::fmt::Debug for $name<$($t),*> {
4682
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4783
use $crate::{INSObject, INSString};
4884
::objc2::rc::autoreleasepool(|pool| {
@@ -53,27 +89,6 @@ macro_rules! object_struct {
5389
};
5490
}
5591

56-
/// TODO
57-
///
58-
/// # Safety
59-
///
60-
/// The given type must be valid as an Objective-C object. TODO: More.
61-
macro_rules! object_impl {
62-
(unsafe $name:ident) => (
63-
object_impl!(unsafe $name,);
64-
);
65-
(unsafe $name:ident<$($t:ident$(: $b:ident)?),+>) => (
66-
object_impl!(unsafe $name, $($t$(: $b)?),+);
67-
);
68-
(unsafe $name:ident, $($t:ident$(: $b:ident)?),*) => (
69-
unsafe impl<$($t$(:($b))?),*> ::objc2::Message for $name<$($t),*> { }
70-
71-
unsafe impl<$($t$(: $b)?),*> ::objc2::RefEncode for $name<$($t),*> {
72-
const ENCODING_REF: ::objc2::Encoding<'static> = ::objc2::Encoding::Object;
73-
}
74-
);
75-
}
76-
7792
macro_rules! unsafe_def_fn {
7893
($v:vis fn new -> $o:ty) => {
7994
$v fn new() -> Id<Self, $o> {

objc2-foundation/src/object.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub unsafe trait INSObject: Sized + Message {
3737
}
3838
}
3939

40-
object_struct!(unsafe NSObject);
40+
object!(unsafe pub struct NSObject);
4141

4242
impl NSObject {
4343
unsafe_def_fn!(pub fn new -> Owned);
@@ -46,18 +46,15 @@ impl NSObject {
4646
#[cfg(test)]
4747
mod tests {
4848
use super::{INSObject, NSObject};
49-
use crate::{INSString, NSString};
49+
use crate::NSString;
5050
use alloc::format;
51-
use objc2::rc::autoreleasepool;
5251

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

5957
let obj2 = NSObject::new();
60-
assert!(!obj1.is_equal(&*obj2));
6158
assert_ne!(obj1, obj2);
6259
}
6360

@@ -68,16 +65,10 @@ mod tests {
6865
}
6966

7067
#[test]
71-
fn test_description() {
68+
fn test_debug() {
7269
let obj = NSObject::new();
73-
let description = obj.description();
7470
let expected = format!("<NSObject: {:p}>", &*obj);
75-
autoreleasepool(|pool| {
76-
assert_eq!(description.as_str(pool), &*expected);
77-
});
78-
79-
let expected = format!("\"<NSObject: {:p}>\"", &*obj);
80-
assert_eq!(format!("{:?}", obj), expected);
71+
assert_eq!(format!("{:?}", obj), format!("{:?}", expected));
8172
}
8273

8374
#[test]

0 commit comments

Comments
 (0)