Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge generic enum tags #785

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,16 @@ derive_tagged_enum_copy_assignment = false
# default: false
private_default_tagged_enum_constructor = false

# Whether to only output a single tag enum for generic tagged enums. This only
# applies when generics are being monomorphized (i.e. not C++).
#
# For example, an enum monomorph `COption<u8>` would normally generate a tag enum
# `COption_u8_Tag`, but with this option enabled all monomorphs of `COption<T>` will
# use the same tag enum, named `COption_Tag`.
#
# default: false
merge_generic_tags = false




Expand Down
3 changes: 3 additions & 0 deletions src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,8 @@ pub struct EnumConfig {
/// Whether to generate empty, private default-constructors for tagged
/// enums.
pub private_default_tagged_enum_constructor: bool,
/// Whether to only output a single tag enum for generic tagged enums.
pub merge_generic_tags: bool,
}

impl Default for EnumConfig {
Expand All @@ -618,6 +620,7 @@ impl Default for EnumConfig {
derive_ostream: false,
enum_class: true,
private_default_tagged_enum_constructor: false,
merge_generic_tags: false,
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/bindgen/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::collections::HashSet;

use crate::bindgen::ir::{ItemContainer, Path};

use super::library::Library;

/// A dependency list is used for gathering what order to output the types.
#[derive(Default)]
pub struct Dependencies {
Expand All @@ -22,6 +24,24 @@ impl Dependencies {
}
}

pub fn add_path(&mut self, library: &Library, path: &Path) {
if let Some(items) = library.get_items(path) {
if !self.items.contains(path) {
self.items.insert(path.clone());

for item in &items {
item.deref().add_dependencies(library, self);
}
self.order.extend(items);
}
} else {
warn!(
"Can't find {}. This usually means that this type was incompatible or not found.",
path
);
}
}

pub fn sort(&mut self) {
// Sort untagged enums and opaque structs into their own layers because they don't
// depend on each other or anything else.
Expand Down
73 changes: 60 additions & 13 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ pub struct Enum {
pub repr: Repr,
pub variants: Vec<EnumVariant>,
pub tag: Option<String>,
pub external_tag: bool,
pub cfg: Option<Cfg>,
pub annotations: AnnotationSet,
pub documentation: Documentation,
Expand Down Expand Up @@ -438,6 +439,7 @@ impl Enum {
repr,
variants,
tag,
/* external_tag */ false,
Cfg::append(mod_cfg, Cfg::load(&item.attrs)),
annotations,
Documentation::load(&item.attrs),
Expand All @@ -451,6 +453,7 @@ impl Enum {
repr: Repr,
variants: Vec<EnumVariant>,
tag: Option<String>,
external_tag: bool,
cfg: Option<Cfg>,
annotations: AnnotationSet,
documentation: Documentation,
Expand All @@ -463,6 +466,7 @@ impl Enum {
repr,
variants,
tag,
external_tag,
cfg,
annotations,
documentation,
Expand Down Expand Up @@ -519,9 +523,9 @@ impl Item for Enum {
fn rename_for_config(&mut self, config: &Config) {
config.export.rename(&mut self.export_name);

if config.language != Language::Cxx && self.tag.is_some() {
if config.language != Language::Cxx && self.tag.is_some() && !self.external_tag {
// it makes sense to always prefix Tag with type name in C
let new_tag = format!("{}_Tag", self.export_name);
let new_tag = format!("{}_Tag", self.export_name());
if self.repr.style == ReprStyle::Rust {
for variant in &mut self.variants {
if let VariantBody::Body { ref mut body, .. } = variant.body {
Expand Down Expand Up @@ -553,18 +557,18 @@ impl Item for Enum {
if config.enumeration.prefix_with_name
|| self.annotations.bool("prefix-with-name").unwrap_or(false)
{
let prefix = self.export_name.trim_end_matches("_Tag");

let separator = if config.export.mangle.remove_underscores {
""
} else {
"_"
};

for variant in &mut self.variants {
variant.export_name =
format!("{}{}{}", self.export_name, separator, variant.export_name);
variant.export_name = format!("{}{}{}", prefix, separator, variant.export_name);
if let VariantBody::Body { ref mut body, .. } = variant.body {
body.export_name =
format!("{}{}{}", self.export_name, separator, body.export_name());
body.export_name = format!("{}{}{}", prefix, separator, body.export_name());
}
}
}
Expand Down Expand Up @@ -616,6 +620,34 @@ impl Item for Enum {
library: &Library,
out: &mut Monomorphs,
) {
let config = library.get_config();
let external_tag = config.enumeration.merge_generic_tags;

let tag = if external_tag {
let new_tag = format!("{}_Tag", self.export_name());
let path = Path::new(new_tag.clone());

if !out.contains(&GenericPath::new(self.path.clone(), vec![])) {
let tag = Enum::new(
path,
GenericParams::default(),
self.repr,
self.variants.clone(),
None,
false,
self.cfg.clone(),
self.annotations.clone(),
self.documentation.clone(),
);

out.insert_enum(library, self, tag, vec![]);
}

Some(new_tag)
} else {
self.tag.clone()
};

let mappings = self.generic_params.call(self.path.name(), generic_values);

for variant in &self.variants {
Expand All @@ -638,7 +670,8 @@ impl Item for Enum {
.iter()
.map(|v| v.specialize(generic_values, &mappings, library.get_config()))
.collect(),
self.tag.clone(),
tag,
external_tag,
self.cfg.clone(),
self.annotations.clone(),
self.documentation.clone(),
Expand All @@ -648,6 +681,15 @@ impl Item for Enum {
}

fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
if self.external_tag {
if let Some(tag) = self.tag.clone() {
let path = Path::new(tag);

// If there is an external tag enum, then add it as a dependency.
out.add_path(library, &path);
}
}

for variant in &self.variants {
variant.add_dependencies(library, out);
}
Expand All @@ -674,14 +716,19 @@ impl Source for Enum {
self.open_struct_or_union(config, out, inline_tag_field);
}

// Emit the tag enum and everything related to it.
self.write_tag_enum(config, out, size, has_data, tag_name);
if !self.external_tag {
// Emit the tag enum and everything related to it.
self.write_tag_enum(config, out, size, has_data, tag_name);

if has_data {
out.new_line();
out.new_line();
}
}

// If the enum has data, we need to emit structs for the variants and gather them together.
if has_data {
self.write_variant_defs(config, out);
out.new_line();
out.new_line();

// Open the struct or union for the data (**), gathering all the variants with data
// together, unless it's C++, then we have already opened that struct/union at (*) and
Expand Down Expand Up @@ -887,8 +934,6 @@ impl Enum {
..
} = variant.body
{
out.new_line();
out.new_line();
let condition = variant.cfg.to_condition(config);
// Cython doesn't support conditional enum variants.
if config.language != Language::Cython {
Expand All @@ -898,6 +943,8 @@ impl Enum {
if config.language != Language::Cython {
condition.write_after(config, out);
}
out.new_line();
out.new_line();
}
}
}
Expand Down
19 changes: 1 addition & 18 deletions src/bindgen/ir/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,24 +823,7 @@ impl Type {
}
let path = generic.path();
if !generic_params.iter().any(|param| param.name() == path) {
if let Some(items) = library.get_items(path) {
if !out.items.contains(path) {
out.items.insert(path.clone());

for item in &items {
item.deref().add_dependencies(library, out);
}
for item in items {
out.order.push(item);
}
}
} else {
warn!(
"Can't find {}. This usually means that this type was incompatible or \
not found.",
path
);
}
out.add_path(library, path);
}
}
Type::Primitive(_) => {}
Expand Down
1 change: 1 addition & 0 deletions template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ derive_tagged_enum_destructor = false
derive_tagged_enum_copy_constructor = false
enum_class = true
private_default_tagged_enum_constructor = false
merge_generic_tags = false



Expand Down
29 changes: 29 additions & 0 deletions tests/expectations/merge_generic_tags.both.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef enum COption_Tag {
Some,
None,
} COption_Tag;

typedef struct COption_u8 {
COption_Tag tag;
union {
struct {
uint8_t some;
};
};
} COption_u8;

typedef struct COption_u32 {
COption_Tag tag;
union {
struct {
uint32_t some;
};
};
} COption_u32;

void root(struct COption_u8 a, struct COption_u32 b);
37 changes: 37 additions & 0 deletions tests/expectations/merge_generic_tags.both.compat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef enum COption_Tag {
Some,
None,
} COption_Tag;

typedef struct COption_u8 {
COption_Tag tag;
union {
struct {
uint8_t some;
};
};
} COption_u8;

typedef struct COption_u32 {
COption_Tag tag;
union {
struct {
uint32_t some;
};
};
} COption_u32;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void root(struct COption_u8 a, struct COption_u32 b);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
29 changes: 29 additions & 0 deletions tests/expectations/merge_generic_tags.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef enum {
Some,
None,
} COption_Tag;

typedef struct {
COption_Tag tag;
union {
struct {
uint8_t some;
};
};
} COption_u8;

typedef struct {
COption_Tag tag;
union {
struct {
uint32_t some;
};
};
} COption_u32;

void root(COption_u8 a, COption_u32 b);
Loading