Skip to content

Commit a9ff910

Browse files
authored
Merge pull request #359 from madsmtm:sendability-attributes
Sendability attributes
2 parents b1c04fe + 53e3cc4 commit a9ff910

24 files changed

+571
-127
lines changed

crates/header-translator/framework-includes.h

-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
// Workaround for clang < 13, only used in NSBundle.h
2-
#define NS_FORMAT_ARGUMENT(A)
3-
4-
// Workaround for clang < 13
5-
#define _Nullable_result _Nullable
6-
71
#include <TargetConditionals.h>
82

93
#if TARGET_OS_OSX

crates/header-translator/src/cache.rs

+87-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, BTreeSet};
22
use std::mem;
33

44
use crate::config::Config;
@@ -7,16 +7,38 @@ use crate::id::ItemIdentifier;
77
use crate::method::Method;
88
use crate::output::Output;
99
use crate::stmt::Stmt;
10+
use crate::Mutability;
1011

1112
/// A helper struct for doing global analysis on the output.
1213
#[derive(Debug, PartialEq, Clone)]
1314
pub struct Cache<'a> {
1415
config: &'a Config,
16+
mainthreadonly_classes: BTreeSet<ItemIdentifier>,
1517
}
1618

1719
impl<'a> Cache<'a> {
18-
pub fn new(_output: &Output, config: &'a Config) -> Self {
19-
Self { config }
20+
pub fn new(output: &Output, config: &'a Config) -> Self {
21+
let mut mainthreadonly_classes = BTreeSet::new();
22+
23+
for library in output.libraries.values() {
24+
for file in library.files.values() {
25+
for stmt in file.stmts.iter() {
26+
if let Stmt::ClassDecl {
27+
id,
28+
mutability: Mutability::MainThreadOnly,
29+
..
30+
} = stmt
31+
{
32+
mainthreadonly_classes.insert(id.clone());
33+
}
34+
}
35+
}
36+
}
37+
38+
Self {
39+
config,
40+
mainthreadonly_classes,
41+
}
2042
}
2143

2244
pub fn update(&self, output: &mut Output) {
@@ -68,6 +90,67 @@ impl<'a> Cache<'a> {
6890
}
6991
}
7092

93+
// Add `mainthreadonly` to relevant methods
94+
for stmt in file.stmts.iter_mut() {
95+
match stmt {
96+
Stmt::Methods {
97+
cls: id, methods, ..
98+
}
99+
| Stmt::ProtocolDecl { id, methods, .. } => {
100+
for method in methods.iter_mut() {
101+
let mut result_type_contains_mainthreadonly: bool = false;
102+
method.result_type.visit_required_types(&mut |id| {
103+
if self.mainthreadonly_classes.contains(id) {
104+
result_type_contains_mainthreadonly = true;
105+
}
106+
});
107+
108+
match (method.is_class, self.mainthreadonly_classes.contains(id)) {
109+
// MainThreadOnly class with static method
110+
(true, true) => {
111+
// Assume the method needs main thread
112+
result_type_contains_mainthreadonly = true;
113+
}
114+
// Class with static method
115+
(true, false) => {
116+
// Continue with the normal check
117+
}
118+
// MainThreadOnly class with non-static method
119+
(false, true) => {
120+
// Method is already required to run on main
121+
// thread, so no need to add MainThreadMarker
122+
continue;
123+
}
124+
// Class with non-static method
125+
(false, false) => {
126+
// Continue with the normal check
127+
}
128+
}
129+
130+
if result_type_contains_mainthreadonly {
131+
let mut any_argument_contains_mainthreadonly: bool = false;
132+
for (_, argument) in method.arguments.iter() {
133+
// Important: We only visit the top-level types, to not
134+
// include e.g. `Option<&NSView>` or `&NSArray<NSView>`.
135+
argument.visit_toplevel_types(&mut |id| {
136+
if self.mainthreadonly_classes.contains(id) {
137+
any_argument_contains_mainthreadonly = true;
138+
}
139+
});
140+
}
141+
142+
// Apply main thread only, unless a (required)
143+
// argument was main thread only.
144+
if !any_argument_contains_mainthreadonly {
145+
method.mainthreadonly = true;
146+
}
147+
}
148+
}
149+
}
150+
_ => {}
151+
}
152+
}
153+
71154
// Fix up a few typedef + enum declarations
72155
let mut iter = mem::take(&mut file.stmts).into_iter().peekable();
73156
while let Some(stmt) = iter.next() {
@@ -84,6 +167,7 @@ impl<'a> Cache<'a> {
84167
ty: enum_ty,
85168
kind: _,
86169
variants: _,
170+
sendable: _,
87171
}) = iter.peek_mut()
88172
{
89173
if enum_ty.is_typedef_to(&id.name) {

crates/header-translator/src/id.rs

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ impl ItemIdentifier {
119119
self.library == "Foundation" && self.name == "NSString"
120120
}
121121

122+
pub fn is_nscomparator(&self) -> bool {
123+
self.library == "Foundation" && self.name == "NSComparator"
124+
}
125+
122126
pub fn feature(&self) -> Option<impl fmt::Display + '_> {
123127
struct ItemIdentifierFeature<'a>(&'a ItemIdentifier);
124128

crates/header-translator/src/method.rs

+56-6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ struct MethodModifiers {
5252
returns_retained: bool,
5353
returns_not_retained: bool,
5454
designated_initializer: bool,
55+
non_isolated: bool,
56+
sendable: Option<bool>,
57+
mainthreadonly: bool,
5558
}
5659

5760
impl MethodModifiers {
@@ -68,6 +71,18 @@ impl MethodModifiers {
6871
UnexposedAttr::ReturnsNotRetained => {
6972
this.returns_not_retained = true;
7073
}
74+
UnexposedAttr::NonIsolated => {
75+
this.non_isolated = true;
76+
}
77+
UnexposedAttr::Sendable => {
78+
this.sendable = Some(true);
79+
}
80+
UnexposedAttr::NonSendable => {
81+
this.sendable = Some(false);
82+
}
83+
UnexposedAttr::UIActor => {
84+
this.mainthreadonly = true;
85+
}
7186
attr => error!(?attr, "unknown attribute"),
7287
}
7388
}
@@ -214,6 +229,7 @@ impl MemoryManagement {
214229
consumes_self: false,
215230
returns_retained: false,
216231
returns_not_retained: false,
232+
..
217233
} = modifiers
218234
{
219235
Self::Normal
@@ -233,11 +249,14 @@ pub struct Method {
233249
pub is_class: bool,
234250
is_optional_protocol: bool,
235251
memory_management: MemoryManagement,
236-
arguments: Vec<(String, Ty)>,
252+
pub(crate) arguments: Vec<(String, Ty)>,
237253
pub result_type: Ty,
238254
safe: bool,
239255
mutating: bool,
240256
is_protocol: bool,
257+
// Thread-safe, even on main-thread only (@MainActor/@UIActor) classes
258+
non_isolated: bool,
259+
pub(crate) mainthreadonly: bool,
241260
}
242261

243262
impl Method {
@@ -367,6 +386,10 @@ impl<'tu> PartialMethod<'tu> {
367386

368387
let modifiers = MethodModifiers::parse(&entity, context);
369388

389+
if modifiers.sendable.is_some() {
390+
error!("sendable on method");
391+
}
392+
370393
let mut arguments: Vec<_> = entity
371394
.get_arguments()
372395
.expect("method arguments")
@@ -377,6 +400,8 @@ impl<'tu> PartialMethod<'tu> {
377400
let qualifier = entity
378401
.get_objc_qualifiers()
379402
.map(MethodArgumentQualifier::parse);
403+
let mut sendable = None;
404+
let mut no_escape = false;
380405

381406
immediate_children(&entity, |entity, _span| match entity.get_kind() {
382407
EntityKind::ObjCClassRef
@@ -390,7 +415,12 @@ impl<'tu> PartialMethod<'tu> {
390415
}
391416
EntityKind::UnexposedAttr => {
392417
if let Some(attr) = UnexposedAttr::parse(&entity, context) {
393-
error!(?attr, "unknown attribute");
418+
match attr {
419+
UnexposedAttr::Sendable => sendable = Some(true),
420+
UnexposedAttr::NonSendable => sendable = Some(false),
421+
UnexposedAttr::NoEscape => no_escape = true,
422+
attr => error!(?attr, "unknown attribute"),
423+
}
394424
}
395425
}
396426
// For some reason we recurse into array types
@@ -399,7 +429,7 @@ impl<'tu> PartialMethod<'tu> {
399429
});
400430

401431
let ty = entity.get_type().expect("argument type");
402-
let ty = Ty::parse_method_argument(ty, qualifier, context);
432+
let ty = Ty::parse_method_argument(ty, qualifier, sendable, no_escape, context);
403433

404434
(name, ty)
405435
})
@@ -463,6 +493,8 @@ impl<'tu> PartialMethod<'tu> {
463493
// immutable subclass, or as a property.
464494
mutating: data.mutating.unwrap_or(parent_is_mutable),
465495
is_protocol,
496+
non_isolated: modifiers.non_isolated,
497+
mainthreadonly: modifiers.mainthreadonly,
466498
},
467499
))
468500
}
@@ -519,6 +551,7 @@ impl PartialProperty<'_> {
519551
let ty = Ty::parse_property_return(
520552
entity.get_type().expect("property type"),
521553
is_copy,
554+
modifiers.sendable,
522555
context,
523556
);
524557

@@ -538,6 +571,8 @@ impl PartialProperty<'_> {
538571
// is, so let's default to immutable.
539572
mutating: getter_data.mutating.unwrap_or(false),
540573
is_protocol,
574+
non_isolated: modifiers.non_isolated,
575+
mainthreadonly: modifiers.mainthreadonly,
541576
})
542577
} else {
543578
None
@@ -546,8 +581,12 @@ impl PartialProperty<'_> {
546581
let setter = if let Some(setter_name) = setter_name {
547582
let setter_data = setter_data.expect("setter_data must be present if setter_name was");
548583
if !setter_data.skipped {
549-
let ty =
550-
Ty::parse_property(entity.get_type().expect("property type"), is_copy, context);
584+
let ty = Ty::parse_property(
585+
entity.get_type().expect("property type"),
586+
is_copy,
587+
modifiers.sendable,
588+
context,
589+
);
551590

552591
let selector = setter_name.clone() + ":";
553592
let memory_management =
@@ -566,6 +605,8 @@ impl PartialProperty<'_> {
566605
// Setters are usually mutable if the class itself is.
567606
mutating: setter_data.mutating.unwrap_or(parent_is_mutable),
568607
is_protocol,
608+
non_isolated: modifiers.non_isolated,
609+
mainthreadonly: modifiers.mainthreadonly,
569610
})
570611
} else {
571612
None
@@ -595,6 +636,11 @@ impl fmt::Display for Method {
595636
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596637
let _span = debug_span!("method", self.fn_name).entered();
597638

639+
// TODO: Use this somehow?
640+
// if self.non_isolated {
641+
// writeln!(f, "// non_isolated")?;
642+
// }
643+
598644
//
599645
// Attributes
600646
//
@@ -648,7 +694,11 @@ impl fmt::Display for Method {
648694
// Arguments
649695
for (param, arg_ty) in &self.arguments {
650696
let param = handle_reserved(&crate::to_snake_case(param));
651-
write!(f, "{param}: {arg_ty},")?;
697+
write!(f, "{param}: {arg_ty}, ")?;
698+
}
699+
// FIXME: Skipping main thread only on protocols for now
700+
if self.mainthreadonly && !self.is_protocol {
701+
write!(f, "mtm: MainThreadMarker")?;
652702
}
653703
write!(f, ")")?;
654704

0 commit comments

Comments
 (0)