Skip to content

Commit afa9286

Browse files
committed
Add support for transparent enum
1 parent 42ae2fe commit afa9286

20 files changed

+1223
-263
lines changed

docs.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,12 @@ You can learn about all of the different repr attributes [by reading Rust's refe
133133

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

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

140+
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.
141+
140142
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.
141143

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

408410
### Enum Annotations
409411

410-
* 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.
412+
* enum-trailing-values=\[variant1, variant2, ...\] -- add the following fieldless enum variants to
413+
the end of the enum's definition. These variant names *will* have the enum's renaming rules
414+
applied.
415+
416+
WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does
417+
not know about them, and will assume they cannot happen.
411418

412-
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.
419+
* transparent-typedef -- when emitting the typedef for a transparent enum, mark it as
420+
transparent. All references to the enum will be replaced with the type of its underlying NZST
421+
variant field, effectively making the enum invisible on the FFI side. For exmaples of how this
422+
works, see [Struct Annotations](#struct-annotations).
413423

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

src/bindgen/ir/enumeration.rs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::bindgen::dependencies::Dependencies;
1212
use crate::bindgen::ir::{
1313
AnnotationSet, AnnotationValue, Cfg, ConditionWrite, DeprecatedNoteKind, Documentation, Field,
1414
GenericArgument, GenericParams, GenericPath, Item, ItemContainer, Literal, Path, Repr,
15-
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type,
15+
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type, Typedef,
1616
};
1717
use crate::bindgen::language_backend::LanguageBackend;
1818
use crate::bindgen::library::Library;
@@ -308,7 +308,12 @@ impl Enum {
308308

309309
/// Enum with data turns into a union of structs with each struct having its own tag field.
310310
pub(crate) fn inline_tag_field(repr: &Repr) -> bool {
311-
repr.style != ReprStyle::C
311+
// NOTE: repr(C) requires an out of line tag field, and repr(transparent) doesn't use tags.
312+
repr.style == ReprStyle::Rust
313+
}
314+
315+
pub fn is_transparent(&self) -> bool {
316+
self.repr.style == ReprStyle::Transparent
312317
}
313318

314319
pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
@@ -345,7 +350,7 @@ impl Enum {
345350
) -> Result<Enum, String> {
346351
let repr = Repr::load(&item.attrs)?;
347352
if repr.style == ReprStyle::Rust && repr.ty.is_none() {
348-
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned());
353+
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)] or #[repr(transparent)].".to_owned());
349354
}
350355
// TODO: Implement translation of aligned enums.
351356
if repr.align.is_some() {
@@ -418,13 +423,35 @@ impl Enum {
418423
pub fn new(
419424
path: Path,
420425
generic_params: GenericParams,
421-
repr: Repr,
426+
mut repr: Repr,
422427
variants: Vec<EnumVariant>,
423428
tag: Option<String>,
424429
cfg: Option<Cfg>,
425430
annotations: AnnotationSet,
426431
documentation: Documentation,
427432
) -> Self {
433+
// WARNING: A transparent enum with no fields (or whose fields are all 1-ZST) is legal rust
434+
// [1], but it is a zero-sized type and as such is "best avoided entirely" [2] because it
435+
// "will be nonsensical or problematic if passed through the FFI boundary" [1]. Further,
436+
// because no well-defined underlying native type exists for a 1-ZST, we cannot emit a
437+
// typedef and must fall back to repr(C) behavior that defines a tagged enum.
438+
//
439+
// [1] https://doc.rust-lang.org/nomicon/other-reprs.html
440+
// [2] https://github.com/rust-lang/rust/issues/77841#issuecomment-716796313
441+
if repr.style == ReprStyle::Transparent {
442+
let zero_sized = match variants.first() {
443+
Some(EnumVariant {
444+
body: VariantBody::Body { ref body, .. },
445+
..
446+
}) => body.fields.is_empty(),
447+
_ => true,
448+
};
449+
if zero_sized {
450+
warn!("Passing zero-sized transparent enum {} across the FFI boundary is undefined behavior", &path);
451+
repr.style = ReprStyle::C;
452+
}
453+
}
454+
428455
let export_name = path.name().to_owned();
429456
Self {
430457
path,
@@ -438,6 +465,28 @@ impl Enum {
438465
documentation,
439466
}
440467
}
468+
469+
/// Attempts to convert this enum to a typedef (only works for transparent enums).
470+
pub fn as_typedef(&self) -> Option<Typedef> {
471+
if self.is_transparent() {
472+
if let Some(EnumVariant {
473+
body: VariantBody::Body { ref body, .. },
474+
..
475+
}) = self.variants.first()
476+
{
477+
if let Some(field) = body.fields.first() {
478+
return Some(Typedef::new_from_item_field(self, field));
479+
}
480+
}
481+
}
482+
None
483+
}
484+
485+
// Transparent enums become typedefs, so try converting to typedef and recurse on that.
486+
pub fn as_transparent_alias(&self, generics: &[GenericArgument]) -> Option<Type> {
487+
self.as_typedef()
488+
.and_then(|t| t.as_transparent_alias(generics))
489+
}
441490
}
442491

443492
impl Item for Enum {
@@ -470,7 +519,9 @@ impl Item for Enum {
470519
}
471520

472521
fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
473-
if self.tag.is_some() {
522+
if self.repr.style == ReprStyle::Transparent {
523+
resolver.add_none(&self.path);
524+
} else if self.tag.is_some() {
474525
if self.repr.style == ReprStyle::C {
475526
resolver.add_struct(&self.path);
476527
} else {

src/bindgen/ir/structure.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,12 @@ impl Struct {
136136
// [2] https://github.com/rust-lang/rust/issues/77841#issuecomment-716796313
137137
// [3] https://doc.rust-lang.org/nomicon/other-reprs.html
138138
if fields.is_empty() {
139-
warn!(
140-
"Passing zero-sized struct {} across the FFI boundary is undefined behavior",
141-
&path
142-
);
139+
if !is_enum_variant_body {
140+
warn!(
141+
"Passing zero-sized struct {} across the FFI boundary is undefined behavior",
142+
&path
143+
);
144+
}
143145
is_transparent = false;
144146
}
145147

@@ -163,7 +165,7 @@ impl Struct {
163165
/// Attempts to convert this struct to a typedef (only works for transparent structs).
164166
pub fn as_typedef(&self) -> Option<Typedef> {
165167
match self.fields.first() {
166-
Some(field) if self.is_transparent => Some(Typedef::new_from_struct_field(self, field)),
168+
Some(field) if self.is_transparent => Some(Typedef::new_from_item_field(self, field)),
167169
_ => None,
168170
}
169171
}

src/bindgen/ir/ty.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ impl Type {
569569
ItemContainer::OpaqueItem(o) => o.as_transparent_alias(&generics),
570570
ItemContainer::Typedef(t) => t.as_transparent_alias(&generics),
571571
ItemContainer::Struct(s) => s.as_transparent_alias(&generics),
572+
ItemContainer::Enum(e) => e.as_transparent_alias(&generics),
572573
_ => None,
573574
};
574575
if let Some(mut ty) = aliased_ty {

src/bindgen/ir/typedef.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
1111
use crate::bindgen::dependencies::Dependencies;
1212
use crate::bindgen::ir::{
1313
AnnotationSet, Cfg, Documentation, Field, GenericArgument, GenericParams, Item, ItemContainer,
14-
Path, Struct, TransparentTypeEraser, Type,
14+
Path, TransparentTypeEraser, Type,
1515
};
1616
use crate::bindgen::library::Library;
1717
use crate::bindgen::mangle;
@@ -69,12 +69,12 @@ impl Typedef {
6969
}
7070
}
7171

72-
// Used to convert a transparent Struct to a Typedef.
73-
pub fn new_from_struct_field(item: &Struct, field: &Field) -> Self {
72+
// Used to convert a transparent Struct or Enum to a Typedef.
73+
pub fn new_from_item_field(item: &impl Item, field: &Field) -> Self {
7474
Self {
7575
path: item.path().clone(),
7676
export_name: item.export_name().to_string(),
77-
generic_params: item.generic_params.clone(),
77+
generic_params: item.generic_params().clone(),
7878
aliased: field.ty.clone(),
7979
cfg: item.cfg().cloned(),
8080
annotations: item.annotations().clone(),

src/bindgen/language_backend/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,27 @@ pub trait LanguageBackend: Sized {
157157
}
158158
}
159159

160+
/// If the enum is transparent, emit a typedef of its NZST field type instead.
161+
fn write_enum_or_typedef<W: Write>(
162+
&mut self,
163+
out: &mut SourceWriter<W>,
164+
e: &Enum,
165+
_b: &Bindings,
166+
) {
167+
if let Some(typedef) = e.as_typedef() {
168+
self.write_type_def(out, &typedef);
169+
// TODO: Associated constants are not supported for enums. Should they be? Rust
170+
// enum exports as a union, and C/C++ at least supports static union members?
171+
//
172+
//for constant in &e.associated_constants {
173+
// out.new_line();
174+
// constant.write(b.config, self, out, Some(e));
175+
//}
176+
} else {
177+
self.write_enum(out, e);
178+
}
179+
}
180+
160181
fn write_items<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
161182
for item in &b.items {
162183
if item
@@ -172,7 +193,7 @@ pub trait LanguageBackend: Sized {
172193
match *item {
173194
ItemContainer::Constant(..) => unreachable!(),
174195
ItemContainer::Static(..) => unreachable!(),
175-
ItemContainer::Enum(ref x) => self.write_enum(out, x),
196+
ItemContainer::Enum(ref x) => self.write_enum_or_typedef(out, x, b),
176197
ItemContainer::Struct(ref x) => self.write_struct_or_typedef(out, x, b),
177198
ItemContainer::Union(ref x) => self.write_union(out, x),
178199
ItemContainer::OpaqueItem(ref x) => self.write_opaque_item(out, x),

tests/expectations/const_transparent.compat.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@
33
#include <stdint.h>
44
#include <stdlib.h>
55

6+
#define TransparentEnum_ASSOC_ENUM_FOO 8
7+
68
typedef uint8_t TransparentStruct;
79
#define TransparentStruct_ASSOC_STRUCT_FOO 1
810
#define TransparentStruct_ASSOC_STRUCT_BAR 2
911

1012

1113
typedef uint8_t TransparentTupleStruct;
1214

15+
typedef uint8_t TransparentEnum;
16+
1317
#define STRUCT_FOO 4
1418

1519
#define STRUCT_BAR 5
1620

1721

1822

1923

24+
25+
26+
27+
28+
29+
30+
31+

tests/expectations/const_transparent.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <ostream>
55
#include <new>
66

7+
constexpr static const int64_t TransparentEnum_ASSOC_ENUM_FOO = 8;
8+
79
template<typename T>
810
using Wrapper = T;
911

@@ -17,10 +19,23 @@ using TransparentTupleStruct = uint8_t;
1719
template<typename T>
1820
using TransparentStructWithErasedField = T;
1921

22+
using TransparentEnum = uint8_t;
23+
24+
template<typename T>
25+
using TransparentWrapperEnum = T;
26+
2027
constexpr static const TransparentStruct STRUCT_FOO = 4;
2128

2229
constexpr static const TransparentTupleStruct STRUCT_BAR = 5;
2330

2431
constexpr static const TransparentStruct STRUCT_BAZ = 6;
2532

2633
constexpr static const TransparentStructWithErasedField<TransparentStruct> COMPLEX = 7;
34+
35+
36+
37+
38+
39+
40+
41+

tests/expectations/const_transparent.pyx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,29 @@ cdef extern from *:
66

77
cdef extern from *:
88

9+
const int64_t TransparentEnum_ASSOC_ENUM_FOO # = 8
10+
911
ctypedef uint8_t TransparentStruct;
1012
const int64_t TransparentStruct_ASSOC_STRUCT_FOO # = 1
1113
const TransparentStruct TransparentStruct_ASSOC_STRUCT_BAR # = 2
1214

1315

1416
ctypedef uint8_t TransparentTupleStruct;
1517

18+
ctypedef uint8_t TransparentEnum;
19+
1620
const TransparentStruct STRUCT_FOO # = 4
1721

1822
const TransparentTupleStruct STRUCT_BAR # = 5
1923

2024

2125

2226

27+
28+
29+
30+
31+
32+
33+
34+

0 commit comments

Comments
 (0)