-
Notifications
You must be signed in to change notification settings - Fork 151
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
Conversation
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; |
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 could you use indexmap::map::IndexMap as HashMap
so that we get predictable iteration order.
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'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! |
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. |
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. |
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. |
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.
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), |
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 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() { |
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 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 |
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.
Can you do newset.retain(...)
?
class_states | ||
.get(b) | ||
.map(|cs| !cs.undefined.is_empty()) | ||
.unwrap_or(false) |
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.
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?
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.
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() | ||
|
||
_ => {} |
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 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); |
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.
I like 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.
I wish I'd thought of using this for loads of the other tests!
PodAnalysis { | ||
bases, | ||
kind: TypeKind::Pod | TypeKind::NonPod, | ||
castable_bases, |
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.
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) |
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 add comment // skip receiver
or somesuch
|
||
// class Partial5 : public Base { | ||
// public: | ||
// ~Partial5() = 0; |
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.
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.
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.
(also if you could file a new github issue that'd be great)
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 |
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. |
engine/src/conversion/apivec.rs
Outdated
@@ -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() |
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.
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) { |
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.
Don't forget to remove this dbg
This is ready for review @adetaylor :) |
I'm good to merge this once the formatting problems are fixed! |
Thanks very much for this! |
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 mySignature
object but I'm not seeing anything inFunction
that I could use to fill that. Also, ideally the arguments wouldn't be the QualifiedName so type aliases don't get it confused