Skip to content

Commit 886544b

Browse files
authored
Merge pull request #91 from madsmtm/encode-fixes
`Encoding` comparison fixes
2 parents 7a3cb1f + aa1f48a commit 886544b

File tree

10 files changed

+110
-46
lines changed

10 files changed

+110
-46
lines changed

objc2-encode/CHANGELOG.md

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

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* `Encoding::equivalent_to`, `Encoding::equivalent_to_str` and
11+
`Encoding::equivalent_to_start_of_str` methods for more precise comparison
12+
semantics.
13+
14+
### Changed
15+
* Discourage comparing `str` with `Encoding` using `PartialEq`. This trait
16+
impl might get removed in a future version.
17+
918

1019
## 2.0.0-beta.0 - 2021-11-22
1120

objc2-encode/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,21 @@ unsafe impl Encode for MyObject {
4242
);
4343
}
4444

45-
assert_eq!(&MyObject::ENCODING, "{MyObject=fs}");
45+
assert!(MyObject::ENCODING.equivalent_to_str("{MyObject=fs}"));
4646

4747
unsafe impl RefEncode for MyObject {
4848
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
4949
}
5050

51-
assert_eq!(&MyObject::ENCODING_REF, "^{MyObject=fs}");
51+
assert!(MyObject::ENCODING_REF.equivalent_to_str("^{MyObject=fs}"));
5252
```
5353

5454
An `Encoding` can be compared with an encoding string from the Objective-C
5555
runtime:
5656

5757
```rust
5858
use objc2_encode::Encode;
59-
assert!(&i32::ENCODING == "i");
59+
assert!(i32::ENCODING.equivalent_to_str("i"));
6060
```
6161

6262
`Encoding` implements `Display` as its string representation. This can be

objc2-encode/examples/ns_uinteger.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ unsafe impl RefEncode for NSUInteger {
2626
}
2727

2828
fn main() {
29-
assert_eq!(&NSUInteger::ENCODING, "Q");
30-
assert_eq!(&<&NSUInteger>::ENCODING, "^Q");
29+
assert!(NSUInteger::ENCODING.equivalent_to_str("Q"));
30+
assert!(<&NSUInteger>::ENCODING.equivalent_to_str("^Q"));
31+
assert!(<&NSUInteger>::ENCODING.equivalent_to_str("r^Q"));
3132
}

objc2-encode/examples/opaque_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ unsafe impl RefEncode for NSDecimal {
2828
}
2929

3030
fn main() {
31-
assert_eq!(&NSDecimal::ENCODING_REF, "^{?=cCCC[38C]}");
31+
assert!(NSDecimal::ENCODING_REF.equivalent_to_str("^{?=cCCC[38C]}"));
3232
// Does not compile:
3333
// println!("{:?}", NSDecimal::ENCODING);
3434
}

objc2-encode/src/encoding.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,39 @@ pub enum Encoding<'a> {
106106
// NSLog(@"Encoding: %s", @encode(const int*)); // -> r^i
107107
}
108108

109+
impl Encoding<'_> {
110+
/// Check if one encoding is equivalent to another.
111+
pub fn equivalent_to(&self, other: &Self) -> bool {
112+
// For now, because we don't allow representing qualifiers
113+
self == other
114+
}
115+
116+
/// Check if an encoding is equivalent to the given string representation.
117+
pub fn equivalent_to_str(&self, s: &str) -> bool {
118+
// if the given encoding can be successfully removed from the start
119+
// and an empty string remains, they were fully equivalent!
120+
if let Some(res) = self.equivalent_to_start_of_str(s) {
121+
res.is_empty()
122+
} else {
123+
false
124+
}
125+
}
126+
127+
/// Check if an encoding is equivalent to the start of the given string
128+
/// representation.
129+
///
130+
/// If it is equivalent, the remaining part of the string is returned.
131+
/// Otherwise this returns [`None`].
132+
pub fn equivalent_to_start_of_str<'a>(&self, s: &'a str) -> Option<&'a str> {
133+
// strip leading qualifiers
134+
let s = s.trim_start_matches(parse::QUALIFIERS);
135+
136+
// TODO: Allow missing/"?" names in structs and unions?
137+
138+
parse::rm_enc_prefix(s, self)
139+
}
140+
}
141+
109142
impl fmt::Display for Encoding<'_> {
110143
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111144
use Encoding::*;
@@ -162,15 +195,37 @@ impl fmt::Display for Encoding<'_> {
162195
}
163196
}
164197

198+
// TODO: Deprecate and remove these PartialEq impls
199+
200+
/// Partial equality between an [`Encoding`] and a [`str`].
201+
///
202+
/// Using this is heavily discouraged, since it is not transitive; use
203+
/// [`Encoding::equivalent_to_str`] instead for more correct semantics.
165204
impl PartialEq<str> for Encoding<'_> {
205+
/// Using this is discouraged.
166206
fn eq(&self, other: &str) -> bool {
167-
parse::eq_enc(other, self)
207+
self.equivalent_to_str(other)
208+
}
209+
210+
/// Using this is discouraged.
211+
fn ne(&self, other: &str) -> bool {
212+
!self.eq(other)
168213
}
169214
}
170215

216+
/// Partial equality between an [`Encoding`] and a [`str`].
217+
///
218+
/// Using this is heavily discouraged, since it is not transitive; use
219+
/// [`Encoding::equivalent_to_str`] instead for more correct semantics.
171220
impl PartialEq<Encoding<'_>> for str {
221+
/// Using this is discouraged.
172222
fn eq(&self, other: &Encoding<'_>) -> bool {
173-
parse::eq_enc(self, other)
223+
other.equivalent_to_str(self)
224+
}
225+
226+
/// Using this is discouraged.
227+
fn ne(&self, other: &Encoding<'_>) -> bool {
228+
!self.eq(other)
174229
}
175230
}
176231

@@ -183,14 +238,14 @@ mod tests {
183238
fn test_array_display() {
184239
let e = Encoding::Array(12, &Encoding::Int);
185240
assert_eq!(e.to_string(), "[12i]");
186-
assert_eq!(&e, "[12i]");
241+
assert!(e.equivalent_to_str("[12i]"));
187242
}
188243

189244
#[test]
190245
fn test_pointer_display() {
191246
let e = Encoding::Pointer(&Encoding::Int);
192247
assert_eq!(e.to_string(), "^i");
193-
assert_eq!(&e, "^i");
248+
assert!(e.equivalent_to_str("^i"));
194249
}
195250

196251
#[test]
@@ -205,7 +260,7 @@ mod tests {
205260
#[test]
206261
fn test_int_display() {
207262
assert_eq!(Encoding::Int.to_string(), "i");
208-
assert_eq!(&Encoding::Int, "i");
263+
assert!(Encoding::Int.equivalent_to_str("i"));
209264
}
210265

211266
#[test]
@@ -221,7 +276,7 @@ mod tests {
221276
fn test_struct_display() {
222277
let s = Encoding::Struct("CGPoint", &[Encoding::Char, Encoding::Int]);
223278
assert_eq!(s.to_string(), "{CGPoint=ci}");
224-
assert_eq!(&s, "{CGPoint=ci}");
279+
assert!(s.equivalent_to_str("{CGPoint=ci}"));
225280
}
226281

227282
#[test]
@@ -235,7 +290,7 @@ mod tests {
235290
fn test_union_display() {
236291
let u = Encoding::Union("Onion", &[Encoding::Char, Encoding::Int]);
237292
assert_eq!(u.to_string(), "(Onion=ci)");
238-
assert_eq!(&u, "(Onion=ci)");
293+
assert!(u.equivalent_to_str("(Onion=ci)"));
239294
}
240295

241296
#[test]

objc2-encode/src/parse.rs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::Encoding;
55

6-
const QUALIFIERS: &[char] = &[
6+
pub(crate) const QUALIFIERS: &[char] = &[
77
'r', // const
88
'n', // in
99
'N', // inout
@@ -13,7 +13,7 @@ const QUALIFIERS: &[char] = &[
1313
'V', // oneway
1414
];
1515

16-
fn rm_enc_prefix<'a>(s: &'a str, enc: &Encoding<'_>) -> Option<&'a str> {
16+
pub(crate) fn rm_enc_prefix<'a>(s: &'a str, enc: &Encoding<'_>) -> Option<&'a str> {
1717
use Encoding::*;
1818
let code = match *enc {
1919
Char => "c",
@@ -93,15 +93,6 @@ fn rm_int_prefix(s: &str, other: usize) -> Option<&str> {
9393
chomp_int(s).and_then(|(n, t)| if other == n { Some(t) } else { None })
9494
}
9595

96-
pub(crate) fn eq_enc(s: &str, enc: &Encoding<'_>) -> bool {
97-
// strip qualifiers
98-
let s = s.trim_start_matches(QUALIFIERS);
99-
100-
// if the given encoding can be successfully removed from the start
101-
// and an empty string remains, they were equal!
102-
rm_enc_prefix(s, enc).map_or(false, str::is_empty)
103-
}
104-
10596
#[cfg(test)]
10697
mod tests {
10798
use super::*;
@@ -116,26 +107,31 @@ mod tests {
116107
Encoding::Int,
117108
],
118109
);
119-
assert!(eq_enc("{A={B=ci}ci}", &enc));
120-
assert!(!eq_enc("{A={B=ci}ci", &enc));
110+
assert!(enc.equivalent_to_str("{A={B=ci}ci}"));
111+
assert_eq!(
112+
enc.equivalent_to_start_of_str("{A={B=ci}ci}def"),
113+
Some("def")
114+
);
115+
assert!(!enc.equivalent_to_str("{A={B=ci}ci"));
121116
}
122117

123118
#[test]
124119
fn test_bitfield() {
125-
assert!(eq_enc("b32", &Encoding::BitField(32)));
126-
assert!(!eq_enc("b", &Encoding::BitField(32)));
127-
assert!(!eq_enc("b-32", &Encoding::BitField(32)));
120+
assert!(Encoding::BitField(32).equivalent_to_str("b32"));
121+
assert!(!Encoding::BitField(32).equivalent_to_str("b32a"));
122+
assert!(!Encoding::BitField(32).equivalent_to_str("b"));
123+
assert!(!Encoding::BitField(32).equivalent_to_str("b-32"));
128124
}
129125

130126
#[test]
131127
fn test_qualifiers() {
132-
assert!(eq_enc("Vv", &Encoding::Void));
133-
assert!(eq_enc("r*", &Encoding::String));
128+
assert!(Encoding::Void.equivalent_to_str("Vv"));
129+
assert!(Encoding::String.equivalent_to_str("r*"));
134130
}
135131

136132
#[test]
137133
fn test_unicode() {
138134
let fields = &[Encoding::Char, Encoding::Int];
139-
assert!(eq_enc("{☃=ci}", &Encoding::Struct("☃", fields)));
135+
assert!(Encoding::Struct("☃", fields).equivalent_to_str("{☃=ci}"));
140136
}
141137
}

objc2-foundation/src/value.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ pub unsafe trait INSValue: INSObject {
2929
fn get(&self) -> Self::Value {
3030
if let Some(encoding) = self.encoding() {
3131
// TODO: This can be a debug assertion (?)
32-
assert!(&Self::Value::ENCODING == encoding, "Wrong encoding");
32+
assert!(
33+
Self::Value::ENCODING.equivalent_to_str(encoding),
34+
"Wrong encoding"
35+
);
3336
unsafe { self.get_unchecked() }
3437
} else {
3538
panic!("Missing NSValue encoding");
@@ -108,7 +111,7 @@ mod tests {
108111
fn test_value() {
109112
let val = NSValue::new(13u32);
110113
assert_eq!(val.get(), 13);
111-
assert!(&u32::ENCODING == val.encoding().unwrap());
114+
assert!(u32::ENCODING.equivalent_to_str(val.encoding().unwrap()));
112115
}
113116

114117
#[test]
@@ -162,7 +165,7 @@ mod tests {
162165
#[test]
163166
fn test_value_nsrange() {
164167
let val = NSValue::new(NSRange::from(1..2));
165-
assert!(&NSRange::ENCODING == val.encoding().unwrap());
168+
assert!(NSRange::ENCODING.equivalent_to_str(val.encoding().unwrap()));
166169
let range: NSRange = unsafe { objc2::msg_send![val, rangeValue] };
167170
assert_eq!(range, NSRange::from(1..2));
168171
// NSValue -getValue is broken on GNUStep for some types

objc2/examples/introspection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ fn main() {
3232
let hash_method = cls.instance_method(hash_sel).unwrap();
3333
let hash_return = hash_method.return_type();
3434
println!("-[NSObject hash] return type: {:?}", hash_return);
35-
assert!(*hash_return == usize::ENCODING);
35+
assert!(usize::ENCODING.equivalent_to_str(&hash_return));
3636

3737
// Invoke a method on the object
3838
let hash: usize = unsafe { msg_send![obj, hash] };

objc2/src/message/verify.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ where
7070

7171
let ret = R::ENCODING;
7272
let expected_ret = method.return_type();
73-
if ret != *expected_ret {
73+
if !ret.equivalent_to_str(&*expected_ret) {
7474
return Err(VerificationError::MismatchedReturn(method, ret));
7575
}
7676

@@ -85,7 +85,7 @@ where
8585

8686
for (i, arg) in self_and_cmd.iter().chain(args).copied().enumerate() {
8787
let expected = method.argument_type(i).unwrap();
88-
if arg != *expected {
88+
if !arg.equivalent_to_str(&*expected) {
8989
return Err(VerificationError::MismatchedArgument(method, i, arg));
9090
}
9191
}

objc2/src/runtime.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ impl RefUnwindSafe for Protocol {}
490490
fn get_ivar_offset<T: Encode>(cls: &Class, name: &str) -> isize {
491491
match cls.instance_variable(name) {
492492
Some(ivar) => {
493-
assert!(ivar.type_encoding() == &T::ENCODING);
493+
assert!(T::ENCODING.equivalent_to_str(ivar.type_encoding()));
494494
ivar.offset()
495495
}
496496
None => panic!("Ivar {} not found on class {:?}", name, cls),
@@ -604,7 +604,7 @@ mod tests {
604604
let cls = test_utils::custom_class();
605605
let ivar = cls.instance_variable("_foo").unwrap();
606606
assert_eq!(ivar.name(), "_foo");
607-
assert!(ivar.type_encoding() == &<u32>::ENCODING);
607+
assert!(<u32>::ENCODING.equivalent_to_str(ivar.type_encoding()));
608608
assert!(ivar.offset() > 0);
609609

610610
let ivars = cls.instance_variables();
@@ -618,8 +618,8 @@ mod tests {
618618
let method = cls.instance_method(sel).unwrap();
619619
assert_eq!(method.name().name(), "foo");
620620
assert_eq!(method.arguments_count(), 2);
621-
assert!(*method.return_type() == <u32>::ENCODING);
622-
assert!(*method.argument_type(1).unwrap() == Sel::ENCODING);
621+
assert!(<u32>::ENCODING.equivalent_to_str(&method.return_type()));
622+
assert!(Sel::ENCODING.equivalent_to_str(&method.argument_type(1).unwrap()));
623623

624624
let methods = cls.instance_methods();
625625
assert!(methods.len() > 0);
@@ -697,10 +697,10 @@ mod tests {
697697

698698
#[test]
699699
fn test_encode() {
700-
assert!(<&Object>::ENCODING.to_string() == "@");
701-
assert!(<*mut Object>::ENCODING.to_string() == "@");
702-
assert!(<&Class>::ENCODING.to_string() == "#");
703-
assert!(Sel::ENCODING.to_string() == ":");
700+
assert_eq!(<&Object>::ENCODING.to_string(), "@");
701+
assert_eq!(<*mut Object>::ENCODING.to_string(), "@");
702+
assert_eq!(<&Class>::ENCODING.to_string(), "#");
703+
assert_eq!(Sel::ENCODING.to_string(), ":");
704704
}
705705

706706
#[test]

0 commit comments

Comments
 (0)