Empty Opaque structs generated due to forward declaration.
Closed this issue · 3 comments
In C, you can make forward declarations like this -
struct A;
struct A
{
int a;
};
ffigen currently generates struct A
as Opaque
(without any fields). (This probably also affects enum declarations).
Note: This will only happen when parsing this declaration sequentially (i.e if the declaration was included).
We have another occurrence of this.
@dcharkes, @mannprerak2, I have a scenario here where
base.h
forward declaresCBS
as:// from base.h typedef struct cbs_st CBS;But in
bytestring.h
we have the fullcbs_st
declaration:// from bytestring.h struct cbs_st { const uint8_t *data; size_t len; };If
base.h
is read first by ffigen, then I get:class cbs_st extends ffi.Opaque {}But if
bytestring.h
is read first, then I get:class cbs_st extends ffi.Struct { external ffi.Pointer<ffi.Uint8> data; @ffi.Size() external int len; }(which is what I need in order to be able to allocate and instance of the struct)
All the other headers include
base.h
(or at-least many of them do). So my options here are:
- Put
bytestring.h
at the top of the list.- Create my own
amalgamation.h
which includes all the headers I need.I'm wondering if this is working as intended, or if it's a bug? I suspect this pattern is common in C-libraries.
I'm not sure if we can solve this without user input about the correct entry-points.
Since each entry-point is parsed separately. If an entry-point contains only the forward declaration and is parsed first, ffigen will only generate an empty definition of that struct.
However, this works -
struct A; // declaration
int sum(struct A a, int b);
struct A{ // definition
int a;
};
since libclang resolves the definition and declaration for us.
A possible solution would be to create a header file containing all other header files but if they don't have include-guards the parsing may fail unexpectedly.
Since each entry-point is parsed separately. If an entry-point contains only the forward declaration and is parsed first, ffigen will only generate an empty definition of that struct.
[...]
since libclang resolves the definition and declaration for us.
Could we retroactively resolve it ourselves instead of relying on libclang? Or do we have too little information? In other words could we know if its the same struct or don't we know anything and do we risk making 2 different structs one and the same? (That's why we have the automatic renaming in the first place.)
For example, if something is opaque, we can always upgrade it to have a definition. However, do we know for sure that that earlier occurrence is not opaque by design but an actual forward declaration?
A possible solution would be to create a header file containing all other header files but if they don't have include-guards the parsing may fail unexpectedly.
It probably depends on the code base if there are include guards. (The Dart codebase has them for example.) So, if we add it, it should be a config option.
I think if we can't solve it within our code base, this might be a nice solution. Just a one config line fix for projects that have include guards.