Skip to content

Commit 06b56d3

Browse files
committed
Split a few macro helpers out into separate files
1 parent c998df2 commit 06b56d3

28 files changed

+857
-837
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/// Helper for specifying the retain semantics for a given selector family.
2+
///
3+
/// Note that we can't actually check if a method is in a method family; only
4+
/// whether the _selector_ is in a _selector_ family.
5+
///
6+
/// The slight difference here is:
7+
/// - The method may be annotated with the `objc_method_family` attribute,
8+
/// which would cause it to be in a different family. That this is not the
9+
/// case is part of the `unsafe` contract of `msg_send_id!`.
10+
/// - The method may not obey the added restrictions of the method family.
11+
/// The added restrictions are:
12+
/// - `new`, `alloc`, `copy` and `mutableCopy`: The method must return a
13+
/// retainable object pointer type - we ensure this by making
14+
/// `message_send_id` return `Id`.
15+
/// - `init`: The method must be an instance method and must return an
16+
/// Objective-C pointer type - We ensure this by taking
17+
/// `Option<Allocated<T>>`, which means it can't be a class method!
18+
///
19+
/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments>
20+
// TODO: Use an enum instead of u8 here when stable
21+
#[derive(Debug)]
22+
pub struct RetainSemantics<const INNER: u8> {}
23+
24+
pub type New = RetainSemantics<1>;
25+
pub type Alloc = RetainSemantics<2>;
26+
pub type Init = RetainSemantics<3>;
27+
pub type CopyOrMutCopy = RetainSemantics<4>;
28+
pub type Other = RetainSemantics<5>;
29+
30+
pub const fn retain_semantics(selector: &str) -> u8 {
31+
let selector = selector.as_bytes();
32+
match (
33+
in_selector_family(selector, b"new"),
34+
in_selector_family(selector, b"alloc"),
35+
in_selector_family(selector, b"init"),
36+
in_selector_family(selector, b"copy"),
37+
in_selector_family(selector, b"mutableCopy"),
38+
) {
39+
(true, false, false, false, false) => 1,
40+
(false, true, false, false, false) => 2,
41+
(false, false, true, false, false) => 3,
42+
(false, false, false, true, false) => 4,
43+
(false, false, false, false, true) => 4,
44+
(false, false, false, false, false) => 5,
45+
_ => unreachable!(),
46+
}
47+
}
48+
49+
/// Checks whether a given selector is said to be in a given selector family.
50+
///
51+
/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families>
52+
const fn in_selector_family(mut selector: &[u8], mut family: &[u8]) -> bool {
53+
// Skip leading underscores from selector
54+
loop {
55+
selector = match selector {
56+
[b'_', rest @ ..] => rest,
57+
_ => break,
58+
}
59+
}
60+
61+
// Compare each character
62+
loop {
63+
(selector, family) = match (selector, family) {
64+
// Remaining items
65+
([s, selector @ ..], [f, family @ ..]) => {
66+
if *s == *f {
67+
// Next iteration
68+
(selector, family)
69+
} else {
70+
// Family does not begin with selector
71+
return false;
72+
}
73+
}
74+
// Equal
75+
([], []) => {
76+
return true;
77+
}
78+
// Selector can't be part of familiy if smaller than it
79+
([], _) => {
80+
return false;
81+
}
82+
// Remaining items in selector
83+
// -> ensure next character is not lowercase
84+
([s, ..], []) => {
85+
return !s.is_ascii_lowercase();
86+
}
87+
}
88+
}
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use alloc::string::ToString;
94+
95+
use super::*;
96+
97+
#[test]
98+
fn test_in_selector_family() {
99+
#[track_caller]
100+
fn assert_in_family(selector: &str, family: &str) {
101+
assert!(in_selector_family(selector.as_bytes(), family.as_bytes()));
102+
let selector = selector.to_string() + "\0";
103+
assert!(in_selector_family(selector.as_bytes(), family.as_bytes()));
104+
}
105+
106+
#[track_caller]
107+
fn assert_not_in_family(selector: &str, family: &str) {
108+
assert!(!in_selector_family(selector.as_bytes(), family.as_bytes()));
109+
let selector = selector.to_string() + "\0";
110+
assert!(!in_selector_family(selector.as_bytes(), family.as_bytes()));
111+
}
112+
113+
// Common cases
114+
115+
assert_in_family("alloc", "alloc");
116+
assert_in_family("allocWithZone:", "alloc");
117+
assert_not_in_family("dealloc", "alloc");
118+
assert_not_in_family("initialize", "init");
119+
assert_not_in_family("decimalNumberWithDecimal:", "init");
120+
assert_in_family("initWithCapacity:", "init");
121+
assert_in_family("_initButPrivate:withParam:", "init");
122+
assert_not_in_family("description", "init");
123+
assert_not_in_family("inIT", "init");
124+
125+
assert_not_in_family("init", "copy");
126+
assert_not_in_family("copyingStuff:", "copy");
127+
assert_in_family("copyWithZone:", "copy");
128+
assert_not_in_family("initWithArray:copyItems:", "copy");
129+
assert_in_family("copyItemAtURL:toURL:error:", "copy");
130+
131+
assert_not_in_family("mutableCopying", "mutableCopy");
132+
assert_in_family("mutableCopyWithZone:", "mutableCopy");
133+
assert_in_family("mutableCopyWithZone:", "mutableCopy");
134+
135+
assert_in_family(
136+
"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:",
137+
"new",
138+
);
139+
assert_in_family(
140+
"newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:",
141+
"new",
142+
);
143+
assert_not_in_family("newsstandAssetDownload", "new");
144+
145+
// Trying to weed out edge-cases:
146+
147+
assert_in_family("__abcDef", "abc");
148+
assert_in_family("_abcDef", "abc");
149+
assert_in_family("abcDef", "abc");
150+
assert_in_family("___a", "a");
151+
assert_in_family("__a", "a");
152+
assert_in_family("_a", "a");
153+
assert_in_family("a", "a");
154+
155+
assert_not_in_family("_abcdef", "abc");
156+
assert_not_in_family("_abcdef", "def");
157+
assert_not_in_family("_bcdef", "abc");
158+
assert_not_in_family("a_bc", "abc");
159+
assert_not_in_family("abcdef", "abc");
160+
assert_not_in_family("abcdef", "def");
161+
assert_not_in_family("abcdef", "abb");
162+
assert_not_in_family("___", "a");
163+
assert_not_in_family("_", "a");
164+
assert_not_in_family("", "a");
165+
166+
assert_in_family("copy", "copy");
167+
assert_in_family("copy:", "copy");
168+
assert_in_family("copyMe", "copy");
169+
assert_in_family("_copy", "copy");
170+
assert_in_family("_copy:", "copy");
171+
assert_in_family("_copyMe", "copy");
172+
assert_not_in_family("copying", "copy");
173+
assert_not_in_family("copying:", "copy");
174+
assert_not_in_family("_copying", "copy");
175+
assert_not_in_family("Copy", "copy");
176+
assert_not_in_family("COPY", "copy");
177+
178+
// Empty family (not supported)
179+
assert_in_family("___", "");
180+
assert_in_family("__", "");
181+
assert_in_family("_", "");
182+
assert_in_family("", "");
183+
assert_not_in_family("_a", "");
184+
assert_not_in_family("a", "");
185+
assert_in_family("_A", "");
186+
assert_in_family("A", "");
187+
188+
// Double-colon selectors
189+
assert_in_family("abc::abc::", "abc");
190+
assert_in_family("abc:::", "abc");
191+
assert_in_family("abcDef::xyz:", "abc");
192+
// Invalid selector (probably)
193+
assert_not_in_family("::abc:", "abc");
194+
}
195+
}

0 commit comments

Comments
 (0)