Skip to content

Commit 7ce3ac0

Browse files
committed
implement transparent typedef feature
1 parent 1c2da73 commit 7ce3ac0

37 files changed

+1818
-213
lines changed

docs.md

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,76 @@ fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();`
282282

283283
### Struct Annotations
284284

285-
* field-names=\[field1, field2, ...\] -- sets the names of all the fields in the output struct. These names will be output verbatim, and are not eligible for renaming.
285+
* field-names=\[field1, field2, ...\] -- sets the names of all the fields in the output
286+
struct. These names will be output verbatim, and are not eligible for renaming.
287+
288+
* transparent-typedef -- when emitting the typedef for a transparent struct, mark it as
289+
transparent. All references to the struct will be replaced with the type of its underlying NZST
290+
field, effectively making the struct invisible on the FFI side. For example, consider the
291+
following Rust code:
292+
293+
```rust
294+
#[repr(transparent)]
295+
pub struct Handle<T> {
296+
ptr: NonNull<T>,
297+
}
298+
299+
pub struct Foo { }
300+
301+
#[no_mangle]
302+
pub extern "C" fn foo_operation(foo: Option<Handle<Foo>>) { }
303+
```
304+
305+
By default, the exported C++ code would fail to compile, because the function takes `Option<...>`
306+
(which is an opaque type) by value:
307+
308+
```cpp
309+
template<typename T>
310+
struct Option<T>;
311+
312+
template<typename T>
313+
using Handle = T;
314+
315+
struct Foo;
316+
317+
void foo_operation(Option<Handle<Foo>> foo);
318+
```
319+
320+
If we annotate `Handle` with `transparent-typedef` (leaving the rest of the code unchanged):
321+
```rust
322+
/// cbindgen:transparent-typedef
323+
#[repr(transparent)]
324+
pub struct Handle<T> {
325+
ptr: NonNull<T>,
326+
}
327+
```
328+
329+
Then cbindgen is able to simplify the exported C++ code to just:
330+
```cpp
331+
struct Foo;
332+
333+
void foo_operation(Foo* foo);
334+
```
335+
336+
NOTE: This annotation does _NOT_ affect user-defined type aliases for transparent structs. If we
337+
we adjust the previous example to use a type alias:
338+
339+
```rust
340+
type NullableFooHandle = Option<Handle<Foo>>;
341+
342+
#[no_mangle]
343+
pub extern "C" fn foo_operation(foo: NullableFooHandle) { }
344+
```
345+
346+
Then the exported code will use it as expected:
347+
348+
```cpp
349+
struct Foo;
350+
351+
using NullableFooHandle = Foo*;
352+
353+
void foo_operation(NullableFooHandle foo);
354+
```
286355
287356
The rest are just local overrides for the same options found in the cbindgen.toml:
288357
@@ -299,27 +368,25 @@ The rest are just local overrides for the same options found in the cbindgen.tom
299368
/ etc(if any). The idea is for this to be used to annotate the operator with
300369
attributes, for example:
301370
302-
```rust
303-
/// cbindgen:eq-attributes=MY_ATTRIBUTES
304-
#[repr(C)]
305-
pub struct Foo { .. }
306-
```
307-
308-
Will generate something like:
371+
```rust
372+
/// cbindgen:eq-attributes=MY_ATTRIBUTES
373+
#[repr(C)]
374+
pub struct Foo { .. }
375+
```
309376

310-
```
311-
MY_ATTRIBUTES bool operator==(const Foo& other) const {
312-
...
313-
}
314-
```
377+
Will generate something like:
315378

316-
Combined with something like:
379+
```
380+
MY_ATTRIBUTES bool operator==(const Foo& other) const {
381+
...
382+
}
383+
```
317384

318-
```
319-
#define MY_ATTRIBUTES [[nodiscard]]
320-
```
385+
Combined with something like:
321386

322-
for example.
387+
```
388+
#define MY_ATTRIBUTES [[nodiscard]]
389+
```
323390

324391
### Enum Annotations
325392

src/bindgen/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ impl Builder {
368368
let mut result = Parse::new();
369369

370370
if self.std_types {
371-
result.add_std_types();
371+
result.add_std_types(self.config.language);
372372
}
373373

374374
for x in &self.srcs {

src/bindgen/ir/constant.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use crate::bindgen::config::{Config, Language};
1313
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
1414
use crate::bindgen::dependencies::Dependencies;
1515
use crate::bindgen::ir::{
16-
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path,
17-
Struct, ToCondition, Type,
16+
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericArgument, GenericParams, Item,
17+
ItemContainer, Path, Struct, ToCondition, TransparentTypeEraser, Type,
1818
};
1919
use crate::bindgen::language_backend::LanguageBackend;
2020
use crate::bindgen::library::Library;
@@ -597,6 +597,18 @@ impl Item for Constant {
597597
fn generic_params(&self) -> Option<&GenericParams> {
598598
None
599599
}
600+
601+
fn erase_transparent_types_inplace(
602+
&mut self,
603+
library: &Library,
604+
eraser: &mut TransparentTypeEraser,
605+
_generics: &[GenericArgument],
606+
) {
607+
// NOTE: We also need to simplify the literal initializer value to match the underlying
608+
// type, but that is true for all transparent structs (not just transparent-typedef
609+
// structs), and is handled by the `write` method below.
610+
eraser.erase_transparent_types_inplace(library, &mut self.ty, &[]);
611+
}
600612
}
601613

602614
impl Constant {

src/bindgen/ir/enumeration.rs

Lines changed: 25 additions & 13 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, Type,
15+
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type,
1616
};
1717
use crate::bindgen::language_backend::LanguageBackend;
1818
use crate::bindgen::library::Library;
@@ -247,12 +247,6 @@ impl EnumVariant {
247247
}
248248
}
249249

250-
fn simplify_standard_types(&mut self, config: &Config) {
251-
if let VariantBody::Body { ref mut body, .. } = self.body {
252-
body.simplify_standard_types(config);
253-
}
254-
}
255-
256250
fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
257251
if let VariantBody::Body { ref body, .. } = self.body {
258252
body.add_dependencies(library, out);
@@ -500,6 +494,30 @@ impl Item for Enum {
500494
Some(&self.generic_params)
501495
}
502496

497+
fn erase_transparent_types_inplace(
498+
&mut self,
499+
library: &Library,
500+
eraser: &mut TransparentTypeEraser,
501+
generics: &[GenericArgument],
502+
) {
503+
let mut skip_inline_tag_field = Self::inline_tag_field(&self.repr);
504+
let generics = self.generic_params.defaulted_generics(generics);
505+
let mappings = self.generic_params.call(self.name(), &generics);
506+
for variant in self.variants.iter_mut() {
507+
if let VariantBody::Body { ref mut body, .. } = variant.body {
508+
for field in body.fields.iter_mut() {
509+
// Ignore the inline Tag field, if any (it's always first)
510+
if skip_inline_tag_field {
511+
debug!("Skipping inline Tag field {:?}", field);
512+
skip_inline_tag_field = false;
513+
} else {
514+
eraser.erase_transparent_types_inplace(library, &mut field.ty, &mappings);
515+
}
516+
}
517+
}
518+
}
519+
}
520+
503521
fn rename_for_config(&mut self, config: &Config) {
504522
config.export.rename(&mut self.export_name);
505523

@@ -1493,10 +1511,4 @@ impl Enum {
14931511
}
14941512
}
14951513
}
1496-
1497-
pub fn simplify_standard_types(&mut self, config: &Config) {
1498-
for variant in &mut self.variants {
1499-
variant.simplify_standard_types(config);
1500-
}
1501-
}
15021514
}

src/bindgen/ir/function.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use syn::ext::IdentExt;
99
use crate::bindgen::config::{Config, Language};
1010
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
1111
use crate::bindgen::dependencies::Dependencies;
12-
use crate::bindgen::ir::{AnnotationSet, Cfg, Documentation, GenericPath, Path, Type};
12+
use crate::bindgen::ir::{
13+
AnnotationSet, Cfg, Documentation, GenericPath, Path, TransparentTypeEraser, Type,
14+
};
1315
use crate::bindgen::library::Library;
1416
use crate::bindgen::monomorph::Monomorphs;
1517
use crate::bindgen::rename::{IdentifierType, RenameRule};
@@ -108,13 +110,6 @@ impl Function {
108110
&self.path
109111
}
110112

111-
pub fn simplify_standard_types(&mut self, config: &Config) {
112-
self.ret.simplify_standard_types(config);
113-
for arg in &mut self.args {
114-
arg.ty.simplify_standard_types(config);
115-
}
116-
}
117-
118113
pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
119114
self.ret.add_dependencies(library, out);
120115
for arg in &self.args {
@@ -143,6 +138,18 @@ impl Function {
143138
}
144139
}
145140

141+
// NOTE: No `generics` arg because Functions do not support generics and do not `impl Item`.
142+
pub fn erase_transparent_types_inplace(
143+
&mut self,
144+
library: &Library,
145+
eraser: &mut TransparentTypeEraser,
146+
) {
147+
eraser.erase_transparent_types_inplace(library, &mut self.ret, &[]);
148+
for arg in &mut self.args {
149+
eraser.erase_transparent_types_inplace(library, &mut arg.ty, &[]);
150+
}
151+
}
152+
146153
pub fn rename_for_config(&mut self, config: &Config) {
147154
// Rename the types used in arguments
148155
let generic_params = Default::default();

src/bindgen/ir/generic_path.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::borrow::Cow;
2+
use std::collections::HashMap;
13
use std::io::Write;
24
use std::ops::Deref;
35

@@ -8,16 +10,17 @@ use crate::bindgen::config::{Config, Language};
810
use crate::bindgen::declarationtyperesolver::{DeclarationType, DeclarationTypeResolver};
911
use crate::bindgen::ir::{ConstExpr, Path, Type};
1012
use crate::bindgen::language_backend::LanguageBackend;
13+
use crate::bindgen::library::Library;
1114
use crate::bindgen::utilities::IterHelpers;
1215
use crate::bindgen::writer::SourceWriter;
1316

14-
#[derive(Debug, Clone)]
17+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1518
pub enum GenericParamType {
1619
Type,
1720
Const(Type),
1821
}
1922

20-
#[derive(Debug, Clone)]
23+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2124
pub struct GenericParam {
2225
name: Path,
2326
ty: GenericParamType,
@@ -97,6 +100,24 @@ impl GenericParams {
97100
Ok(GenericParams(params))
98101
}
99102

103+
/// If `generics` is empty, create a set of "default" generic arguments, which preserves the
104+
/// existing parameter name. Useful to allow `call` to work when no generics are provided.
105+
pub fn defaulted_generics<'a>(
106+
&self,
107+
generics: &'a [GenericArgument],
108+
) -> Cow<'a, [GenericArgument]> {
109+
if !self.is_empty() && generics.is_empty() {
110+
Cow::Owned(
111+
self.iter()
112+
.map(|param| Type::Path(GenericPath::new(param.name.clone(), vec![])))
113+
.map(GenericArgument::Type)
114+
.collect(),
115+
)
116+
} else {
117+
Cow::Borrowed(generics)
118+
}
119+
}
120+
100121
/// Associate each parameter with an argument.
101122
pub fn call<'out>(
102123
&'out self,
@@ -228,6 +249,48 @@ impl GenericArgument {
228249
}
229250
}
230251

252+
/// Helper for erasing transparent types, which memoizes already-seen types to avoid repeated work.
253+
#[derive(Default)]
254+
pub struct TransparentTypeEraser {
255+
// Remember paths we've already visited, so we don't repeat unnecessary work.
256+
// TODO: how to handle recursive types such as `struct Foo { next: Box<Foo> }`?
257+
known_types: HashMap<Type, Option<Type>>,
258+
}
259+
260+
impl TransparentTypeEraser {
261+
pub fn erase_transparent_types_inplace(
262+
&mut self,
263+
library: &Library,
264+
target: &mut Type,
265+
mappings: &[(&Path, &GenericArgument)],
266+
) {
267+
if let Some(erased_type) = self.erase_transparent_types(library, target, mappings) {
268+
*target = erased_type;
269+
}
270+
}
271+
272+
#[must_use]
273+
pub fn erase_transparent_types(
274+
&mut self,
275+
library: &Library,
276+
target: &Type,
277+
mappings: &[(&Path, &GenericArgument)],
278+
) -> Option<Type> {
279+
let known_type = self.known_types.get(target);
280+
let unknown_type = known_type.is_none();
281+
let erased_type = if let Some(ty) = known_type {
282+
ty.clone()
283+
} else {
284+
target.erase_transparent_types(library, mappings, self)
285+
};
286+
if unknown_type {
287+
debug!("Caching erasure of {:?} as {:?}", target, erased_type);
288+
self.known_types.insert(target.clone(), erased_type.clone());
289+
}
290+
erased_type
291+
}
292+
}
293+
231294
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
232295
pub struct GenericPath {
233296
path: Path,

0 commit comments

Comments
 (0)