Skip to content

Commit 8a76012

Browse files
committed
implement transparent typedef feature
1 parent ba96d90 commit 8a76012

37 files changed

+1817
-213
lines changed

docs.md

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

300300
### Struct Annotations
301301

302-
* 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.
302+
* field-names=\[field1, field2, ...\] -- sets the names of all the fields in the output
303+
struct. These names will be output verbatim, and are not eligible for renaming.
304+
305+
* transparent-typedef -- when emitting the typedef for a transparent struct, mark it as
306+
transparent. All references to the struct will be replaced with the type of its underlying NZST
307+
field, effectively making the struct invisible on the FFI side. For example, consider the
308+
following Rust code:
309+
310+
```rust
311+
#[repr(transparent)]
312+
pub struct Handle<T> {
313+
ptr: NonNull<T>,
314+
}
315+
316+
pub struct Foo { }
317+
318+
#[no_mangle]
319+
pub extern "C" fn foo_operation(foo: Option<Handle<Foo>>) { }
320+
```
321+
322+
By default, the exported C++ code would fail to compile, because the function takes `Option<...>`
323+
(which is an opaque type) by value:
324+
325+
```cpp
326+
template<typename T>
327+
struct Option<T>;
328+
329+
template<typename T>
330+
using Handle = T;
331+
332+
struct Foo;
333+
334+
void foo_operation(Option<Handle<Foo>> foo);
335+
```
336+
337+
If we annotate `Handle` with `transparent-typedef` (leaving the rest of the code unchanged):
338+
```rust
339+
/// cbindgen:transparent-typedef
340+
#[repr(transparent)]
341+
pub struct Handle<T> {
342+
ptr: NonNull<T>,
343+
}
344+
```
345+
346+
Then cbindgen is able to simplify the exported C++ code to just:
347+
```cpp
348+
struct Foo;
349+
350+
void foo_operation(Foo* foo);
351+
```
352+
353+
NOTE: This annotation does _NOT_ affect user-defined type aliases for transparent structs. If we
354+
we adjust the previous example to use a type alias:
355+
356+
```rust
357+
type NullableFooHandle = Option<Handle<Foo>>;
358+
359+
#[no_mangle]
360+
pub extern "C" fn foo_operation(foo: NullableFooHandle) { }
361+
```
362+
363+
Then the exported code will use it as expected:
364+
365+
```cpp
366+
struct Foo;
367+
368+
using NullableFooHandle = Foo*;
369+
370+
void foo_operation(NullableFooHandle foo);
371+
```
303372
304373
The rest are just local overrides for the same options found in the cbindgen.toml:
305374
@@ -316,27 +385,25 @@ The rest are just local overrides for the same options found in the cbindgen.tom
316385
/ etc(if any). The idea is for this to be used to annotate the operator with
317386
attributes, for example:
318387
319-
```rust
320-
/// cbindgen:eq-attributes=MY_ATTRIBUTES
321-
#[repr(C)]
322-
pub struct Foo { .. }
323-
```
324-
325-
Will generate something like:
388+
```rust
389+
/// cbindgen:eq-attributes=MY_ATTRIBUTES
390+
#[repr(C)]
391+
pub struct Foo { .. }
392+
```
326393

327-
```
328-
MY_ATTRIBUTES bool operator==(const Foo& other) const {
329-
...
330-
}
331-
```
394+
Will generate something like:
332395

333-
Combined with something like:
396+
```
397+
MY_ATTRIBUTES bool operator==(const Foo& other) const {
398+
...
399+
}
400+
```
334401

335-
```
336-
#define MY_ATTRIBUTES [[nodiscard]]
337-
```
402+
Combined with something like:
338403

339-
for example.
404+
```
405+
#define MY_ATTRIBUTES [[nodiscard]]
406+
```
340407

341408
### Enum Annotations
342409

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;
@@ -603,6 +603,18 @@ impl Item for Constant {
603603
fn generic_params(&self) -> &GenericParams {
604604
GenericParams::empty()
605605
}
606+
607+
fn erase_transparent_types_inplace(
608+
&mut self,
609+
library: &Library,
610+
eraser: &mut TransparentTypeEraser,
611+
_generics: &[GenericArgument],
612+
) {
613+
// NOTE: We also need to simplify the literal initializer value to match the underlying
614+
// type, but that is true for all transparent structs (not just transparent-typedef
615+
// structs), and is handled by the `write` method below.
616+
eraser.erase_transparent_types_inplace(library, &mut self.ty, &[]);
617+
}
606618
}
607619

608620
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
&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};
@@ -115,13 +117,6 @@ impl Function {
115117
&self.path
116118
}
117119

118-
pub fn simplify_standard_types(&mut self, config: &Config) {
119-
self.ret.simplify_standard_types(config);
120-
for arg in &mut self.args {
121-
arg.ty.simplify_standard_types(config);
122-
}
123-
}
124-
125120
pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
126121
self.ret.add_dependencies(library, out);
127122
for arg in &self.args {
@@ -150,6 +145,18 @@ impl Function {
150145
}
151146
}
152147

148+
// NOTE: No `generics` arg because Functions do not support generics and do not `impl Item`.
149+
pub fn erase_transparent_types_inplace(
150+
&mut self,
151+
library: &Library,
152+
eraser: &mut TransparentTypeEraser,
153+
) {
154+
eraser.erase_transparent_types_inplace(library, &mut self.ret, &[]);
155+
for arg in &mut self.args {
156+
eraser.erase_transparent_types_inplace(library, &mut arg.ty, &[]);
157+
}
158+
}
159+
153160
pub fn rename_for_config(&mut self, config: &Config) {
154161
// Rename the types used in arguments
155162
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,
@@ -103,6 +106,24 @@ impl GenericParams {
103106
Ok(GenericParams(params))
104107
}
105108

109+
/// If `generics` is empty, create a set of "default" generic arguments, which preserves the
110+
/// existing parameter name. Useful to allow `call` to work when no generics are provided.
111+
pub fn defaulted_generics<'a>(
112+
&self,
113+
generics: &'a [GenericArgument],
114+
) -> Cow<'a, [GenericArgument]> {
115+
if !self.is_empty() && generics.is_empty() {
116+
Cow::Owned(
117+
self.iter()
118+
.map(|param| Type::Path(GenericPath::new(param.name.clone(), vec![])))
119+
.map(GenericArgument::Type)
120+
.collect(),
121+
)
122+
} else {
123+
Cow::Borrowed(generics)
124+
}
125+
}
126+
106127
/// Associate each parameter with an argument.
107128
pub fn call<'out>(
108129
&'out self,
@@ -234,6 +255,48 @@ impl GenericArgument {
234255
}
235256
}
236257

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

0 commit comments

Comments
 (0)