Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions crates/header-translator/framework-includes.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
// Workaround for clang < 13, only used in NSBundle.h
#define NS_FORMAT_ARGUMENT(A)

// Workaround for clang < 13
#define _Nullable_result _Nullable

#include <TargetConditionals.h>

#if TARGET_OS_OSX
Expand Down
90 changes: 87 additions & 3 deletions crates/header-translator/src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::mem;

use crate::config::Config;
Expand All @@ -7,16 +7,38 @@ use crate::id::ItemIdentifier;
use crate::method::Method;
use crate::output::Output;
use crate::stmt::Stmt;
use crate::Mutability;

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

impl<'a> Cache<'a> {
pub fn new(_output: &Output, config: &'a Config) -> Self {
Self { config }
pub fn new(output: &Output, config: &'a Config) -> Self {
let mut mainthreadonly_classes = BTreeSet::new();

for library in output.libraries.values() {
for file in library.files.values() {
for stmt in file.stmts.iter() {
if let Stmt::ClassDecl {
id,
mutability: Mutability::MainThreadOnly,
..
} = stmt
{
mainthreadonly_classes.insert(id.clone());
}
}
}
}

Self {
config,
mainthreadonly_classes,
}
}

pub fn update(&self, output: &mut Output) {
Expand Down Expand Up @@ -68,6 +90,67 @@ impl<'a> Cache<'a> {
}
}

// Add `mainthreadonly` to relevant methods
for stmt in file.stmts.iter_mut() {
match stmt {
Stmt::Methods {
cls: id, methods, ..
}
| Stmt::ProtocolDecl { id, methods, .. } => {
for method in methods.iter_mut() {
let mut result_type_contains_mainthreadonly: bool = false;
method.result_type.visit_required_types(&mut |id| {
if self.mainthreadonly_classes.contains(id) {
result_type_contains_mainthreadonly = true;
}
});

match (method.is_class, self.mainthreadonly_classes.contains(id)) {
// MainThreadOnly class with static method
(true, true) => {
// Assume the method needs main thread
result_type_contains_mainthreadonly = true;
}
// Class with static method
(true, false) => {
// Continue with the normal check
}
// MainThreadOnly class with non-static method
(false, true) => {
// Method is already required to run on main
// thread, so no need to add MainThreadMarker
continue;
}
// Class with non-static method
(false, false) => {
// Continue with the normal check
}
}

if result_type_contains_mainthreadonly {
let mut any_argument_contains_mainthreadonly: bool = false;
for (_, argument) in method.arguments.iter() {
// Important: We only visit the top-level types, to not
// include e.g. `Option<&NSView>` or `&NSArray<NSView>`.
argument.visit_toplevel_types(&mut |id| {
if self.mainthreadonly_classes.contains(id) {
any_argument_contains_mainthreadonly = true;
}
});
}

// Apply main thread only, unless a (required)
// argument was main thread only.
if !any_argument_contains_mainthreadonly {
method.mainthreadonly = true;
}
}
}
}
_ => {}
}
}

// Fix up a few typedef + enum declarations
let mut iter = mem::take(&mut file.stmts).into_iter().peekable();
while let Some(stmt) = iter.next() {
Expand All @@ -84,6 +167,7 @@ impl<'a> Cache<'a> {
ty: enum_ty,
kind: _,
variants: _,
sendable: _,
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(&id.name) {
Expand Down
4 changes: 4 additions & 0 deletions crates/header-translator/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ impl ItemIdentifier {
self.library == "Foundation" && self.name == "NSString"
}

pub fn is_nscomparator(&self) -> bool {
self.library == "Foundation" && self.name == "NSComparator"
}

pub fn feature(&self) -> Option<impl fmt::Display + '_> {
struct ItemIdentifierFeature<'a>(&'a ItemIdentifier);

Expand Down
62 changes: 56 additions & 6 deletions crates/header-translator/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ struct MethodModifiers {
returns_retained: bool,
returns_not_retained: bool,
designated_initializer: bool,
non_isolated: bool,
sendable: Option<bool>,
mainthreadonly: bool,
}

impl MethodModifiers {
Expand All @@ -68,6 +71,18 @@ impl MethodModifiers {
UnexposedAttr::ReturnsNotRetained => {
this.returns_not_retained = true;
}
UnexposedAttr::NonIsolated => {
this.non_isolated = true;
}
UnexposedAttr::Sendable => {
this.sendable = Some(true);
}
UnexposedAttr::NonSendable => {
this.sendable = Some(false);
}
UnexposedAttr::UIActor => {
this.mainthreadonly = true;
}
attr => error!(?attr, "unknown attribute"),
}
}
Expand Down Expand Up @@ -214,6 +229,7 @@ impl MemoryManagement {
consumes_self: false,
returns_retained: false,
returns_not_retained: false,
..
} = modifiers
{
Self::Normal
Expand All @@ -233,11 +249,14 @@ pub struct Method {
pub is_class: bool,
is_optional_protocol: bool,
memory_management: MemoryManagement,
arguments: Vec<(String, Ty)>,
pub(crate) arguments: Vec<(String, Ty)>,
pub result_type: Ty,
safe: bool,
mutating: bool,
is_protocol: bool,
// Thread-safe, even on main-thread only (@MainActor/@UIActor) classes
non_isolated: bool,
pub(crate) mainthreadonly: bool,
}

impl Method {
Expand Down Expand Up @@ -367,6 +386,10 @@ impl<'tu> PartialMethod<'tu> {

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

if modifiers.sendable.is_some() {
error!("sendable on method");
}

let mut arguments: Vec<_> = entity
.get_arguments()
.expect("method arguments")
Expand All @@ -377,6 +400,8 @@ impl<'tu> PartialMethod<'tu> {
let qualifier = entity
.get_objc_qualifiers()
.map(MethodArgumentQualifier::parse);
let mut sendable = None;
let mut no_escape = false;

immediate_children(&entity, |entity, _span| match entity.get_kind() {
EntityKind::ObjCClassRef
Expand All @@ -390,7 +415,12 @@ impl<'tu> PartialMethod<'tu> {
}
EntityKind::UnexposedAttr => {
if let Some(attr) = UnexposedAttr::parse(&entity, context) {
error!(?attr, "unknown attribute");
match attr {
UnexposedAttr::Sendable => sendable = Some(true),
UnexposedAttr::NonSendable => sendable = Some(false),
UnexposedAttr::NoEscape => no_escape = true,
attr => error!(?attr, "unknown attribute"),
}
}
}
// For some reason we recurse into array types
Expand All @@ -399,7 +429,7 @@ impl<'tu> PartialMethod<'tu> {
});

let ty = entity.get_type().expect("argument type");
let ty = Ty::parse_method_argument(ty, qualifier, context);
let ty = Ty::parse_method_argument(ty, qualifier, sendable, no_escape, context);

(name, ty)
})
Expand Down Expand Up @@ -463,6 +493,8 @@ impl<'tu> PartialMethod<'tu> {
// immutable subclass, or as a property.
mutating: data.mutating.unwrap_or(parent_is_mutable),
is_protocol,
non_isolated: modifiers.non_isolated,
mainthreadonly: modifiers.mainthreadonly,
},
))
}
Expand Down Expand Up @@ -519,6 +551,7 @@ impl PartialProperty<'_> {
let ty = Ty::parse_property_return(
entity.get_type().expect("property type"),
is_copy,
modifiers.sendable,
context,
);

Expand All @@ -538,6 +571,8 @@ impl PartialProperty<'_> {
// is, so let's default to immutable.
mutating: getter_data.mutating.unwrap_or(false),
is_protocol,
non_isolated: modifiers.non_isolated,
mainthreadonly: modifiers.mainthreadonly,
})
} else {
None
Expand All @@ -546,8 +581,12 @@ impl PartialProperty<'_> {
let setter = if let Some(setter_name) = setter_name {
let setter_data = setter_data.expect("setter_data must be present if setter_name was");
if !setter_data.skipped {
let ty =
Ty::parse_property(entity.get_type().expect("property type"), is_copy, context);
let ty = Ty::parse_property(
entity.get_type().expect("property type"),
is_copy,
modifiers.sendable,
context,
);

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

// TODO: Use this somehow?
// if self.non_isolated {
// writeln!(f, "// non_isolated")?;
// }

//
// Attributes
//
Expand Down Expand Up @@ -648,7 +694,11 @@ impl fmt::Display for Method {
// Arguments
for (param, arg_ty) in &self.arguments {
let param = handle_reserved(&crate::to_snake_case(param));
write!(f, "{param}: {arg_ty},")?;
write!(f, "{param}: {arg_ty}, ")?;
}
// FIXME: Skipping main thread only on protocols for now
if self.mainthreadonly && !self.is_protocol {
write!(f, "mtm: MainThreadMarker")?;
}
write!(f, ")")?;

Expand Down
Loading