Skip to content

Commit

Permalink
Add support for transparent enum
Browse files Browse the repository at this point in the history
  • Loading branch information
scovich committed Aug 12, 2024
1 parent 8a76012 commit 8c76d22
Show file tree
Hide file tree
Showing 20 changed files with 992 additions and 233 deletions.
16 changes: 13 additions & 3 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ You can learn about all of the different repr attributes [by reading Rust's refe

* `#[repr(C)]`: give this struct/union/enum the same layout and ABI C would
* `#[repr(u8, u16, ... etc)]`: give this enum the same layout and ABI as the given integer type
* `#[repr(transparent)]`: give this single-field struct the same ABI as its field (useful for newtyping integers but keeping the integer ABI)
* `#[repr(transparent)]`: give this single-field struct or enum the same ABI as its field (useful for newtyping integers but keeping the integer ABI)

cbindgen supports the `#[repr(align(N))]` and `#[repr(packed)]` attributes, but currently does not support `#[repr(packed(N))]`.

cbindgen supports using `repr(transparent)` on single-field structs and single-variant enums with fields. Transparent structs and enums are exported as typedefs that alias the underlying single field's type.

cbindgen also supports using `repr(C)`/`repr(u8)` on non-C-like enums (enums with fields). This gives a C-compatible tagged union layout, as [defined by this RFC 2195][really-tagged-unions]. `repr(C)` will give a simpler layout that is perhaps more intuitive, while `repr(u8)` will produce a more compact layout.

If you ensure everything has a guaranteed repr, then cbindgen will generate definitions for:
Expand Down Expand Up @@ -407,9 +409,17 @@ The rest are just local overrides for the same options found in the cbindgen.tom

### Enum Annotations

* enum-trailing-values=\[variant1, variant2, ...\] -- add the following fieldless enum variants to the end of the enum's definition. These variant names *will* have the enum's renaming rules applied.
* enum-trailing-values=\[variant1, variant2, ...\] -- add the following fieldless enum variants to
the end of the enum's definition. These variant names *will* have the enum's renaming rules
applied.

WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does
not know about them, and will assume they cannot happen.

WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does not know about them, and will assume they cannot happen.
* transparent-typedef -- when emitting the typedef for a transparent enum, mark it as
transparent. All references to the enum will be replaced with the type of its underlying NZST
variant field, effectively making the enum invisible on the FFI side. For exmaples of how this
works, see [Struct Annotations](#struct-annotations).

The rest are just local overrides for the same options found in the cbindgen.toml:

Expand Down
35 changes: 33 additions & 2 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, AnnotationValue, Cfg, ConditionWrite, DeprecatedNoteKind, Documentation, Field,
GenericArgument, GenericParams, GenericPath, Item, ItemContainer, Literal, Path, Repr,
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type,
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type, Typedef,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::library::Library;
Expand Down Expand Up @@ -311,6 +311,10 @@ impl Enum {
repr.style != ReprStyle::C
}

pub fn is_transparent(&self) -> bool {
self.repr.style == ReprStyle::Transparent
}

pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
if self.is_generic() {
return;
Expand Down Expand Up @@ -438,6 +442,31 @@ impl Enum {
documentation,
}
}

/// Attempts to convert this enum to a typedef (only works for transparent enums).
pub fn as_typedef(&self) -> Option<Typedef> {
if self.is_transparent() {
// NOTE: A `#[repr(transparent)]` enum fails to compile unless it has
// exactly one NZT variant. So we can safely assume the variant exists.
if let Some(EnumVariant {
body: VariantBody::Body { ref body, .. },
..
}) = self.variants.first()
{
// NOTE: Inline tagged enum has the tag field first, ignore it.
if let Some(field) = body.fields.last() {
return Some(Typedef::new_from_item_field(self, field));
}
}
}
None
}

// Transparent enums become typedefs, so try converting to typedef and recurse on that.
pub fn as_transparent_alias(&self, generics: &[GenericArgument]) -> Option<Type> {
self.as_typedef()
.and_then(|t| t.as_transparent_alias(generics))
}
}

impl Item for Enum {
Expand Down Expand Up @@ -470,7 +499,9 @@ impl Item for Enum {
}

fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
if self.tag.is_some() {
if self.repr.style == ReprStyle::Transparent {
resolver.add_none(&self.path);
} else if self.tag.is_some() {
if self.repr.style == ReprStyle::C {
resolver.add_struct(&self.path);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/bindgen/ir/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl Struct {
// NOTE: A `#[repr(transparent)]` struct with 2+ NZT fields fails to compile, but 0
// fields is allowed for some strange reason. Don't emit the typedef in that case.
if let Some(field) = self.fields.first() {
return Some(Typedef::new_from_struct_field(self, field));
return Some(Typedef::new_from_item_field(self, field));
} else {
error!(
"Cannot convert empty transparent struct {} to typedef",
Expand Down
1 change: 1 addition & 0 deletions src/bindgen/ir/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ impl Type {
ItemContainer::OpaqueItem(o) => o.as_transparent_alias(&generics),
ItemContainer::Typedef(t) => t.as_transparent_alias(&generics),
ItemContainer::Struct(s) => s.as_transparent_alias(&generics),
ItemContainer::Enum(e) => e.as_transparent_alias(&generics),
_ => None,
};
if let Some(mut ty) = aliased_ty {
Expand Down
8 changes: 4 additions & 4 deletions src/bindgen/ir/typedef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, Cfg, Documentation, Field, GenericArgument, GenericParams, Item, ItemContainer,
Path, Struct, TransparentTypeEraser, Type,
Path, TransparentTypeEraser, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::mangle;
Expand Down Expand Up @@ -69,12 +69,12 @@ impl Typedef {
}
}

// Used to convert a transparent Struct to a Typedef.
pub fn new_from_struct_field(item: &Struct, field: &Field) -> Self {
// Used to convert a transparent Struct or Enum to a Typedef.
pub fn new_from_item_field(item: &impl Item, field: &Field) -> Self {
Self {
path: item.path().clone(),
export_name: item.export_name().to_string(),
generic_params: item.generic_params.clone(),
generic_params: item.generic_params().clone(),
aliased: field.ty.clone(),
cfg: item.cfg().cloned(),
annotations: item.annotations().clone(),
Expand Down
23 changes: 22 additions & 1 deletion src/bindgen/language_backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ pub trait LanguageBackend: Sized {
}
}

/// If the enum is transparent, emit a typedef of its NZST field type instead.
fn write_enum_or_typedef<W: Write>(
&mut self,
out: &mut SourceWriter<W>,
e: &Enum,
_b: &Bindings,
) {
if let Some(typedef) = e.as_typedef() {
self.write_type_def(out, &typedef);
// TODO: Associated constants are not supported for enums. Should they be? Rust
// enum exports as a union, and C/C++ at least supports static union members?
//
//for constant in &e.associated_constants {
// out.new_line();
// constant.write(b.config, self, out, Some(e));
//}
} else {
self.write_enum(out, e);
}
}

fn write_items<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
for item in &b.items {
if item
Expand All @@ -172,7 +193,7 @@ pub trait LanguageBackend: Sized {
match *item {
ItemContainer::Constant(..) => unreachable!(),
ItemContainer::Static(..) => unreachable!(),
ItemContainer::Enum(ref x) => self.write_enum(out, x),
ItemContainer::Enum(ref x) => self.write_enum_or_typedef(out, x, b),
ItemContainer::Struct(ref x) => self.write_struct_or_typedef(out, x, b),
ItemContainer::Union(ref x) => self.write_union(out, x),
ItemContainer::OpaqueItem(ref x) => self.write_opaque_item(out, x),
Expand Down
12 changes: 12 additions & 0 deletions tests/expectations/const_transparent.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@
#include <stdint.h>
#include <stdlib.h>

#define TransparentEnum_ASSOC_ENUM_FOO 8

typedef uint8_t TransparentStruct;
#define TransparentStruct_ASSOC_STRUCT_FOO 1
#define TransparentStruct_ASSOC_STRUCT_BAR 2


typedef uint8_t TransparentTupleStruct;

typedef uint8_t TransparentEnum;

#define STRUCT_FOO 4

#define STRUCT_BAR 5












15 changes: 15 additions & 0 deletions tests/expectations/const_transparent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <ostream>
#include <new>

constexpr static const int64_t TransparentEnum_ASSOC_ENUM_FOO = 8;

template<typename T>
using Wrapper = T;

Expand All @@ -17,10 +19,23 @@ using TransparentTupleStruct = uint8_t;
template<typename T>
using TransparentStructWithErasedField = T;

using TransparentEnum = uint8_t;

template<typename T>
using TransparentWrapperEnum = T;

constexpr static const TransparentStruct STRUCT_FOO = 4;

constexpr static const TransparentTupleStruct STRUCT_BAR = 5;

constexpr static const TransparentStruct STRUCT_BAZ = 6;

constexpr static const TransparentStructWithErasedField<TransparentStruct> COMPLEX = 7;








12 changes: 12 additions & 0 deletions tests/expectations/const_transparent.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,29 @@ cdef extern from *:

cdef extern from *:

const int64_t TransparentEnum_ASSOC_ENUM_FOO # = 8

ctypedef uint8_t TransparentStruct;
const int64_t TransparentStruct_ASSOC_STRUCT_FOO # = 1
const TransparentStruct TransparentStruct_ASSOC_STRUCT_BAR # = 2


ctypedef uint8_t TransparentTupleStruct;

ctypedef uint8_t TransparentEnum;

const TransparentStruct STRUCT_FOO # = 4

const TransparentTupleStruct STRUCT_BAR # = 5












94 changes: 74 additions & 20 deletions tests/expectations/transparent.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,77 @@ typedef DummyStruct TransparentComplexWrappingStructTuple;

typedef uint32_t TransparentPrimitiveWrappingStructTuple;

typedef DummyStruct TransparentComplexWrappingStructure;
typedef DummyStruct TransparentComplexWrappingStruct;

typedef uint32_t TransparentPrimitiveWrappingStructure;
typedef uint32_t TransparentPrimitiveWrappingStruct;

typedef DummyStruct TransparentComplexWrapper_i32;
typedef DummyStruct TransparentComplexWrapperStruct_i32;

typedef uint32_t TransparentPrimitiveWrapper_i32;
typedef uint32_t TransparentPrimitiveWrapperStruct_i32;

typedef uint32_t TransparentPrimitiveWithAssociatedConstants;
#define TransparentPrimitiveWithAssociatedConstants_ZERO 0
#define TransparentPrimitiveWithAssociatedConstants_ONE 1
typedef uint32_t TransparentPrimitiveStructWithAssociatedConstants;
#define TransparentPrimitiveStructWithAssociatedConstants_STRUCT_ZERO 0
#define TransparentPrimitiveStructWithAssociatedConstants_STRUCT_ONE 1

typedef const uint32_t *TransparentPointerWrappingStructure;
typedef const uint32_t *TransparentPointerWrappingStruct;

typedef int32_t TransparentIntStruct;

typedef DummyStruct TransparentComplexStruct;

typedef TransparentPrimitiveWrappingStructure TransparentTransparentStruct;
typedef TransparentPrimitiveWrappingStruct TransparentTransparentStruct;

typedef uint32_t *TransparentNonNullStruct;

typedef uint32_t *TransparentOptionNonNullStruct;

typedef DummyStruct TransparentComplexWrappingEnumTuple;

typedef uint32_t TransparentPrimitiveWrappingEnumTuple;

typedef DummyStruct TransparentComplexWrappingEnum;

typedef uint32_t TransparentPrimitiveWrappingEnum;

typedef DummyStruct TransparentComplexWrapperEnum_i32;

typedef uint32_t TransparentPrimitiveWrapperEnum_i32;

typedef uint32_t TransparentPrimitiveEnumWithAssociatedConstants;

typedef const uint32_t *TransparentPointerWrappingEnum;

typedef int32_t TransparentIntEnum;

typedef DummyStruct TransparentComplexEnum;

typedef TransparentPrimitiveWrappingEnum TransparentTransparentEnum;

typedef uint32_t *TransparentNonNullEnum;

typedef uint32_t *TransparentOptionNonNullEnum;

#define StructWithAssociatedConstantInImpl_STRUCT_TEN 10





#define EnumWithAssociatedConstantInImpl_ENUM_TEN 10

void root(TransparentComplexWrappingStructTuple a,
TransparentPrimitiveWrappingStructTuple b,
TransparentComplexWrappingStructure c,
TransparentPrimitiveWrappingStructure d,
TransparentComplexWrapper_i32 e,
TransparentPrimitiveWrapper_i32 f,
TransparentPrimitiveWithAssociatedConstants g,
TransparentPointerWrappingStructure h,
StructWithAssociatedConstantInImpl i,
EnumWithAssociatedConstantInImpl j);
void struct_root(TransparentComplexWrappingStructTuple a,
TransparentPrimitiveWrappingStructTuple b,
TransparentComplexWrappingStruct c,
TransparentPrimitiveWrappingStruct d,
TransparentComplexWrapperStruct_i32 e,
TransparentPrimitiveWrapperStruct_i32 f,
TransparentPrimitiveStructWithAssociatedConstants g,
TransparentPointerWrappingStruct h,
StructWithAssociatedConstantInImpl i);

void erased_root(uint32_t *a,
uint32_t *b,
TransparentPrimitiveWrappingStructure c,
TransparentPrimitiveWrappingStruct c,
uint32_t *d,
TransparentIntStruct e,
int32_t f,
Expand All @@ -66,3 +95,28 @@ void erased_root(uint32_t *a,
TransparentTransparentStruct l,
TransparentNonNullStruct m,
TransparentOptionNonNullStruct n);

void enum_root(TransparentComplexWrappingEnumTuple a,
TransparentPrimitiveWrappingEnumTuple b,
TransparentComplexWrappingEnum c,
TransparentPrimitiveWrappingEnum d,
TransparentComplexWrapperEnum_i32 e,
TransparentPrimitiveWrapperEnum_i32 f,
TransparentPrimitiveEnumWithAssociatedConstants g,
TransparentPointerWrappingEnum h,
EnumWithAssociatedConstantInImpl i);

void erased_enum_root(uint32_t *a,
uint32_t *b,
TransparentPrimitiveWrappingEnum c,
uint32_t *d,
TransparentIntEnum e,
int32_t f,
DummyStruct g,
uint32_t *h,
int32_t i,
TransparentIntEnum j,
TransparentComplexEnum k,
TransparentTransparentEnum l,
TransparentNonNullEnum m,
TransparentOptionNonNullEnum n);
Loading

0 comments on commit 8c76d22

Please sign in to comment.