Skip to content

Commit 06a5949

Browse files
committed
Future-proof struct encodings
1 parent a5f7814 commit 06a5949

File tree

7 files changed

+119
-94
lines changed

7 files changed

+119
-94
lines changed

crates/objc2-encode/CHANGELOG.md

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

77
## Unreleased - YYYY-MM-DD
88

9+
### Changed
10+
* **BREAKING**: Changed the type of `EncodingBox::Struct` and
11+
`EncodingBox::Union` to no longer contain an `Option`.
12+
13+
### Fixed
14+
* Fixed encoding equivalence between empty structs and unions.
15+
916

1017
## 3.0.0 - 2023-07-31
1118

crates/objc2-encode/src/encoding.rs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ pub enum Encoding {
104104
/// are possible for the type.
105105
///
106106
/// A `BitField(_, Some(_))` and a `BitField(_, None)` do _not_ compare
107-
/// equal; instead, you should set the bitfield depending on the target
108-
/// platform.
107+
/// equal; instead, you should set the bitfield correctly depending on the
108+
/// target platform.
109109
BitField(u8, Option<&'static (u64, Encoding)>),
110110
/// A pointer to the given type.
111111
///
@@ -129,12 +129,18 @@ pub enum Encoding {
129129
/// It is not uncommon for the name to be `"?"`.
130130
///
131131
/// Corresponds to the `"{" name "=" fields... "}"` code.
132+
///
133+
/// Note that the `=` may be omitted in some situations; this is
134+
/// considered equal to the case where there are no fields.
132135
Struct(&'static str, &'static [Encoding]),
133-
/// A union with the given name and fields.
136+
/// A union with the given name and members.
134137
///
135-
/// The order of the fields must match the order of the order in this.
138+
/// The order of the members must match the order of the order in this.
139+
///
140+
/// Corresponds to the `"(" name "=" members... ")"` code.
136141
///
137-
/// Corresponds to the `"(" name "=" fields... ")"` code.
142+
/// Note that the `=` may be omitted in some situations; this is
143+
/// considered equal to the case where there are no members.
138144
Union(&'static str, &'static [Encoding]),
139145
// TODO: "Vector" types have the '!' encoding, but are not implemented in
140146
// clang
@@ -189,6 +195,9 @@ impl Encoding {
189195
/// - Structs or unions behind multiple pointers are considered
190196
/// equivalent, since Objective-C compilers strip this information to
191197
/// avoid unnecessary nesting.
198+
/// - Structs or unions with no fields/members are considered to represent
199+
/// "opqaue" types, and will therefore be equivalent to all other
200+
/// structs / unions.
192201
///
193202
/// The comparison may be changed in the future to e.g. ignore struct
194203
/// names or similar changes that may be required because of limitations
@@ -303,16 +312,16 @@ mod tests {
303312

304313
// Check equivalence comparisons
305314
assert!(E.equivalent_to(&E), "equivalent self");
306-
assert!(E.equivalent_to_str($string), "equivalent self string");
315+
assert!(E.equivalent_to_str($string), "equivalent self string {}", $string);
307316
assert!(E.equivalent_to_box(&boxed), "equivalent self boxed");
308317
$(
309-
assert!(E.equivalent_to(&$equivalent_encoding), "equivalent encoding");
318+
assert!(E.equivalent_to(&$equivalent_encoding), "equivalent encoding {}", $equivalent_encoding);
310319
assert!(E.equivalent_to_str(&$equivalent_encoding.to_string()), "equivalent encoding string");
311320
let boxed = EncodingBox::from_str(&$equivalent_encoding.to_string()).expect("parse equivalent encoding");
312321
assert!(E.equivalent_to_box(&boxed), "equivalent encoding boxed");
313322
)*
314323
$(
315-
assert!(E.equivalent_to_str($equivalent_string), "equivalent string");
324+
assert!(E.equivalent_to_str($equivalent_string), "equivalent string {}", $equivalent_string);
316325
let boxed = EncodingBox::from_str($equivalent_string).expect("parse equivalent string");
317326
assert!(E.equivalent_to_box(&boxed), "equivalent string boxed");
318327
)*
@@ -447,52 +456,61 @@ mod tests {
447456
448457
fn struct_() {
449458
Encoding::Struct("SomeStruct", &[Encoding::Char, Encoding::Int]);
459+
~Encoding::Struct("SomeStruct", &[]);
450460
!Encoding::Union("SomeStruct", &[Encoding::Char, Encoding::Int]);
451461
!Encoding::Int;
452462
!Encoding::Struct("SomeStruct", &[Encoding::Int]);
453463
!Encoding::Struct("SomeStruct", &[Encoding::Char, Encoding::Int, Encoding::Int]);
454464
!Encoding::Struct("SomeStruct", &[Encoding::Int, Encoding::Char]);
455465
!Encoding::Struct("AnotherName", &[Encoding::Char, Encoding::Int]);
456466
"{SomeStruct=ci}";
457-
!"{SomeStruct=ci";
467+
~"{SomeStruct=}";
458468
!"{SomeStruct}";
459-
!"{SomeStruct=}";
469+
!"{SomeStruct=ic}";
470+
!"{SomeStruct=malformed";
460471
}
461472
462473
fn pointer_struct() {
463474
Encoding::Pointer(&Encoding::Struct("SomeStruct", &[Encoding::Char, Encoding::Int]));
475+
~Encoding::Pointer(&Encoding::Struct("SomeStruct", &[]));
464476
!Encoding::Pointer(&Encoding::Struct("SomeStruct", &[Encoding::Int, Encoding::Char]));
465477
!Encoding::Pointer(&Encoding::Struct("AnotherName", &[Encoding::Char, Encoding::Int]));
466478
"^{SomeStruct=ci}";
467-
!"^{SomeStruct=ci";
479+
~"^{SomeStruct=}";
468480
!"^{SomeStruct}";
469-
!"^{SomeStruct=}";
481+
!"^{SomeStruct=ic}";
482+
!"^{SomeStruct=malformed";
470483
}
471484
472485
fn pointer_pointer_struct() {
473486
Encoding::Pointer(&Encoding::Pointer(&Encoding::Struct("SomeStruct", &[Encoding::Char, Encoding::Int])));
487+
~Encoding::Pointer(&Encoding::Pointer(&Encoding::Struct("SomeStruct", &[])));
474488
~Encoding::Pointer(&Encoding::Pointer(&Encoding::Struct("SomeStruct", &[Encoding::Int, Encoding::Char])));
475489
!Encoding::Pointer(&Encoding::Pointer(&Encoding::Struct("AnotherName", &[Encoding::Char, Encoding::Int])));
476490
"^^{SomeStruct}";
477-
!"^^{SomeStruct=ci}";
478-
!"^^{SomeStruct=ii}";
479-
!"^^{SomeStruct=ci";
480491
!"^^{SomeStruct=}";
492+
!"^^{SomeStruct=ci}";
493+
!"^^{SomeStruct=ic}";
494+
!"^^{AnotherName=ic}";
495+
!"^^{SomeStruct=malformed";
481496
}
482497
483498
fn atomic_struct() {
484499
Encoding::Atomic(&Encoding::Struct("SomeStruct", &[Encoding::Char, Encoding::Int]));
500+
~Encoding::Atomic(&Encoding::Struct("SomeStruct", &[]));
485501
~Encoding::Atomic(&Encoding::Struct("SomeStruct", &[Encoding::Int, Encoding::Char]));
486502
!Encoding::Atomic(&Encoding::Struct("AnotherName", &[Encoding::Char, Encoding::Int]));
487503
"A{SomeStruct}";
488-
!"A{SomeStruct=ci}";
489-
!"A{SomeStruct=ci";
490504
!"A{SomeStruct=}";
505+
!"A{SomeStruct=ci}";
506+
!"A{SomeStruct=ic}";
507+
!"A{SomeStruct=malformed";
491508
}
492509
493510
fn empty_struct() {
494511
Encoding::Struct("SomeStruct", &[]);
495512
"{SomeStruct=}";
513+
~"{SomeStruct=ci}";
496514
!"{SomeStruct}";
497515
}
498516
@@ -554,12 +572,25 @@ mod tests {
554572
]
555573
);
556574
"{abc=^[8B](def=@?)^^b255c?}";
575+
~"{abc=}";
576+
!"{abc}";
557577
}
558578
559579
fn identifier() {
560580
Encoding::Struct("_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", &[]);
561581
"{_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=}";
562582
}
583+
584+
// Regression test. The encoding of the `CGLContextObj` object changed
585+
// between versions of macOS. As such, this is something that we must
586+
// be prepared to handle.
587+
fn cgl_context_obj() {
588+
Encoding::Pointer(&Encoding::Struct("_CGLContextObject", &[]));
589+
"^{_CGLContextObject=}";
590+
~"^{_CGLContextObject=^{__GLIContextRec}{__GLIFunctionDispatchRec=^?^?^?^?^?}^{_CGLPrivateObject}^v}";
591+
!"^{_CGLContextObject}";
592+
!"^{SomeOtherStruct=}";
593+
}
563594
}
564595
565596
#[test]
@@ -592,7 +623,7 @@ mod tests {
592623
let parsed = EncodingBox::from_str(s).unwrap();
593624
let expected = EncodingBox::Struct(
594625
"S".to_string(),
595-
Some(vec![EncodingBox::Block, EncodingBox::Block]),
626+
vec![EncodingBox::Block, EncodingBox::Block],
596627
);
597628
assert_eq!(parsed, expected);
598629

crates/objc2-encode/src/encoding_box.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ pub enum EncodingBox {
8585
/// Same as [`Encoding::Array`].
8686
Array(u64, Box<Self>),
8787
/// Same as [`Encoding::Struct`].
88-
Struct(String, Option<Vec<Self>>),
88+
Struct(String, Vec<Self>),
8989
/// Same as [`Encoding::Union`].
90-
Union(String, Option<Vec<Self>>),
90+
Union(String, Vec<Self>),
9191
}
9292

9393
impl EncodingBox {
@@ -188,20 +188,20 @@ mod tests {
188188
));
189189
let enc2 = EncodingBox::Atomic(Box::new(EncodingBox::Struct(
190190
"test".to_string(),
191-
Some(vec![EncodingBox::Array(2, Box::new(EncodingBox::Int))]),
191+
vec![EncodingBox::Array(2, Box::new(EncodingBox::Int))],
192192
)));
193193
let enc3 = EncodingBox::Atomic(Box::new(EncodingBox::Struct(
194194
"test".to_string(),
195-
Some(vec![EncodingBox::Array(2, Box::new(EncodingBox::Char))]),
195+
vec![EncodingBox::Array(2, Box::new(EncodingBox::Char))],
196196
)));
197197
assert_eq!(enc1, enc2);
198198
assert_ne!(enc1, enc3);
199199
}
200200

201201
#[test]
202-
fn ne_struct_with_without_fields() {
203-
let enc1 = EncodingBox::Struct("test".to_string(), Some(vec![EncodingBox::Char]));
204-
let enc2 = EncodingBox::Struct("test".to_string(), None);
202+
fn struct_nested_in_pointer() {
203+
let enc1 = EncodingBox::Struct("test".to_string(), vec![EncodingBox::Char]);
204+
let enc2 = EncodingBox::Struct("test".to_string(), vec![EncodingBox::Int]);
205205
const ENC3A: Encoding = Encoding::Struct("test", &[Encoding::Char]);
206206
assert_ne!(enc1, enc2);
207207
assert!(ENC3A.equivalent_to_box(&enc1));
@@ -225,15 +225,12 @@ mod tests {
225225
#[test]
226226
fn parse_atomic_struct() {
227227
let expected = EncodingBox::Atomic(Box::new(EncodingBox::Atomic(Box::new(
228-
EncodingBox::Struct("a".into(), Some(Vec::new())),
228+
EncodingBox::Struct("a".into(), vec![]),
229229
))));
230230
let actual = EncodingBox::from_str("AA{a=}").unwrap();
231231
assert_eq!(expected, actual);
232232
assert_eq!(expected.to_string(), "AA{a}");
233233

234-
let expected = EncodingBox::Atomic(Box::new(EncodingBox::Atomic(Box::new(
235-
EncodingBox::Struct("a".into(), None),
236-
))));
237234
let actual = EncodingBox::from_str("AA{a}").unwrap();
238235
assert_eq!(expected, actual);
239236
assert_eq!(expected.to_string(), "AA{a}");
@@ -243,7 +240,7 @@ mod tests {
243240
fn parse_part_of_string() {
244241
let mut s = "{a}cb0i16";
245242

246-
let expected = EncodingBox::Struct("a".into(), None);
243+
let expected = EncodingBox::Struct("a".into(), vec![]);
247244
let actual = EncodingBox::from_start_of_str(&mut s).unwrap();
248245
assert_eq!(expected, actual);
249246

crates/objc2-encode/src/helper.rs

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl NestingLevel {
4949
}
5050
}
5151

52-
const fn include_container_fields(self) -> bool {
52+
pub(crate) const fn include_container_fields(self) -> bool {
5353
match self {
5454
Self::Top | Self::Within => true,
5555
Self::Bottom => false,
@@ -80,7 +80,7 @@ pub(crate) fn compare_encodings<E1: EncodingType, E2: EncodingType>(
8080
level2
8181
};
8282

83-
// TODO: Are level1 and level2 ever be different?
83+
// TODO: Are level1 and level2 ever different?
8484

8585
match (enc1.helper(level1), enc2.helper(level2)) {
8686
(Primitive(p1), Primitive(p2)) => p1 == p2,
@@ -106,9 +106,16 @@ pub(crate) fn compare_encodings<E1: EncodingType, E2: EncodingType>(
106106
(Container(kind1, name1, items1, level1), Container(kind2, name2, items2, level2)) => {
107107
kind1 == kind2
108108
&& name1 == name2
109-
&& match (items1, items2) {
110-
(None, None) => true,
111-
(Some(items1), Some(items2)) => {
109+
&& match (
110+
items1,
111+
items2,
112+
level1.include_container_fields() && level2.include_container_fields(),
113+
) {
114+
(_, _, false) => true,
115+
// If one container is empty, then they are equivalent
116+
([], _, true) => true,
117+
(_, [], true) => true,
118+
(items1, items2, true) => {
112119
if items1.len() != items2.len() {
113120
return false;
114121
}
@@ -119,14 +126,6 @@ pub(crate) fn compare_encodings<E1: EncodingType, E2: EncodingType>(
119126
}
120127
true
121128
}
122-
// A bit unsure about this one, but the "safe" default
123-
// here is to say that a container with items does not
124-
// compare equal to another container without items.
125-
//
126-
// Note that this may be confusing, since a `Pointer` to
127-
// the two containers might suddenly start comparing
128-
// equal, but
129-
_ => false,
130129
}
131130
}
132131
(_, _) => false,
@@ -263,7 +262,7 @@ pub(crate) enum Helper<'a, E = Encoding> {
263262
BitField(u8, Option<&'a (u64, E)>, NestingLevel),
264263
Indirection(IndirectionKind, &'a E, NestingLevel),
265264
Array(u64, &'a E, NestingLevel),
266-
Container(ContainerKind, &'a str, Option<&'a [E]>, NestingLevel),
265+
Container(ContainerKind, &'a str, &'a [E], NestingLevel),
267266
}
268267

269268
impl<E: EncodingType> fmt::Display for Helper<'_, E> {
@@ -285,7 +284,7 @@ impl<E: EncodingType> fmt::Display for Helper<'_, E> {
285284
Self::Container(kind, name, items, level) => {
286285
write!(f, "{}", kind.start())?;
287286
write!(f, "{name}")?;
288-
if let Some(items) = items {
287+
if level.include_container_fields() {
289288
write!(f, "=")?;
290289
for item in *items {
291290
write!(f, "{}", item.helper(*level))?;
@@ -333,22 +332,12 @@ impl Helper<'_> {
333332
if !verify_name(name) {
334333
panic!("Struct name was not a valid identifier");
335334
}
336-
let fields = if level.include_container_fields() {
337-
Some(*fields)
338-
} else {
339-
None
340-
};
341335
Self::Container(ContainerKind::Struct, name, fields, level.container())
342336
}
343337
Union(name, members) => {
344338
if !verify_name(name) {
345339
panic!("Union name was not a valid identifier");
346340
}
347-
let members = if level.include_container_fields() {
348-
Some(*members)
349-
} else {
350-
None
351-
};
352341
Self::Container(ContainerKind::Union, name, members, level.container())
353342
}
354343
}
@@ -391,22 +380,12 @@ impl<'a> Helper<'a, EncodingBox> {
391380
if !verify_name(name) {
392381
panic!("Struct name was not a valid identifier");
393382
}
394-
let fields = if level.include_container_fields() {
395-
fields.as_deref()
396-
} else {
397-
None
398-
};
399383
Self::Container(ContainerKind::Struct, name, fields, level.container())
400384
}
401385
Union(name, members) => {
402386
if !verify_name(name) {
403387
panic!("Union name was not a valid identifier");
404388
}
405-
let members = if level.include_container_fields() {
406-
members.as_deref()
407-
} else {
408-
None
409-
};
410389
Self::Container(ContainerKind::Union, name, members, level.container())
411390
}
412391
}

0 commit comments

Comments
 (0)