Skip to content

Commit

Permalink
Add downcasting for CoreFoundation-like types
Browse files Browse the repository at this point in the history
Part of #692.
  • Loading branch information
madsmtm committed Jan 10, 2025
1 parent 1ad4e04 commit 3468e00
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 11 deletions.
15 changes: 15 additions & 0 deletions crates/header-translator/src/bin/check_framework_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ fn main() -> Result<(), Box<dyn Error>> {

let mut success = true;

println!("Testing all CoreFoundation features");
let features = get_features(
&workspace_dir
.join("framework-crates")
.join("objc2-core-foundation")
.join("Cargo.toml"),
)?;
let feature_sets = features.iter().map(|feature| vec![&**feature]);
test_feature_sets(
&mut success,
workspace_dir,
feature_sets,
"objc2-core-foundation",
)?;

println!("Testing all Foundation features");
let features = get_features(
&workspace_dir
Expand Down
66 changes: 62 additions & 4 deletions crates/header-translator/src/global_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,72 @@ use std::mem;
use crate::method::Method;
use crate::module::Module;
use crate::stmt::Stmt;
use crate::Library;
use crate::{ItemIdentifier, Library};

pub fn global_analysis(library: &mut Library) {
let _span = info_span!("analyzing").entered();
update_module(&mut library.module);
let mut cf_type_id_mapping = find_cf_type_id_mapping(&library.module);
for (external_name, external_data) in &library.data.external {
if let Some(maybe_type_id_base) = external_name.strip_suffix("Ref") {
cf_type_id_mapping.insert(
format!("{maybe_type_id_base}GetTypeID"),
ItemIdentifier::from_raw(external_name.clone(), external_data.module.clone()),
);
}
}
update_module(&mut library.module, &cf_type_id_mapping);
}

fn find_cf_type_id_mapping(module: &Module) -> BTreeMap<String, ItemIdentifier> {
let mut types = BTreeMap::new();
for stmt in &module.stmts {
if let Stmt::OpaqueDecl {
id, is_cf: true, ..
} = stmt
{
let type_id_base = id.name.strip_suffix("Ref").unwrap_or(&id.name);
types.insert(format!("{type_id_base}GetTypeID"), id.clone());
}
}
for submodule in module.submodules.values() {
types.extend(find_cf_type_id_mapping(submodule));
}
types
}

fn update_module(module: &mut Module) {
fn update_module(module: &mut Module, cf_type_id_mapping: &BTreeMap<String, ItemIdentifier>) {
// Fix location for GetTypeId functions
for stmt in module.stmts.iter_mut() {
if let Stmt::FnGetTypeId {
id,
cf_id,
availability,
result_type,
can_unwind,
documentation,
} = stmt
{
if let Some(actual_cf_id) = cf_type_id_mapping.get(&id.name) {
*cf_id = actual_cf_id.clone();
} else {
warn!(?id, ?cf_id, "could not find GetTypeId CF typedef");
*stmt = Stmt::FnDecl {
id: id.clone(),
availability: availability.clone(),
arguments: vec![],
result_type: result_type.clone(),
body: None,
safe: true,
must_use: false,
can_unwind: *can_unwind,
link_name: None,
returns_retained: false,
documentation: documentation.clone(),
};
}
}
}

// disambiguate duplicate names
// NOTE: this only works within single files
let mut names = BTreeMap::<(String, String), &mut Method>::new();
Expand Down Expand Up @@ -112,6 +170,6 @@ fn update_module(module: &mut Module) {
// Recurse for submodules
for (name, module) in &mut module.submodules {
let _span = debug_span!("file", name).entered();
update_module(module);
update_module(module, cf_type_id_mapping);
}
}
3 changes: 1 addition & 2 deletions crates/header-translator/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,10 @@ impl ItemIdentifier {
}
}

#[cfg(test)]
pub fn dummy() -> Self {
Self {
name: "DUMMY".into(),
location: Location::new("__builtin__"),
location: Location::new("__dummy__"),
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/header-translator/src/rust_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,10 @@ impl Ty {
}
}

pub(crate) fn is_cf_type_id(&self) -> bool {
matches!(self, Self::TypeDef { id, .. } if id.name == "CFTypeID")
}

pub(crate) fn is_objc_bool(&self) -> bool {
match self {
Self::Primitive(Primitive::ObjcBool) => true,
Expand Down
80 changes: 79 additions & 1 deletion crates/header-translator/src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,15 @@ pub enum Stmt {
returns_retained: bool,
documentation: Documentation,
},
/// CFTypeID CGColorGetTypeID(void)
FnGetTypeId {
id: ItemIdentifier,
cf_id: ItemIdentifier,
result_type: Ty,
availability: Availability,
can_unwind: bool,
documentation: Documentation,
},
/// typedef Type TypedefName;
AliasDecl {
id: ItemIdentifier,
Expand Down Expand Up @@ -1514,6 +1523,7 @@ impl Stmt {
}

let availability = Availability::parse(entity, context);
let documentation = Documentation::from_entity(entity);
let result_type = entity.get_result_type().expect("function result type");
let result_type = Ty::parse_function_return(result_type, context);
let mut arguments = Vec::new();
Expand Down Expand Up @@ -1617,6 +1627,29 @@ impl Stmt {
None
};

if id.name.ends_with("GetTypeID") && id.name != "CFGetTypeID" {
assert!(arguments.is_empty(), "{id:?} must have no arguments");
assert!(result_type.is_cf_type_id(), "{id:?} must return CFTypeID");
assert!(body.is_none(), "{id:?} must not be inline");
assert!(
data.unsafe_,
"{id:?} must not have manually modified safety"
);
assert!(!must_use, "{id:?} must not have must_use");
assert!(link_name.is_none(), "{id:?} must not have link_name");
assert!(!returns_retained, "{id:?} must not have returns_retained");

return vec![Self::FnGetTypeId {
id,
// Will get replaced in global_analysis with the actual id.
cf_id: ItemIdentifier::dummy(),
result_type,
availability,
can_unwind,
documentation,
}];
}

vec![Self::FnDecl {
id,
availability,
Expand All @@ -1628,7 +1661,7 @@ impl Stmt {
can_unwind,
link_name,
returns_retained,
documentation: Documentation::from_entity(entity),
documentation,
}]
}
EntityKind::UnionDecl => {
Expand Down Expand Up @@ -1674,6 +1707,7 @@ impl Stmt {
Self::FnDecl { id, body: None, .. } => Some(id.clone()),
// TODO
Self::FnDecl { body: Some(_), .. } => None,
Self::FnGetTypeId { .. } => None, // Emits a trait impl
Self::AliasDecl { id, .. } => Some(id.clone()),
Self::OpaqueDecl { id, .. } => Some(id.clone()),
}
Expand All @@ -1691,6 +1725,7 @@ impl Stmt {
Self::ConstDecl { id, .. } => id.location(),
Self::VarDecl { id, .. } => id.location(),
Self::FnDecl { id, .. } => id.location(),
Self::FnGetTypeId { id, .. } => id.location(),
Self::AliasDecl { id, .. } => id.location(),
Self::OpaqueDecl { id, .. } => id.location(),
}
Expand Down Expand Up @@ -1753,6 +1788,13 @@ impl Stmt {
}
// TODO
Self::FnDecl { body: Some(_), .. } => Vec::new(),
Self::FnGetTypeId {
cf_id, result_type, ..
} => {
let mut items = vec![cf_id.clone(), ItemIdentifier::cf("ConcreteType")];
items.extend(result_type.fn_return_required_items());
items
}
Self::AliasDecl { ty, .. } => ty.required_items(),
Self::OpaqueDecl { superclass, .. } => {
let mut items = vec![ItemIdentifier::unsafecell(), ItemIdentifier::phantoms()];
Expand Down Expand Up @@ -2752,6 +2794,42 @@ impl Stmt {
writeln!(f, "}}")?;
}
}
Self::FnGetTypeId {
id,
cf_id,
result_type,
availability: _, // #[deprecated] is useless on traits.
can_unwind,
documentation,
} => {
let abi = if *can_unwind { "C-unwind" } else { "C" };

// Only emit for base types, not for mutable subclasses,
// as it's unclear whether it's safe to downcast to
// mutable subclasses.
write!(f, "{}", self.cfg_gate_ln(config))?;
writeln!(f, "unsafe impl ConcreteType for {} {{", cf_id.path())?;

write!(f, "{}", documentation.fmt(None))?;
writeln!(f, " #[doc(alias = {:?})]", id.name)?;
writeln!(f, " #[inline]")?;
writeln!(f, " fn type_id(){} {{", result_type.fn_return())?;

writeln!(f, " extern {abi:?} {{")?;
writeln!(
f,
" fn {}(){};",
id.name,
result_type.fn_return()
)?;
writeln!(f, " }}")?;

writeln!(f, " unsafe {{ {}() }}", id.name)?;

writeln!(f, " }}")?;
writeln!(f, "}}")?;
return Ok(());
}
Self::AliasDecl {
id,
availability: _,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ pub use objc2::encode::{Encode, Encoding, RefEncode};
pub use objc2::runtime::AnyObject;
#[cfg(feature = "objc2")]
pub use objc2::Message;

#[cfg(feature = "CFBase")]
pub use crate::CFTypeID;
#[cfg(not(feature = "CFBase"))]
pub use core::primitive::usize as CFTypeID;
34 changes: 34 additions & 0 deletions framework-crates/objc2-core-foundation/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use core::fmt;
use core::hash;
use core::marker::{PhantomData, PhantomPinned};

use crate::ConcreteType;
use crate::{CFEqual, CFHash, Type};

/// [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cftypeid?language=objc)
Expand All @@ -67,6 +68,39 @@ pub struct CFType {
_p: UnsafeCell<PhantomData<(*const UnsafeCell<()>, PhantomPinned)>>,
}

impl CFType {
/// Attempt to downcast the type to that of type `T`.
///
/// This is the reference-variant. Use [`CFRetained::downcast`] if you
/// want to convert a retained type. See also [`ConcreteType`] for more
/// details on which types support being converted to.
///
/// [`CFRetained::downcast`]: crate::CFRetained::downcast
//
// Not #[inline], we call two functions here.
#[doc(alias = "CFGetTypeID")]
pub fn downcast_ref<T: ConcreteType>(&self) -> Option<&T> {
extern "C-unwind" {
fn CFGetTypeID(cf: Option<&CFType>) -> CFTypeID;
}

// SAFETY: The pointer is valid.
if unsafe { CFGetTypeID(Some(self)) } == T::type_id() {
let ptr: *const Self = self;
let ptr: *const T = ptr.cast();
// SAFETY: Just checked that the object is a class of type `T`.
// Additionally, `ConcreteType::type_id` is guaranteed to uniquely
// identify the class (including ruling out mutable subclasses),
// so we know for _sure_ that the class is actually of that type
// here.
let this: &T = unsafe { &*ptr };
Some(this)
} else {
None
}
}
}

// Reflexive AsRef impl.
impl AsRef<Self> for CFType {
#[inline]
Expand Down
2 changes: 1 addition & 1 deletion framework-crates/objc2-core-foundation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub use self::generated::*;
#[cfg(feature = "CFCGTypes")]
pub use self::geometry::*;
pub use self::retained::CFRetained;
pub use self::type_traits::Type;
pub use self::type_traits::{ConcreteType, Type};

// MacTypes.h
#[allow(dead_code)]
Expand Down
Loading

0 comments on commit 3468e00

Please sign in to comment.