diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c405c7da..0003f26952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,6 +139,7 @@ # Unreleased ## Added +* Objective-c bindings generate impl blocks for categories inherited through parent classes ([#1784][]). ## Fixed diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 19886e3d3c..13a91f32e2 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -4185,6 +4185,27 @@ impl CodeGenerator for ObjCInterface { result.push(impl_trait); } } + for (category_name, category_template_names) in + &parent.categories + { + let category_name = ctx.rust_ident(category_name); + let impl_trait = if !category_template_names.is_empty() { + let template_names: Vec = + category_template_names + .iter() + .map(|g| ctx.rust_ident(g)) + .collect(); + quote! { + impl <#(#template_names :'static),*> #category_name <#(#template_names),*> for #class_name { + } + } + } else { + quote! { + impl #category_name for #class_name { } + } + }; + result.push(impl_trait); + } if !parent.is_template() { let parent_struct_name = parent.name(); let child_struct_name = self.name(); @@ -4216,7 +4237,10 @@ impl CodeGenerator for ObjCInterface { } }; result.push(try_into_block); - } + parent.parent_class + } else { + None + }; } } diff --git a/src/ir/context.rs b/src/ir/context.rs index b2e6f98542..1e660e4fe8 100644 --- a/src/ir/context.rs +++ b/src/ir/context.rs @@ -875,6 +875,11 @@ If you encounter an error missing from this list, please file an issue or a PR!" }) } + /// Get a mutable reference to a given ItemId. + pub fn get_item_mut(&mut self, id: ItemId) -> Option<&mut Item> { + self.items.get_mut(id.0)?.as_mut() + } + /// Have we collected all unresolved type references yet? pub fn collected_typerefs(&self) -> bool { self.collected_typerefs diff --git a/src/ir/objc.rs b/src/ir/objc.rs index 0845ad0fde..8fd2cc32fc 100644 --- a/src/ir/objc.rs +++ b/src/ir/objc.rs @@ -1,10 +1,10 @@ //! Objective C types -use super::context::{BindgenContext, ItemId}; +use super::context::{BindgenContext, ItemId, TypeId}; use super::function::FunctionSig; use super::item::Item; use super::traversal::{Trace, Tracer}; -use super::ty::TypeKind; +use super::ty::{Type, TypeKind}; use crate::clang; use crate::parse::ClangItemParser; use clang_sys::CXChildVisit_Continue; @@ -37,6 +37,9 @@ pub struct ObjCInterface { /// The list of protocols that this interface conforms to. pub conforms_to: Vec, + /// The list of categories (and the template tags) that this interface is extended by. + pub categories: Vec<(String, Vec)>, + /// The direct parent for this interface. pub parent_class: Option, @@ -68,6 +71,7 @@ impl ObjCInterface { ObjCInterface { name: name.to_owned(), category: None, + categories: Vec::new(), is_protocol: false, template_names: Vec::new(), parent_class: None, @@ -133,14 +137,25 @@ impl ObjCInterface { interface.is_protocol = true; } + // This is the ItemId for the real interface to which a category extends. We must make it + // an optional, set it when we visit the ObjCCategoryDecl and then use it after we've + // visited the entire tree. We must do it in this order to ensure that this interface has + // all the template tags assigned to it. + let mut real_interface_id_for_category: Option = None; + cursor.visit(|c| { match c.kind() { CXCursor_ObjCClassRef => { if cursor.kind() == CXCursor_ObjCCategoryDecl { // We are actually a category extension, and we found the reference - // to the original interface, so name this interface approriately + // to the original interface, so name this interface approriately. + interface.name = c.spelling(); interface.category = Some(cursor.spelling()); + real_interface_id_for_category = Some( + Item::from_ty_or_ref(c.cur_type(), c, None, ctx) + .into(), + ); } } CXCursor_ObjCProtocolRef => { @@ -200,9 +215,61 @@ impl ObjCInterface { } CXChildVisit_Continue }); + + if interface.is_category() { + // If this interface is a category, we need to find the interface that this category + // extends. + if let Some(ref mut ty) = + Self::get_parent_ty(ctx, real_interface_id_for_category) + { + if let TypeKind::ObjCInterface(ref mut real_interface) = + ty.kind_mut() + { + if !real_interface.is_category() { + real_interface.categories.push(( + interface.rust_name(), + interface.template_names.clone(), + )); + } + } + } + } Some(interface) } + fn get_parent_ty( + ctx: &mut BindgenContext, + parent_id: Option, + ) -> Option<&mut Type> { + // This is pretty gross but using the ItemResolver doesn't yield a mutable reference. + let mut ty = ctx.resolve_item_fallible(parent_id?)?.kind().as_type()?; + let mut item_id: Option<&TypeId> = None; + loop { + match ty.kind() { + TypeKind::ResolvedTypeRef(ref_id) => { + let ref_item: ItemId = ref_id.into(); + ty = ctx + .resolve_item_fallible(ref_item)? + .kind() + .as_type()?; + //ty = ref_item.kind().as_type()()?;; + item_id = Some(ref_id); + } + TypeKind::ObjCInterface(..) => { + break; + } + _ => return None, + }; + } + + let real_interface_id: ItemId = item_id?.into(); + let ty = ctx + .get_item_mut(real_interface_id)? + .kind_mut() + .as_type_mut()?; + return Some(ty); + } + fn add_method(&mut self, method: ObjCMethod) { if method.is_class_method { self.class_methods.push(method); diff --git a/src/ir/ty.rs b/src/ir/ty.rs index d573408c3b..3128638024 100644 --- a/src/ir/ty.rs +++ b/src/ir/ty.rs @@ -588,7 +588,7 @@ pub enum TypeKind { /// A compound type, that is, a class, struct, or union. Comp(CompInfo), - /// An opaque type that we just don't understand. All usage of this shoulf + /// An opaque type that we just don't understand. All usage of this should /// result in an opaque blob of bytes generated from the containing type's /// layout. Opaque, diff --git a/tests/expectations/tests/objc_category_inheritance.rs b/tests/expectations/tests/objc_category_inheritance.rs new file mode 100644 index 0000000000..a1a33eb128 --- /dev/null +++ b/tests/expectations/tests/objc_category_inheritance.rs @@ -0,0 +1,67 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] +#![cfg(target_os = "macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +#[repr(transparent)] +#[derive(Clone)] +pub struct Foo(pub id); +impl std::ops::Deref for Foo { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Foo {} +impl Foo { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Foo), alloc) }) + } +} +impl IFoo for Foo {} +pub trait IFoo: Sized + std::ops::Deref {} +impl Foo_BarCategory for Foo {} +pub trait Foo_BarCategory: Sized + std::ops::Deref {} +#[repr(transparent)] +#[derive(Clone)] +pub struct Bar(pub id); +impl std::ops::Deref for Bar { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Bar {} +impl Bar { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Bar), alloc) }) + } +} +impl IFoo for Bar {} +impl Foo_BarCategory for Bar {} +impl From for Foo { + fn from(child: Bar) -> Foo { + Foo(child.0) + } +} +impl std::convert::TryFrom for Bar { + type Error = &'static str; + fn try_from(parent: Foo) -> Result { + let is_kind_of: bool = + unsafe { msg_send!(parent, isKindOfClass: class!(Bar)) }; + if is_kind_of { + Ok(Bar(parent.0)) + } else { + Err("This Foo cannot be downcasted to Bar") + } + } +} +impl IBar for Bar {} +pub trait IBar: Sized + std::ops::Deref {} diff --git a/tests/expectations/tests/objc_category_template_inheritance.rs b/tests/expectations/tests/objc_category_template_inheritance.rs new file mode 100644 index 0000000000..1b762603eb --- /dev/null +++ b/tests/expectations/tests/objc_category_template_inheritance.rs @@ -0,0 +1,50 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] +#![cfg(target_os = "macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +#[repr(transparent)] +#[derive(Clone)] +pub struct Foo(pub id); +impl std::ops::Deref for Foo { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Foo {} +impl Foo { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Foo), alloc) }) + } +} +impl IFoo for Foo {} +pub trait IFoo: Sized + std::ops::Deref {} +impl Foo_Baz for Foo {} +pub trait Foo_Baz: Sized + std::ops::Deref {} +#[repr(transparent)] +#[derive(Clone)] +pub struct Bar(pub id); +impl std::ops::Deref for Bar { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Bar {} +impl Bar { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Bar), alloc) }) + } +} +impl IFoo for Bar {} +impl Foo_Baz for Bar {} +impl IBar for Bar {} +pub trait IBar: Sized + std::ops::Deref {} diff --git a/tests/expectations/tests/objc_template_inheritance.rs b/tests/expectations/tests/objc_template_inheritance.rs new file mode 100644 index 0000000000..13629bdedd --- /dev/null +++ b/tests/expectations/tests/objc_template_inheritance.rs @@ -0,0 +1,47 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] +#![cfg(target_os = "macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +#[repr(transparent)] +#[derive(Clone)] +pub struct Foo(pub id); +impl std::ops::Deref for Foo { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Foo {} +impl Foo { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Foo), alloc) }) + } +} +impl IFoo for Foo {} +pub trait IFoo: Sized + std::ops::Deref {} +#[repr(transparent)] +#[derive(Clone)] +pub struct Bar(pub id); +impl std::ops::Deref for Bar { + type Target = objc::runtime::Object; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} +unsafe impl objc::Message for Bar {} +impl Bar { + pub fn alloc() -> Self { + Self(unsafe { msg_send!(objc::class!(Bar), alloc) }) + } +} +impl IFoo for Bar {} +impl IBar for Bar {} +pub trait IBar: Sized + std::ops::Deref {} diff --git a/tests/headers/objc_category_inheritance.h b/tests/headers/objc_category_inheritance.h new file mode 100644 index 0000000000..80f294e2ef --- /dev/null +++ b/tests/headers/objc_category_inheritance.h @@ -0,0 +1,11 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo +@end + +@interface Foo (BarCategory) +@end + +@interface Bar: Foo +@end diff --git a/tests/headers/objc_category_template_inheritance.h b/tests/headers/objc_category_template_inheritance.h new file mode 100644 index 0000000000..7c74323966 --- /dev/null +++ b/tests/headers/objc_category_template_inheritance.h @@ -0,0 +1,11 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo<__covariant ObjectType> +@end + +@interface Foo<__covariant ObjectType> (Baz) +@end + +@interface Bar<__covariant ObjectType>: Foo +@end diff --git a/tests/headers/objc_template_inheritance.h b/tests/headers/objc_template_inheritance.h new file mode 100644 index 0000000000..019f571d60 --- /dev/null +++ b/tests/headers/objc_template_inheritance.h @@ -0,0 +1,8 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo<__covariant ObjectType> +@end + +@interface Bar<__covariant ObjectType>: Foo +@end