Skip to content
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

Objective-c inheritance support #1750

Merged
merged 1 commit into from
May 12, 2020
Merged

Conversation

simlay
Copy link
Contributor

@simlay simlay commented Mar 19, 2020

This PR adds objective-c inheritance support by generating the impl IFoo for Foo { } and impl IBar for Foo { } (see the unit test) which generally match the objective-c inheritance patterns.

I also changed all the unsafe fns do be internal unsafe blocks to mimic that of @SSheldon's objc-foundation crate. I'm actually not sure this is the best way to do it. Would love some comments as to why not.

I've got a branch of the bindgen book that I'm working on as well. I'm just gonna make it a separate PR and link to a uikit-sys crate.

Similar to #1722 I did, I don't have all the versions of clang so I'll fix CI once we discuss this more.

@highfive
Copy link

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!

Copy link
Contributor

@emilio emilio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with the unsafe change I think.

I think looping over all the items to find the base class is not the way to go though. Does objective-c allow for multiple interfaces with the same name under different C++ namespaces? If so it seems this would break.

Is there any chance this could look more similar to the C++ case?

src/codegen/mod.rs Outdated Show resolved Hide resolved
src/ir/objc.rs Outdated Show resolved Hide resolved
src/ir/objc.rs Outdated Show resolved Hide resolved
src/ir/objc.rs Outdated
@@ -179,6 +183,35 @@ impl ObjCInterface {
let name = c.spelling();
interface.template_names.push(name);
}
CXCursor_ObjCSuperClassRef => {
let items_map = ctx.items();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looping over all the items means that this has really really bad performance characteristics. Which is pretty unfortunate... Do you really need to do a name lookup? I think Item::from_ty_or_ref would do the right thing if you give it the cursor? See the handling of CXCursor_CXXBaseSpecifier and such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to get around the need for the name lookup. After a bit of thought and reading through the Base stuff in the handling of CXCursor_CXXBaseSpecifier, I think the thing that would be similar would be something like:

struct Inheritance {
    inheritance_type: InheritanceType,
    parent_name: String,
}
enum InheritanceType {
    Protocol,
    Class,
}

The part about Class and Protocol in InheritanceType is because the logic from the needle, haystack stuff I took from the protocol logic so it would make sense to fix both of those handlers. I'll think about it more and maybe implement it if it's not too gross. I'm not sure what goes on when you iterate through ctx.items() but I kinda assume it's the whole syntax tree... 🤢

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bleh, I tried this a couple different ways based on the above idea similar to how
CXCursor_CXXBaseSpecifier handling works. The issue is the templates. If we don't need to know if the parent is a template, then we can just use format!("I{}", c.spelling()) stored in the ir for the objc codegen.

I propose that we do one of the two options:

  • take out template tags. I don't know how you'd use the template things with with the generated bindings anyway so why do we have them?
  • Deal with the current bad performance - I've tested it with uikit-sys, it takes ~2.5 minutes to build so it's not the end of the world but people will definitely dislike that crate :/

I'm a bit up for doing option 1 and excluding generics support as a whole. @scoopr, do you have opinions on that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, are you talking generally about generics support for objc code or just when there is a parent class that has generic params?

I would really like that NSArray<Foo> works, but I guess I could be convinced that it is not critical with parent classes. Perhaps stuff like NSMutableArray would look a bit odd?

Yes, the generics desugar to id, so of course everything works without them, so if you must remove them completely, so be it. But I would find that kind of a missed opportunity.

If I had to choose, I would really try to live with bad performance, and perhaps try to optimize it separately if it indeed is that bad. But in the end I guess it is more important to be useful than idealistic.

I tried to understand the code here, but it would take a bit too long to actually ingest what happens here, and why other approaches don't work. Just as random idea, can the iterating of the items be done as a separate pass later, when you know all the needles to search for, so you could do only a single iteration of the items?

Also the objc_template.rs expectation tests shows some method generic params as u64 under libclang-9, though not in libclang-5, so are these types already being dropped from methods (by accident or something else)? The trait has then nevertheless

Copy link
Contributor Author

@simlay simlay Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, are you talking generally about generics support for objc code or just when there is a parent class that has generic params?

Yeah.

Just as random idea, can the iterating of the items be done as a separate pass later, when you know all the needles to search for, so you could do only a single iteration of the items?

Also the objc_template.rs expectation tests shows some method generic params as u64 under libclang-9, though not in libclang-5, so are these types already being dropped from methods (by accident or something else)? The trait has then nevertheless

Yeah, that seems to be an issue with just libclang-9 that probably got merged in #1702. I'm not really sure why that is though. We should file that as a bug and investigate more.

If I had to choose, I would really try to live with bad performance, and perhaps try to optimize it separately if it indeed is that bad. But in the end I guess it is more important to be useful than idealistic.

That's good with me.

@emilio Is there anything else I can do other than fix the tests for this PR to get merged?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think looping through all the items here is not acceptable. Can you explain why the partial type approach (what the base classes / pointers / etc does) doesn't work?

src/codegen/mod.rs Outdated Show resolved Hide resolved
src/ir/objc.rs Outdated
@@ -179,6 +183,35 @@ impl ObjCInterface {
let name = c.spelling();
interface.template_names.push(name);
}
CXCursor_ObjCSuperClassRef => {
let items_map = ctx.items();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think looping through all the items here is not acceptable. Can you explain why the partial type approach (what the base classes / pointers / etc does) doesn't work?

src/ir/objc.rs Outdated
if Some(needle.as_ref()) == ty.name() {
debug!("Found parent class {:?}", item);
interface.parent_classes.push(id);
for i in class.parent_classes.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clones the whole vector unnecessarily.

@simlay
Copy link
Contributor Author

simlay commented May 4, 2020

I still think looping through all the items here is not acceptable. Can you explain why the partial type approach (what the base classes / pointers / etc does) doesn't work?

I managed to get Item:: from_ty_or_ref to work in the code generation phase, it gave me a different ItemId which is a TypeKind::ResolvedTypeRef(TypeId) so I had to call ctx.resolve_type twice so that I could traverse up to the parent's parent and such.

I think a similar thing could be done for the CXCursor_ObjCProtocolRef handling. I wouldn't mind adding that as well.

I'm not really sure how I feel about the solution but I really couldn't figure out how to get a parent's parent all the way up the tree.

I also updated the unit tests to be more useful.

Copy link
Contributor

@emilio emilio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this looks much better! Just need to resolve through type refs in codegen to simplify a bit and not have to manually write that loop.

Doing the same for protocols seems fine, but probably separate patch?

src/codegen/mod.rs Outdated Show resolved Hide resolved
@simlay simlay force-pushed the objc-inheritance branch 2 times, most recently from 0594d09 to b874115 Compare May 8, 2020 20:14
@emilio
Copy link
Contributor

emilio commented May 12, 2020

Thanks for bearing with me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants