Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,15 +680,20 @@ impl Enum {
}
write!(out, " {tag_name}");

if config.cpp_compatible_c() {
out.new_line();
out.write("#ifdef __cplusplus");
out.new_line();
write!(out, " : {prim}");
out.new_line();
out.write("#endif // __cplusplus");
out.new_line();
}
// Emit typed enum syntax (valid in C23 or later or C++)
let cond = if config.cpp_compatible_c() {
"defined(__cplusplus) || __STDC_VERSION__ >= 202311L"
} else {
"__STDC_VERSION__ >= 202311L"
};

out.new_line();
write!(out, "#if {cond}");
out.new_line();
write!(out, " : {prim}");
out.new_line();
write!(out, "#endif // {cond}");
out.new_line();
Comment on lines +691 to +696
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unfortunately, unconditionally outputting this causes issues with some tools that parse header files.

In particular, Python's cffi has very limited support for preprocessor macros and does not support #if conditionals at all. For some conditionals this is trivial to work around (such as header guards -- you can remove them before getting cffi to parse them) but conditionals like this do not have a nice workaround.

If you try to use them, you get this error:

>>> import cffi
>>> ffi = cffi.FFI()
>>> ffi.cdef("#if FOO >= 100\n#endif\n")
cffi.CDefError: cannot parse "#if FOO >= 100"
<cdef source string>:1:1: Directives not supported yet

cyphar/libpathrs#382 is an example of how this can cause build failures in a downstream project -- our build scripts strip out any non-#define macros (which is the only thing cffi supports, and even then it's quite limited) so the error you actually get looks more like:

cffi.CDefError: cannot parse ": uint64_t"
<cdef source string>:22:3: before: :

But the core issue is the same. It would be nice if this output could be made configurable so that we can continue to use cbindgen-generated headers with cffi. Otherwise we will have to pin an older version...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would be ok with reviewing a patch to make this opt-in / opt-out.

Though, have you considered unifdef perhaps? I think that'd work?

Copy link
Copy Markdown
Contributor

@cyphar cyphar May 30, 2026

Choose a reason for hiding this comment

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

That's not a bad idea (and I must admit I hadn't considered it) -- unfortunately shelling out to unifdef from the setup script is a little dodgy, it might not work for some packaging setups (distros would probably be okay with the workaround but I think it would be problematic for Python packaging and people doing local from-source installs).

It would be easier to implement that in Python, at which point it might actually end up being less effort to just add support for basic #if/#ifdef conditionals in cffi...

I would be ok with reviewing a patch to make this opt-in / opt-out.

What would you think about a patch which let you specify a C version to generate (to not make this switch too special-purpose) and so if you pick C99 mode (for instance) you don't get these #if conditionals but if you pick C23 mode you do? (To be fair, reading it back this is a little funky -- if you are picking C23 mode you wouldn't need the conditional. Maybe a "max" and "min" C version? 🤷.)

Or would you prefer something more like an option for "limited C" and "full-on C"? (That would make things easier for me in the future, I must admit! 😅)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think something like allow_fixed_size_c_enums or something, defaulting to true and avoiding the ifdefs would be a bit more consistent with how we deal with other features like constexpr in c++.

It'd be a breaking change but it'd remove the ifdefs altogether from the generated code? I guess the ifdef route was taken here mostly because we already had a __cplusplus if def for C++-compatible-C but...

} else {
if config.style.generate_typedef() {
out.write("typedef ");
Expand Down Expand Up @@ -759,17 +764,39 @@ impl Enum {
}

// Emit typedef specifying the tag enum's size if necessary.
// In C++ enums can "inherit" from numeric types (`enum E: uint8_t { ... }`),
// but in C `typedef uint8_t E` is the only way to give a fixed size to `E`.
// In C++ or C23 enums can "inherit" from numeric types (`enum E: uint8_t { ... }`),
// but in older versions of C `typedef uint8_t E` is the only way to give a fixed size to `E`.
if let Some(prim) = size {
if config.cpp_compatible_c() {
out.new_line_if_not_start();
out.write("#ifndef __cplusplus");
}

if config.language != Language::Cxx {
if config.language == Language::C {
out.new_line();
out.write("#if __STDC_VERSION__ >= 202311L");

out.new_line();
write!(
out,
"{} enum {} {};",
config.language.typedef(),
tag_name,
tag_name
);

out.new_line();
out.write("#else");
}

out.new_line();
write!(out, "{} {} {};", config.language.typedef(), prim, tag_name);

if config.language == Language::C {
out.new_line();
out.write("#endif // __STDC_VERSION__ >= 202311L");
}
Comment on lines +776 to +799
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And this change has a very similar issue -- I believe this will cause multiple-defines errors if our build script strips out the #if directives.

}

if config.cpp_compatible_c() {
Expand Down
10 changes: 9 additions & 1 deletion tests/expectations/alias.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
#include <stdint.h>
#include <stdlib.h>

enum Status {
enum Status
#if __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L

typedef struct {
int32_t a;
Expand Down
8 changes: 6 additions & 2 deletions tests/expectations/alias.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
#include <stdlib.h>

enum Status
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

typedef struct {
Expand Down
10 changes: 9 additions & 1 deletion tests/expectations/alias_both.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
#include <stdint.h>
#include <stdlib.h>

enum Status {
enum Status
#if __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L

typedef struct Dep {
int32_t a;
Expand Down
8 changes: 6 additions & 2 deletions tests/expectations/alias_both.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
#include <stdlib.h>

enum Status
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

typedef struct Dep {
Expand Down
10 changes: 9 additions & 1 deletion tests/expectations/alias_tag.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
#include <stdint.h>
#include <stdlib.h>

enum Status {
enum Status
#if __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L

struct Dep {
int32_t a;
Expand Down
8 changes: 6 additions & 2 deletions tests/expectations/alias_tag.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
#include <stdlib.h>

enum Status
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
Ok,
Err,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum Status Status;
#else
typedef uint32_t Status;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

struct Dep {
Expand Down
30 changes: 27 additions & 3 deletions tests/expectations/annotation.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
#include <stdint.h>
#include <stdlib.h>

enum C {
enum C
#if __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __STDC_VERSION__ >= 202311L
{
X = 2,
Y,
};
#if __STDC_VERSION__ >= 202311L
typedef enum C C;
#else
typedef uint32_t C;
#endif // __STDC_VERSION__ >= 202311L

typedef struct {
int32_t m0;
Expand All @@ -18,12 +26,20 @@ typedef struct {
float y;
} B;

enum F_Tag {
enum F_Tag
#if __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __STDC_VERSION__ >= 202311L
{
Foo,
Bar,
Baz,
};
#if __STDC_VERSION__ >= 202311L
typedef enum F_Tag F_Tag;
#else
typedef uint8_t F_Tag;
#endif // __STDC_VERSION__ >= 202311L

typedef struct {
F_Tag tag;
Expand All @@ -40,12 +56,20 @@ typedef union {
Bar_Body bar;
} F;

enum H_Tag {
enum H_Tag
#if __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __STDC_VERSION__ >= 202311L
{
Hello,
There,
Everyone,
};
#if __STDC_VERSION__ >= 202311L
typedef enum H_Tag H_Tag;
#else
typedef uint8_t H_Tag;
#endif // __STDC_VERSION__ >= 202311L

typedef struct {
uint8_t x;
Expand Down
24 changes: 18 additions & 6 deletions tests/expectations/annotation.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
#include <stdlib.h>

enum C
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
X = 2,
Y,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum C C;
#else
typedef uint32_t C;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

typedef struct {
Expand All @@ -25,16 +29,20 @@ typedef struct {
} B;

enum F_Tag
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
Foo,
Bar,
Baz,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum F_Tag F_Tag;
#else
typedef uint8_t F_Tag;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

typedef struct {
Expand All @@ -53,16 +61,20 @@ typedef union {
} F;

enum H_Tag
#ifdef __cplusplus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __cplusplus
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
Hello,
There,
Everyone,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum H_Tag H_Tag;
#else
typedef uint8_t H_Tag;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus

typedef struct {
Expand Down
30 changes: 27 additions & 3 deletions tests/expectations/annotation_both.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
#include <stdint.h>
#include <stdlib.h>

enum C {
enum C
#if __STDC_VERSION__ >= 202311L
: uint32_t
#endif // __STDC_VERSION__ >= 202311L
{
X = 2,
Y,
};
#if __STDC_VERSION__ >= 202311L
typedef enum C C;
#else
typedef uint32_t C;
#endif // __STDC_VERSION__ >= 202311L

typedef struct A {
int32_t m0;
Expand All @@ -18,12 +26,20 @@ typedef struct B {
float y;
} B;

enum F_Tag {
enum F_Tag
#if __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __STDC_VERSION__ >= 202311L
{
Foo,
Bar,
Baz,
};
#if __STDC_VERSION__ >= 202311L
typedef enum F_Tag F_Tag;
#else
typedef uint8_t F_Tag;
#endif // __STDC_VERSION__ >= 202311L

typedef struct Bar_Body {
F_Tag tag;
Expand All @@ -40,12 +56,20 @@ typedef union F {
Bar_Body bar;
} F;

enum H_Tag {
enum H_Tag
#if __STDC_VERSION__ >= 202311L
: uint8_t
#endif // __STDC_VERSION__ >= 202311L
{
Hello,
There,
Everyone,
};
#if __STDC_VERSION__ >= 202311L
typedef enum H_Tag H_Tag;
#else
typedef uint8_t H_Tag;
#endif // __STDC_VERSION__ >= 202311L

typedef struct There_Body {
uint8_t x;
Expand Down
Loading
Loading