-
Notifications
You must be signed in to change notification settings - Fork 720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial Objective C support #443
Conversation
The failure is due to me hardwiring the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woot, this is great! Thanks for working on this :)
It looks quite good! As you mentioned, it still needs a bunch of work though.
I'm totally fine with landing this incrementally, but with some constraints:
- First, it needs docs (also, CI is failing because we force documentation).
- Second, it needs tests (this is something old bindgen really lacked when I rewrote it, and it's definitely necessary to avoid falling in a spiral of bugs and regressions).
Again, this is awesome! Thanks a lot for working on this :)
@@ -2074,6 +2091,18 @@ impl ToRustTy for FunctionSig { | |||
// [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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling canonical_type
two times, let's use a match
expression.
result: &mut CodegenResult<'a>, | ||
_whitelisted_items: &ItemSet, | ||
_: &Item) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: whitespace should go before attempting to merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this newline
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]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: space after ,
.
Ok(item) if ctx.resolve_item(item).kind().is_function() => item, | ||
let signature = | ||
match Item::parse(cur, Some(potential_id), ctx) { | ||
Ok(item) if ctx.resolve_item(item) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you leave formatting patches in a different commit? That will make this much more pleasurable to review :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, I thought I cleaned them all. I have the editor set up on formatting on save, and I manually cleaned up rest of these.
@@ -1070,6 +1071,7 @@ impl<'ctx> BindgenContext<'ctx> { | |||
let name = item.canonical_path(self)[1..].join("::"); | |||
debug!("whitelisted_items: testing {:?}", name); | |||
match *item.kind() { | |||
ItemKind::ObjCInterface(_) => true, // TODO: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason you have a full ItemKind
for this?
Does an interface have the same characteristics as, let's say, a struct
in C? I mean, can you forward-declare them, point to them, etc?
If so, you probably want to just fold it into Type
instead, since you'd share a lot of code for detecting duplicates, going to the definition of the type, etc.
(Edit: after reviewing the rest of the commit I've seen you've also added TypeKind::ObjCInterface
, you might want to just use ItemKind::Type
if there isn't a better reason to have a full ItemKind
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frankly, I wasn't sure, and I think I just looked at the try_parse! and thought ‘thats what I need’.
Forward declaration is a bit weird, using @class Foo;
, this gives a cursor type CXCursor_ObjCClassRef, have not looked at that more closely.
One other interesting bit is protocols, those are for extending more methods for existing interface. Those come in as CXCursor_ObjCCategoryDecl, so I guess they could be handled completely separate (and generate their own trait)
|
||
match cursor.kind() { | ||
CXCursor_ObjCInterfaceDecl | | ||
CXCursor_ObjCProtocolDecl => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, does libclang return a specific CXTypeKind
for these (you can see this printing cursor.cur_type()
)? If so, then merging it into Type
is probably the best path forward, otherwise, it'll probably need a bit of thinking to get it right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, so I did that.
One annoying hack was that protocols were not types, so I forced it to think they are interfaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of type do they report?
/* let referenced = location.unwrap().referenced().unwrap(); | ||
let referenced_ty = referenced.cur_type(); | ||
let declaration = referenced_ty.declaration(); | ||
println!("referenced={:?}", referenced); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, welcome to the joy of debugging libclang :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got me with my pants down :)
println!("cur type ={:?}", location.unwrap().cur_type()); | ||
println!("objc encoding ={:?}", ty.objc_encoding());*/ | ||
|
||
let inner = Item::from_ty_or_ref(ty.pointee_type().unwrap(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pretty much shares the body with CXType_Pointer
, reference, et. al, right? If so, let's just fold it there when you're done with debugging it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -869,6 +874,60 @@ impl Type { | |||
parent_id, | |||
ctx); | |||
} | |||
CXType_ObjCInterface => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, probably you don't want a new itemid
for each Interface you parse, and I don't see any deduplication going on, so probably you should make this the actual container of your data (and use cursor.definition()
to properly go to the actual definition, just in case you find a forward declaration before). You probably need to do roughly the same thing we do for CXType_Record
(which is a plain struct), just storing most of the specific interface-related data you want.
@@ -697,6 +697,12 @@ impl<'ctx> Bindings<'ctx> { | |||
try!(writer.write("\n".as_bytes())); | |||
} | |||
|
|||
// TODO: | |||
|
|||
try!(writer.write("use objc;\n\n".as_bytes())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of printing this here we should use something like what we do to insert the __BindgenUnionField
, etc. See CodegenResult::saw_union
and similar methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
I'm still missing documentation and testing. |
Right now we rely on the automatic language detection for most of the tests, but you can annotate a test with So it seems you either want |
let arg_ty = if let TypeKind::Array(t, _) = *arg_ty.canonical_type(ctx).kind() { | ||
|
||
|
||
let arg_ty = match *arg_ty.canonical_type(ctx).kind() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation and spacing is quite odd in all this block right now, not sure if intended :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh, I wonder how atom/rustfmt missed that o_O
@@ -2062,6 +2077,13 @@ impl ToRustTy for Type { | |||
let ident = ctx.rust_ident(&name); | |||
quote_ty!(ctx.ext_cx(), $ident) | |||
} | |||
|
|||
TypeKind::ObjCInterface(..) => { | |||
let name = item.canonical_name(ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be enough if you can't mix objective-c with namespaces. If you can, you need to handle them somehow (hint: you could use utils::build_templated_path
, but pass always an empty vec![]
of template arguments).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang will give an actual error if interface embedded in namespace. And to even get that error, it needs to be run with -x objective-c++ mode
Heres my latest, with some rudimentary tests and docs. I'm using the id here to mimic the cocoa-rs convention, thought I think some have suggested trying |
☔ The latest upstream changes (presumably #456) made this pull request unmergeable. Please resolve the merge conflicts. |
You need to add the |
Quite, it didn't seem quite as simple. |
Ehh, botched rebase, I was sure I had run the tests one more time. Sorry for the noise |
ngggh. of course, it needs some cfg guards I'm not quite sure what is the best option. Should I always put in the generated objc prologue #[cfg(bindgen_objc)]? target_os="macos" would seem too harsh, as in theory this could be used on other platforms as well, even if rare. Guarding just the tests is one option too, I guess, but not sure how to implement that |
Woo, finally a passing commit! :D I opted to add cfg(target_os="macos") to the objc tests. It seemed to be the simplest change, though I'm not sure the .contains("objc_") is good enough toggle for it. I guess this also means that the travis config would need a osx target as well, to have those tests be exercised. |
☔ The latest upstream changes (presumably #461) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I think I'm fine with this, mostly nits :)
@@ -98,10 +98,17 @@ fn create_bindgen_builder(header: &PathBuf) | |||
let header_str = try!(header.to_str() | |||
.ok_or(Error::new(ErrorKind::Other, "Invalid header file name"))); | |||
|
|||
let objc_guard; | |||
if header_str.contains("objc_") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of this, let's add a new line to the test configuration (see the if line.contains(bindgen-flags
check above). // bindgen-osx-only
maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, that makes so much more sense, obvious even.
|
||
impl ObjCInstanceMethod { | ||
fn new(name: &str) -> ObjCInstanceMethod { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this initial newline.
fn new(name: &str) -> ObjCInstanceMethod { | ||
|
||
let split_name: Vec<&str> = name.split(':').collect(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may also go away if you want :)
pub struct ObjCInstanceMethod { | ||
/// The original method selector name | ||
/// like, dataWithBytes:length: | ||
pub name: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of making these public, could/should we make them private and add accessors?
pub name: String, | ||
|
||
/// List of the methods defined in this interfae | ||
pub methods: Vec<ObjCInstanceMethod>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of making these public, could/should we make them private and add accessors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
.unsafe_() | ||
.fn_decl() | ||
.self_() | ||
.build(ast::SelfKind::Value(ast::Mutability::Immutable))//ast::SelfKind::Region(None, ast::Mutability::Mutable)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove or reword the comment please :)
let arg_ty = if let TypeKind::Array(t, _) = *arg_ty.canonical_type(ctx).kind() { | ||
|
||
|
||
let arg_ty = match *arg_ty.canonical_type(ctx).kind() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please fix the alignment in this block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damnit, I fixed this once already, but somehow rustfmt got really confused in this block :(
@@ -2142,10 +2162,24 @@ 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() { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove the whitespace between the comment and the let
.
.with_items(trait_items) | ||
.build(); | ||
|
||
let ty_for_impl = aster::AstBuilder::new() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same as: quote_ty!(ctx.ext_cx(), id)
, which I think is clearer.
@@ -2109,6 +2125,10 @@ impl ToRustTy for Type { | |||
let ident = ctx.rust_ident(&name); | |||
quote_ty!(ctx.ext_cx(), $ident) | |||
} | |||
TypeKind::ObjCInterface(..) => { | |||
let ident = ctx.rust_ident("id"); | |||
quote_ty!(ctx.ext_cx(), $ident) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just quote_ty!(ctx.ext_cx(), id)
should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah didn't realise that, lot cleaner for sure!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two little little nits more, an this is ready to go IMO :)
@fitzgen any concern with this?
@@ -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") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to use take(3)
above now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine :)
let inner = ctx.resolve_item(inner); | ||
let inner_ty = inner.expect_type(); | ||
if let TypeKind::ObjCInterface(_) = *inner_ty.canonical_type(ctx).kind() { | ||
aster::ty::TyBuilder::new().id("id") | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, I guess rustfmt really likes to mess with this assignment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that or I forgot add the chunk, nevertheless, I've disabled rustfmt, and hopefully it now makes sense
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.
Looks good to me! I've opened #464 to re-enable testing on OSX, after that lands this is good to go \o/ |
@bors-servo r+ Thanks for this! :) |
📌 Commit 4736263 has been approved by |
Add initial Objective C support Hi! I made an attempt to support parsing objective c headers. This is very much incomplete, and likely not quite yet ready for merging, but I thought I'd share the progress so far. Comments appreciated, I'm still very much a newbie in just about everything related to this change (rust as a language, rust ast, libclang, rust-bindgen), and there were many parts of code I wasn't quite sure about. Commit message: It parses interfaces and protocol but ignores base classes, methods that don’t have arguments, the arguments are 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.
☀️ Test successful - status-travis |
Hi! I made an attempt to support parsing objective c headers. This is very much incomplete,
and likely not quite yet ready for merging, but I thought I'd share the progress so far.
Comments appreciated, I'm still very much a newbie in just about everything related to this change (rust as a language, rust ast, libclang, rust-bindgen), and there were many parts of code I wasn't quite sure about.
Commit message:
It parses interfaces and protocol but ignores base classes,
methods that don’t have arguments, the arguments are 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.