diff options
author | Mikko Lehtonen <scoopr@iki.fi> | 2017-01-29 01:10:38 +0200 |
---|---|---|
committer | Mikko Lehtonen <scoopr@iki.fi> | 2017-01-31 11:57:45 +0200 |
commit | 4736263931176916c3c21a51795dd3ad74e62e3d (patch) | |
tree | ac6d5e0e77a54d932c6c9686a3f978d04d8ef1ee | |
parent | bdd034b07a02a1a886ac8b94a81327608f6124b3 (diff) |
Add initial Objective C support
It parses interfaces and protocol but ignores base classes, and their
methods which don’t have arguments, the method signature is currently
ignored. Also you can pass objc class instances to C functions.
Next steps are inheritance/base classes, method signatures, properties,
categories. Then check with system headers what is missing.
-rw-r--r-- | src/clang.rs | 3 | ||||
-rw-r--r-- | src/codegen/mod.rs | 144 | ||||
-rw-r--r-- | src/ir/mod.rs | 1 | ||||
-rw-r--r-- | src/ir/objc.rs | 99 | ||||
-rw-r--r-- | src/ir/ty.rs | 43 | ||||
-rw-r--r-- | src/lib.rs | 12 | ||||
-rw-r--r-- | src/options.rs | 3 | ||||
-rw-r--r-- | tests/expectations/Cargo.toml | 1 | ||||
-rw-r--r-- | tests/expectations/tests/objc_interface.rs | 15 | ||||
-rw-r--r-- | tests/expectations/tests/objc_interface_type.rs | 33 | ||||
-rw-r--r-- | tests/expectations/tests/objc_method.rs | 17 | ||||
-rw-r--r-- | tests/headers/objc_interface.h | 8 | ||||
-rw-r--r-- | tests/headers/objc_interface_type.h | 13 | ||||
-rw-r--r-- | tests/headers/objc_method.h | 7 | ||||
-rw-r--r-- | tests/tests.rs | 8 |
15 files changed, 394 insertions, 13 deletions
diff --git a/src/clang.rs b/src/clang.rs index 9cf51436..f0c5124c 100644 --- a/src/clang.rs +++ b/src/clang.rs @@ -688,7 +688,8 @@ impl Type { CXType_Pointer | CXType_RValueReference | CXType_LValueReference | - CXType_MemberPointer => { + CXType_MemberPointer | + CXType_ObjCObjectPointer => { let ret = Type { x: unsafe { clang_getPointeeType(self.x) }, }; diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 436c2bd7..9dd3b6b9 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -13,6 +13,7 @@ use ir::item::{Item, ItemAncestors, ItemCanonicalName, ItemCanonicalPath}; use ir::item_kind::ItemKind; use ir::layout::Layout; use ir::module::Module; +use ir::objc::ObjCInterface; use ir::ty::{Type, TypeKind}; use ir::type_collector::ItemSet; use ir::var::Var; @@ -87,6 +88,9 @@ struct CodegenResult<'a> { /// Whether an incomplete array has been generated at least once. saw_incomplete_array: bool, + /// Whether Objective C types have been seen at least once. + saw_objc: bool, + items_seen: HashSet<ItemId>, /// The set of generated function/var names, needed because in C/C++ is /// legal to do something like: @@ -119,6 +123,7 @@ impl<'a> CodegenResult<'a> { items: vec![], saw_union: false, saw_incomplete_array: false, + saw_objc: false, codegen_id: codegen_id, items_seen: Default::default(), functions_seen: Default::default(), @@ -140,6 +145,10 @@ impl<'a> CodegenResult<'a> { self.saw_incomplete_array = true; } + fn saw_objc(&mut self) { + self.saw_objc = true; + } + fn seen(&self, item: ItemId) -> bool { self.items_seen.contains(&item) } @@ -184,6 +193,7 @@ impl<'a> CodegenResult<'a> { self.saw_union |= new.saw_union; self.saw_incomplete_array |= new.saw_incomplete_array; + self.saw_objc |= new.saw_objc; new.items } @@ -359,6 +369,9 @@ impl CodeGenerator for Module { if ctx.need_bindegen_complex_type() { utils::prepend_complex_type(ctx, &mut *result); } + if result.saw_objc { + utils::prepend_objc_header(ctx, &mut *result); + } } }; @@ -623,6 +636,9 @@ impl CodeGenerator for Type { TypeKind::Enum(ref ei) => { ei.codegen(ctx, result, whitelisted_items, item) } + TypeKind::ObjCInterface(ref interface) => { + interface.codegen(ctx, result, whitelisted_items, item) + } ref u @ TypeKind::UnresolvedTypeRef(..) => { unreachable!("Should have been resolved after parsing {:?}!", u) } @@ -2111,6 +2127,9 @@ impl ToRustTy for Type { let ident = ctx.rust_ident(&name); quote_ty!(ctx.ext_cx(), $ident) } + TypeKind::ObjCInterface(..) => { + quote_ty!(ctx.ext_cx(), id) + }, ref u @ TypeKind::UnresolvedTypeRef(..) => { unreachable!("Should have been resolved after parsing {:?}!", u) } @@ -2144,10 +2163,22 @@ impl ToRustTy for FunctionSig { // the array type derivation. // // [1]: http://c0x.coding-guidelines.com/6.7.5.3.html - let arg_ty = if let TypeKind::Array(t, _) = *arg_ty.canonical_type(ctx).kind() { - t.to_rust_ty(ctx).to_ptr(arg_ty.is_const(), ctx.span()) - } else { - arg_item.to_rust_ty(ctx) + let arg_ty = match *arg_ty.canonical_type(ctx).kind() { + TypeKind::Array(t, _) => { + t.to_rust_ty(ctx).to_ptr(arg_ty.is_const(), ctx.span()) + }, + TypeKind::Pointer(inner) => { + let inner = ctx.resolve_item(inner); + let inner_ty = inner.expect_type(); + if let TypeKind::ObjCInterface(_) = *inner_ty.canonical_type(ctx).kind() { + quote_ty!(ctx.ext_cx(), id) + } else { + arg_item.to_rust_ty(ctx) + } + }, + _ => { + arg_item.to_rust_ty(ctx) + } }; let arg_name = match *name { @@ -2263,6 +2294,85 @@ impl CodeGenerator for Function { } } +impl CodeGenerator for ObjCInterface { + type Extra = Item; + fn codegen<'a>(&self, + ctx: &BindgenContext, + result: &mut CodegenResult<'a>, + _whitelisted_items: &ItemSet, + _: &Item) { + let mut impl_items = vec![]; + let mut trait_items = vec![]; + + for method in self.methods() { + let method_name = ctx.rust_ident(method.name()); + + let body = quote_stmt!(ctx.ext_cx(), msg_send![self, $method_name]) + .unwrap(); + let block = ast::Block { + stmts: vec![body], + id: ast::DUMMY_NODE_ID, + rules: ast::BlockCheckMode::Default, + span: ctx.span(), + }; + + let sig = aster::AstBuilder::new() + .method_sig() + .unsafe_() + .fn_decl() + .self_() + .build(ast::SelfKind::Value(ast::Mutability::Immutable)) + .build(ast::FunctionRetTy::Default(ctx.span())); + let attrs = vec![]; + + let impl_item = ast::ImplItem { + id: ast::DUMMY_NODE_ID, + ident: ctx.rust_ident(method.rust_name()), + vis: ast::Visibility::Inherited, // Public, + attrs: attrs.clone(), + node: ast::ImplItemKind::Method(sig.clone(), P(block)), + defaultness: ast::Defaultness::Final, + span: ctx.span(), + }; + + let trait_item = ast::TraitItem { + id: ast::DUMMY_NODE_ID, + ident: ctx.rust_ident(method.rust_name()), + attrs: attrs, + node: ast::TraitItemKind::Method(sig, None), + span: ctx.span(), + }; + + impl_items.push(impl_item); + trait_items.push(trait_item) + } + + + let trait_block = aster::AstBuilder::new() + .item() + .pub_() + .trait_(self.name()) + .with_items(trait_items) + .build(); + + let ty_for_impl = quote_ty!(ctx.ext_cx(), id); + let impl_block = aster::AstBuilder::new() + .item() + .impl_() + .trait_() + .id(self.name()) + .build() + .with_items(impl_items) + .build_ty(ty_for_impl); + + result.push(trait_block); + result.push(impl_block); + result.saw_objc(); + } +} + + + pub fn codegen(context: &mut BindgenContext) -> Vec<P<ast::Item>> { context.gen(|context| { let counter = Cell::new(0); @@ -2296,6 +2406,32 @@ mod utils { use syntax::ast; use syntax::ptr::P; + + pub fn prepend_objc_header(ctx: &BindgenContext, + result: &mut Vec<P<ast::Item>>) { + let use_objc = if ctx.options().objc_extern_crate { + quote_item!(ctx.ext_cx(), + use objc; + ).unwrap() + } else { + quote_item!(ctx.ext_cx(), + #[macro_use] + extern crate objc; + ).unwrap() + }; + + + let id_type = quote_item!(ctx.ext_cx(), + #[allow(non_camel_case_types)] + pub type id = *mut objc::runtime::Object; + ) + .unwrap(); + + let items = vec![use_objc, id_type]; + let old_items = mem::replace(result, items); + result.extend(old_items.into_iter()); + } + pub fn prepend_union_types(ctx: &BindgenContext, result: &mut Vec<P<ast::Item>>) { let prefix = ctx.trait_prefix(); diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 73793b16..d424fcdf 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -17,3 +17,4 @@ pub mod module; pub mod ty; pub mod type_collector; pub mod var; +pub mod objc; diff --git a/src/ir/objc.rs b/src/ir/objc.rs new file mode 100644 index 00000000..b9fe280b --- /dev/null +++ b/src/ir/objc.rs @@ -0,0 +1,99 @@ +//! Objective C types + +use clang; +use clang_sys::CXChildVisit_Continue; +use clang_sys::CXCursor_ObjCInstanceMethodDecl; +// use clang_sys::CXCursor_ObjCSuperClassRef; +use super::context::BindgenContext; + +/// Objective C interface as used in TypeKind +/// +/// Also protocols are parsed as this type +#[derive(Debug)] +pub struct ObjCInterface { + /// The name + /// like, NSObject + name: String, + + /// List of the methods defined in this interfae + methods: Vec<ObjCInstanceMethod>, +} + +/// The objective c methods +#[derive(Debug)] +pub struct ObjCInstanceMethod { + /// The original method selector name + /// like, dataWithBytes:length: + name: String, + + /// Method name as converted to rust + /// like, dataWithBytes_length_ + rust_name: String, +} + +impl ObjCInterface { + fn new(name: &str) -> ObjCInterface { + ObjCInterface { + name: name.to_owned(), + methods: Vec::new(), + } + } + + /// The name + /// like, NSObject + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// List of the methods defined in this interfae + pub fn methods(&self) -> &Vec<ObjCInstanceMethod> { + &self.methods + } + + /// Parses the Objective C interface from the cursor + pub fn from_ty(cursor: &clang::Cursor, + _ctx: &mut BindgenContext) + -> Option<Self> { + let name = cursor.spelling(); + let mut interface = Self::new(&name); + + cursor.visit(|cursor| { + match cursor.kind() { + CXCursor_ObjCInstanceMethodDecl => { + let name = cursor.spelling(); + let method = ObjCInstanceMethod::new(&name); + + interface.methods.push(method); + } + _ => {} + } + CXChildVisit_Continue + }); + Some(interface) + } +} + +impl ObjCInstanceMethod { + fn new(name: &str) -> ObjCInstanceMethod { + let split_name: Vec<&str> = name.split(':').collect(); + + let rust_name = split_name.join("_"); + + ObjCInstanceMethod { + name: name.to_owned(), + rust_name: rust_name.to_owned(), + } + } + + /// The original method selector name + /// like, dataWithBytes:length: + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// Method name as converted to rust + /// like, dataWithBytes_length_ + pub fn rust_name(&self) -> &str { + self.rust_name.as_ref() + } +} diff --git a/src/ir/ty.rs b/src/ir/ty.rs index d0de72a0..8d01d905 100644 --- a/src/ir/ty.rs +++ b/src/ir/ty.rs @@ -10,6 +10,7 @@ use super::function::FunctionSig; use super::int::IntKind; use super::item::Item; use super::layout::Layout; +use super::objc::ObjCInterface; use super::type_collector::{ItemSet, TypeCollector}; /// The base representation of a type in bindgen. @@ -179,8 +180,12 @@ impl Type { /// Is this a incomplete array type? pub fn is_incomplete_array(&self, ctx: &BindgenContext) -> Option<ItemId> { match self.kind { - TypeKind::Array(item, len) => if len == 0 { Some(item) } else { None }, - TypeKind::ResolvedTypeRef(inner) => ctx.resolve_type(inner).is_incomplete_array(ctx), + TypeKind::Array(item, len) => { + if len == 0 { Some(item) } else { None } + } + TypeKind::ResolvedTypeRef(inner) => { + ctx.resolve_type(inner).is_incomplete_array(ctx) + } _ => None, } } @@ -287,7 +292,7 @@ impl Type { let mut remaining = chars; let valid = (first.is_alphabetic() || first == '_') && - remaining.all(|c| c.is_alphanumeric() || c == '_'); + remaining.all(|c| c.is_alphanumeric() || c == '_'); !valid } @@ -324,7 +329,8 @@ impl Type { TypeKind::Void | TypeKind::NullPtr | TypeKind::BlockPointer | - TypeKind::Pointer(..) => Some(self), + TypeKind::Pointer(..) | + TypeKind::ObjCInterface(..) => Some(self), TypeKind::ResolvedTypeRef(inner) | TypeKind::Alias(inner) | @@ -492,6 +498,9 @@ pub enum TypeKind { /// A named type, that is, a template parameter. Named, + + /// Objective C interface. Always referenced through a pointer + ObjCInterface(ObjCInterface), } impl Type { @@ -525,6 +534,8 @@ impl Type { TypeKind::BlockPointer | TypeKind::Pointer(..) => false, + TypeKind::ObjCInterface(..) => true, // dunno? + TypeKind::UnresolvedTypeRef(..) => { unreachable!("Should have been resolved after parsing!"); } @@ -566,7 +577,16 @@ impl Type { debug!("currently_parsed_types: {:?}", ctx.currently_parsed_types); let canonical_ty = ty.canonical_type(); - let kind = match ty.kind() { + + // Parse objc protocols as if they were interfaces + let mut ty_kind = ty.kind(); + if let Some(loc) = location { + if loc.kind() == CXCursor_ObjCProtocolDecl { + ty_kind = CXType_ObjCInterface; + } + } + + let kind = match ty_kind { CXType_Unexposed if *ty != canonical_ty && canonical_ty.kind() != CXType_Invalid => { debug!("Looking for canonical type: {:?}", canonical_ty); @@ -707,8 +727,7 @@ impl Type { } }; - TypeKind::TemplateAlias(inner_type, - args) + TypeKind::TemplateAlias(inner_type, args) } CXCursor_TemplateRef => { let referenced = location.referenced().unwrap(); @@ -806,6 +825,7 @@ impl Type { // // We might need to, though, if the context is already in the // process of resolving them. + CXType_ObjCObjectPointer | CXType_MemberPointer | CXType_Pointer => { let inner = Item::from_ty_or_ref(ty.pointee_type().unwrap(), @@ -887,6 +907,11 @@ impl Type { parent_id, ctx); } + CXType_ObjCInterface => { + let interface = ObjCInterface::from_ty(&location.unwrap(), ctx) + .expect("Not a valid objc interface?"); + TypeKind::ObjCInterface(interface) + } _ => { error!("unsupported type: kind = {:?}; ty = {:?}; at {:?}", ty.kind(), @@ -941,6 +966,10 @@ impl TypeCollector for Type { types.insert(id); } + TypeKind::ObjCInterface(_) => { + // TODO: + } + // None of these variants have edges to other items and types. TypeKind::UnresolvedTypeRef(_, _, None) | TypeKind::Named | @@ -198,6 +198,13 @@ impl Builder { self } + /// Generate '#[macro_use] extern crate objc;' instead of 'use objc;' + /// in the prologue of the files generated from objective-c files + pub fn objc_extern_crate(mut self, doit: bool) -> Self { + self.options.objc_extern_crate = doit; + self + } + /// Generate a C/C++ file that includes the header and has dummy uses of /// every type defined in the header. pub fn dummy_uses<T: Into<String>>(mut self, dummy_uses: T) -> Builder { @@ -542,6 +549,10 @@ pub struct BindgenOptions { /// Wether to whitelist types recursively. Defaults to true. pub whitelist_recursively: bool, + + /// Intead of emitting 'use objc;' to files generated from objective c files, + /// generate '#[macro_use] extern crate objc;' + pub objc_extern_crate: bool, } impl BindgenOptions { @@ -588,6 +599,7 @@ impl Default for BindgenOptions { conservative_inline_namespaces: false, generate_comments: true, whitelist_recursively: true, + objc_extern_crate: false, } } } diff --git a/src/options.rs b/src/options.rs index 307ea6b0..535eac4b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -49,6 +49,9 @@ pub fn builder_from_flags<I>(args: I) Arg::with_name("no-recursive-whitelist") .long("no-recursive-whitelist") .help("Avoid whitelisting types recursively"), + Arg::with_name("objc-extern-crate") + .long("objc-extern-crate") + .help("Use extern crate instead of use for objc"), Arg::with_name("builtins") .long("builtins") .help("Output bindings for builtin definitions, e.g. \ diff --git a/tests/expectations/Cargo.toml b/tests/expectations/Cargo.toml index 034aa141..e0da6d5a 100644 --- a/tests/expectations/Cargo.toml +++ b/tests/expectations/Cargo.toml @@ -9,3 +9,4 @@ authors = [ ] [dependencies] +objc = "0.2" diff --git a/tests/expectations/tests/objc_interface.rs b/tests/expectations/tests/objc_interface.rs new file mode 100644 index 00000000..027cf57e --- /dev/null +++ b/tests/expectations/tests/objc_interface.rs @@ -0,0 +1,15 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + +#![cfg(target_os="macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +pub trait Foo { } +impl Foo for id { } +pub trait bar { } +impl bar for id { } diff --git a/tests/expectations/tests/objc_interface_type.rs b/tests/expectations/tests/objc_interface_type.rs new file mode 100644 index 00000000..2d68ee50 --- /dev/null +++ b/tests/expectations/tests/objc_interface_type.rs @@ -0,0 +1,33 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + +#![cfg(target_os="macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +pub trait Foo { } +impl Foo for id { } +#[repr(C)] +#[derive(Debug, Copy)] +pub struct FooStruct { + pub foo: *mut id, +} +#[test] +fn bindgen_test_layout_FooStruct() { + assert_eq!(::std::mem::size_of::<FooStruct>() , 8usize); + assert_eq!(::std::mem::align_of::<FooStruct>() , 8usize); +} +impl Clone for FooStruct { + fn clone(&self) -> Self { *self } +} +extern "C" { + pub fn fooFunc(foo: id); +} +extern "C" { + #[link_name = "kFoo"] + pub static mut kFoo: *const id; +} diff --git a/tests/expectations/tests/objc_method.rs b/tests/expectations/tests/objc_method.rs new file mode 100644 index 00000000..e2db24f6 --- /dev/null +++ b/tests/expectations/tests/objc_method.rs @@ -0,0 +1,17 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + +#![cfg(target_os="macos")] + +#[macro_use] +extern crate objc; +#[allow(non_camel_case_types)] +pub type id = *mut objc::runtime::Object; +pub trait Foo { + unsafe fn method(self); +} +impl Foo for id { + unsafe fn method(self) { msg_send!(self , method) } +} diff --git a/tests/headers/objc_interface.h b/tests/headers/objc_interface.h new file mode 100644 index 00000000..af84bf92 --- /dev/null +++ b/tests/headers/objc_interface.h @@ -0,0 +1,8 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo +@end + +@protocol bar +@end diff --git a/tests/headers/objc_interface_type.h b/tests/headers/objc_interface_type.h new file mode 100644 index 00000000..31d33664 --- /dev/null +++ b/tests/headers/objc_interface_type.h @@ -0,0 +1,13 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo +@end + +struct FooStruct { + Foo *foo; +}; + +void fooFunc(Foo *foo); + +static const Foo *kFoo; diff --git a/tests/headers/objc_method.h b/tests/headers/objc_method.h new file mode 100644 index 00000000..91645f16 --- /dev/null +++ b/tests/headers/objc_method.h @@ -0,0 +1,7 @@ +// bindgen-flags: --objc-extern-crate -- -x objective-c +// bindgen-osx-only + +@interface Foo +- (void)method; +// TODO: argument methods +@end diff --git a/tests/tests.rs b/tests/tests.rs index 05c8ad2c..2886f8db 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -76,7 +76,7 @@ fn create_bindgen_builder(header: &PathBuf) // Scoop up bindgen-flags from test header let mut flags = Vec::with_capacity(2); - for line in reader.lines().take(2) { + for line in reader.lines().take(3) { let line = try!(line); if line.contains("bindgen-flags: ") { let extra_flags = line.split("bindgen-flags: ") @@ -87,6 +87,12 @@ fn create_bindgen_builder(header: &PathBuf) } else if line.contains("bindgen-unstable") && cfg!(feature = "llvm_stable") { return Ok(None); + } else if line.contains("bindgen-osx-only") { + let prepend_flags = ["--raw-line", "#![cfg(target_os=\"macos\")]"]; + flags = prepend_flags.into_iter() + .map(ToString::to_string) + .chain(flags) + .collect(); } } |