-
Notifications
You must be signed in to change notification settings - Fork 242
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
Optionally derive Debug
for structs that allow it
#1040
base: master
Are you sure you want to change the base?
Conversation
Debug
for structs that allow itDebug
for structs that allow it
I actually meant to open this PR against my fork, not upstream. It may be kind of rough, but it would be helpful to know whether I'm at least on the right track! |
Because I opened this against the wrong repo, it also contains #1030. Sorry, I'm a bit of a github noob. |
The platform differences in va_list are making it hard to unit test this. On Linux, the struct (S3) has a lifetime parameter, but the return type of the translated fn that returns it doesn't have one, so the build fails (that must be a bug, right?). It's also unsafe to construct the object, so it can't just be a static because nothing will wrap it in |
Usually I stack PRs, so PR #1 is merging into |
ec8fdd3
to
785b4cf
Compare
@@ -1194,6 +1194,12 @@ struct ConvertedVariable { | |||
pub init: TranslationResult<WithStmts<Box<Expr>>>, | |||
} | |||
|
|||
pub struct ConvertedStructFields { |
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 needed to be pub
because convert_struct_fields
is, but it seems like both of those should possibly be pub(crate)
. I didn't want to make that change because I didn't see very many instances of pub(crate)
in c2rust-transpile. Should I leave that as is or make these crate-private?
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.
pub
should be fine here. We haven't been careful with the pub
API for c2rust-transpile
since it's meant to be used only from the CLI (being able to call it programmatically would be nice, but it's tricky, as the C++ clang
part of it isn't entirely thread-safe). So since most of the types here are pub
but probably shouldn't be, it's fine to make this one pub
, too.
This is now rebased and no longer includes the commits that are already in #1030. |
785b4cf
to
ceb8c28
Compare
Latest push changes this CLI argument and rebases the branch. |
} | ||
|
||
impl ExtraDerive { | ||
fn to_transpiler_derive(&self) -> Derive { |
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.
These are two different types because I didn't want to make c2rust-transpile depend on clap so it could implement ValueEnum. It works out because allowing --derive Clone
wouldn't make much sense anyway if that's enabled by default.
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 makes sense. One minor (clippy
) suggestion:
fn to_transpiler_derive(&self) -> Derive { | |
fn to_transpiler_derive(self) -> Derive { |
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.
The way the derives are calculated now (including for the existing ones) is much more elegant (thanks!). I do have a bunch of suggestions, though, but it looks quite good so far.
.tcfg | ||
.derives | ||
.iter() | ||
.flat_map(|derive| { |
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.
.flat_map(|derive| { | |
.filter_map(|derive| { |
They're actually functionally equivalent, but .filter_map
is a bit clearer on the intent.
.flat_map(|derive| { | ||
let can_derive = match derive { | ||
Derive::Clone | Derive::Copy => !contains_va_list, | ||
Derive::Debug => can_derive_debug, | ||
Derive::BitfieldStruct => has_bitfields, | ||
}; | ||
if can_derive { | ||
Some(derive.to_string()) | ||
} else { | ||
None | ||
} | ||
}) |
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.
Actually you can split them up and it's much cleaner.
.flat_map(|derive| { | |
let can_derive = match derive { | |
Derive::Clone | Derive::Copy => !contains_va_list, | |
Derive::Debug => can_derive_debug, | |
Derive::BitfieldStruct => has_bitfields, | |
}; | |
if can_derive { | |
Some(derive.to_string()) | |
} else { | |
None | |
} | |
}) | |
.filter(|derive| match derive { | |
Derive::Clone | Derive::Copy => !contains_va_list, | |
Derive::Debug => can_derive_debug, | |
Derive::BitfieldStruct => has_bitfields, | |
}) | |
.map(|derive| format!("{derive:?}") |
.flat_map(|derive| { | ||
let can_derive = match derive { | ||
Derive::Clone | Derive::Copy | Derive::Debug => true, | ||
Derive::BitfieldStruct => false, | ||
}; | ||
if can_derive { | ||
Some(derive.to_string()) | ||
} else { | ||
None | ||
} | ||
}) |
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.
.flat_map(|derive| { | |
let can_derive = match derive { | |
Derive::Clone | Derive::Copy | Derive::Debug => true, | |
Derive::BitfieldStruct => false, | |
}; | |
if can_derive { | |
Some(derive.to_string()) | |
} else { | |
None | |
} | |
}) | |
.filter(|derive| match derive { | |
Derive::Clone | Derive::Copy | Derive::Debug => true, | |
Derive::BitfieldStruct => false, | |
}) | |
.map(|derive| format!("{derive:?}") |
for field_decl_id in fields.as_ref().unwrap_or(&vec![]) { | ||
let field_decl = self | ||
.ast_context | ||
.get_decl(&field_decl_id) |
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.
.get_decl(&field_decl_id) | |
.get_decl(field_decl_id) |
let can_derive = match derive { | ||
Derive::Clone | Derive::Copy => !contains_va_list, | ||
Derive::Debug => can_derive_debug, | ||
Derive::BitfieldStruct => has_bitfields, | ||
}; |
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.
Writing the logic like this is much cleaner and easier to understand than how it was before. That's great!
FieldType::Regular { field, ctype, .. } => { | ||
// Struct is only debuggable if all regular fields are | ||
// debuggable. Assume any fields added by the translator | ||
// such as padding are debuggable | ||
can_derive_debug &= self.can_struct_field_derive_debug(ctype); | ||
field_entries.push(*field) | ||
} |
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 think this is more readable if it's computed similar to how contains_va_list
is:
// A struct is only debuggable if all regular fields are debuggable.
// Assume any fields added by the translator, such as padding, are debuggable.
let can_derive_debug = reorganized_fields.iter().all(|field| match field {
&FieldType::Regular { ctype, .. } => self.can_struct_field_derive_debug(ctype),
_ => true,
});
// A union or struct containing a union cannot derive Debug | ||
CTypeKind::Union(..) => false, | ||
|
||
// All translated non-union C types can derive Debug |
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 about other recursive types like typedef
s, arrays, etc.?
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 man, really good point
/// Returns true if a struct field with this type can be part of a struct that derives `Debug` | ||
fn can_struct_field_derive_debug(&self, ctype: CTypeId) -> bool { |
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.
Is this only checking if a type recursively contains any union
s? If so, can you rename the fn
(and other variables, fields) to contains_union
. That's more similar to contains_va_list
and how that's used to calculate if Clone, Copy
can be derived. And it can potentially be used by other derives that have similar requirements to Debug
, as a bunch probably have similar behavior in regards to union
s.
c2rust-transpile/src/lib.rs
Outdated
@@ -91,6 +93,14 @@ pub struct TranspilerConfig { | |||
pub binaries: Vec<String>, | |||
} | |||
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Display)] |
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.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Display)] | |
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] |
Can we just use format!("{derive:?}")
instead of depending on strum::Display
? Or impl Display
by just delegating to Debug
(I also have another more complex idea that adds better checks that are useful elsewhere, but that's probably overkill for now).
} c; | ||
} b; | ||
} a; | ||
} NotDebuggable1; |
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 can test this is not Debug
using static_assertions::assert_not_impl_any!
. There's also a similar assert_impl_one!
you can use to do the check you're already doing, that a type is Debug
.
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 way you also don't need to generate a value of the types, just the type itself.
None | ||
} | ||
}) | ||
.collect_vec(); |
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.
.collect_vec(); | |
.collect::<Vec<_>>(); |
Equally simple and doesn't require itertools
; same with the other .collect_vec()
.
Marking as a draft while I make the fixes. Thanks for all the feedback! |
This is allowed for any struct that does not contain any unions recursively. The behavior
is optional and can be enabled with
--derive Debug
.