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

Allow constructing subclass of abstract class #1206

Merged
merged 11 commits into from
Feb 20, 2023

Conversation

russelltg
Copy link
Contributor

@russelltg russelltg commented Jan 15, 2023

Fixes #774

I have a solid start here, but I think I'm going to need some guidance to get this complete. I have it all working except overloads, and I need to somehow get the C++ types of the arguments from an Api<FnPrePhase2>

I put in a placeholder args: Vec<QualifiedName> in my Signature object but I'm not seeing anything in Function that I could use to fill that. Also, ideally the arguments wouldn't be the QualifiedName so type aliases don't get it confused

  • Tests pass
  • Appropriate changes to README are included in PR

@google-cla
Copy link

google-cla bot commented Jan 15, 2023

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@@ -6,106 +6,209 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::HashMap;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please could you use indexmap::map::IndexMap as HashMap so that we get predictable iteration order.

@adetaylor
Copy link
Collaborator

Thanks for having a stab at this! Looks to be along the right lines, and good job figuring out the convoluted codebase well enough to get this in the right places.

I need to somehow get the C++ types of the arguments from an Api

FnAnalysis::params is what you need. This contains the Rust types, but they're "converted", which means we have things like UniquePtr<Foo> rather than whatever bindgen gave us. Therefore the syn::Types in that field are essentially canonical and can be relied upon to uniquely represent C++ types; they should be good enough for overload resolution.
i.e. Rather than compare QualifiedNames, you should compare syn::Types, which is what you'll find in this location.

I'm not going to take a more detailed look at this until the tests are green and you tell me that it's good to go. Good luck!

@russelltg
Copy link
Contributor Author

russelltg commented Jan 18, 2023

Thanks for the help there, I've figured out the overload resolution stuff.

The only missing thing is it doesn't really support pure virtual dtors in either the base or the sub class. I don't think pure virtual dtors get passed to the analysis? Or at least I don't see it.

@russelltg
Copy link
Contributor Author

I'm going to go ahead and mark this as ready for review, as I think it's useful and correct as is. I'm happy to deal with the pure virtual dtor edgecase if you think that's important/worthwhile.

@russelltg russelltg marked this pull request as ready for review January 18, 2023 04:45
@adetaylor
Copy link
Collaborator

Thanks, I'm travelling right now so I'm unlikely to take a look at this for a few days, but I'll get there.

Copy link
Collaborator

@adetaylor adetaylor left a comment

Choose a reason for hiding this comment

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

Sorry for the delay on reviewing this.

It's looking great!

My comments are:

  • a load of nit picks and suggestions
  • one substantial comment that I can't easily follow what happens in cases where our data structures are incomplete (for unforeseen reasons) - I want to make sure we're erring on the side of regarding classes as abstract. I'm sure I could figure it out by peering more carefully at it, but the fact it's hard to follow suggests it could do with a little bit more in the way of comments.

kind:
FnKind::Method {
impl_for: self_ty_name,
method_kind: MethodKind::Virtual(constness),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please could you reduce the duplication of code here by just matching as far as FnKind::Method in the outer match, and then having an additional match on the method_kind to choose between defined and undefined.

Api::Struct {
analysis: PodAndConstructorAnalysis {
pod: PodAnalysis {
for api in apis.iter() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please could you look into whether fields_and_bases_first can simplify this code? This is an iterator through the APIs in a specific order which should remove the need for the outer while loop.

I think fields_and_bases_first is newer than the original code in this function, so that's why the code didn't use it originally. As you're reworking this function anyway, it would be good to see if you can switch to this at the same time?

If it doesn't work out - that's fine. (But please could you add a comment saying why not?)


let state = class_states.entry(name.name.clone()).or_default();

newset = newset
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you do newset.retain(...)?

class_states
.get(b)
.map(|cs| !cs.undefined.is_empty())
.unwrap_or(false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are a lot of negatives in these two lines (!, undefined, is_empty, false) so it's not too easy to read. Could you add a comment?

I'm trying to convince myself that unwrap_or(false) is correct. Goal 1 is to avoid generating faulty bindings that will prevent autocxx working with any codebase, so if we're unsure we should err on the side of regarding a class as abstract. I'm not able to convince myself whether false is right for that. Under what circumstances would a class state not be recorded in the maps?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've now been through the whole thing and this turns out to be my only substantial comment. I just want to be 100% sure we're erring on the side of caution if there are unforeseen circumstances which might result in our data structures being incomplete.

})
.collect()

_ => {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could be if let instead, now you're no longer doing anything with the other cases


"};
let rs = quote! {
static_assertions::assert_impl_all!(ffi::FullyDefined: moveit::CopyNew);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like it!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I wish I'd thought of using this for loads of the other tests!

PodAnalysis {
bases,
kind: TypeKind::Pod | TypeKind::NonPod,
castable_bases,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know you just copied the old code pattern here, but I think it's got more and more yucky as more fields have gradually accumulated in PodAnalysis. If you want to simplify this to something like:

for api in apis.iter_mut() {
  if let Api::Struct(api) = api {
     if api is abstract {
        api.blah.blah.blah.kind = TypeKind::Abstract
    }
   }
}

(or something like that) that would be fine with me. But also, feel free not.

name: name.cpp_name(),
args: params
.iter()
.skip(1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add comment // skip receiver or somesuch


// class Partial5 : public Base {
// public:
// ~Partial5() = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Totally happy not to address this case now, but please could you add a comment in this test case explaining why this is commented out.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(also if you could file a new github issue that'd be great)

@russelltg
Copy link
Contributor Author

russelltg commented Jan 30, 2023

fields_and_bases_first is super cool, thanks for pointing me there, that significnatly simplifies the code. The only problem....it doesn't seem to do methods first. It's definitely emitting the Struct api's before the methods.....

I suppose the name is evident--it's fields and bases first not fields, methods, and bases first. I think the cleanest way to fix this would be to either have a new traverser that does to methods first (or just edit fields_and_bases_first to do that) or just go through the API list twice like I was doing previously

@adetaylor
Copy link
Collaborator

I suppose the name is evident--it's fields and bases first not fields, methods, and bases first. I think the cleanest way to fix this would be to either have a new traverser that does to methods first (or just edit fields_and_bases_first to do that)

Originally it did do this, but it turned out this caused cycles, because (I think) some types of constructor need to depend on the type they're constructing. You'd be most welcome to try to solve this but I think it was Hard. (I can't remember the details). So going through the list twice is probably best.

@@ -97,6 +97,10 @@ impl<P: AnalysisPhase> ApiVec<P> {
self.apis.iter()
}

pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Api<P>> {
self.apis.iter_mut()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, I'm really sorry, I offered you bad advice here. iter_mut is considered harmful here for the reasons explained in this comment:
https://github.com/google/autocxx/pull/1206/files/8d4d785f4105c3332b1f77f8f0c0756ef9e72545..c90cd896eca376977c87e04707ec48ba01b243bd#diff-dd61892ff96d055fbcab83c5eecc76c8b6120c3eb2f1b5687a8c025435200fcdL32
I just plain forgot! Sorry for the rework.

let mut abstract_classes = HashSet::new();

for api in fields_and_bases_first(apis.iter()) {
match dbg!(api) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't forget to remove this dbg

@russelltg
Copy link
Contributor Author

This is ready for review @adetaylor :)

@adetaylor
Copy link
Collaborator

I'm good to merge this once the formatting problems are fixed!

@adetaylor
Copy link
Collaborator

Thanks very much for this!

@adetaylor adetaylor merged commit 5540b86 into google:main Feb 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

autocxx does not recognize concrete subclasses of abstract base classes
2 participants