diff --git a/docs.md b/docs.md index d420f2dbe..676572f1b 100644 --- a/docs.md +++ b/docs.md @@ -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` would normally generate a tag enum +# `COption_u8_Tag`, but with this option enabled all monomorphs of `COption` will +# use the same tag enum, named `COption_Tag`. +# +# default: false +merge_generic_tags = false + diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index c7590355a..55be140d9 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -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 { @@ -618,6 +620,7 @@ impl Default for EnumConfig { derive_ostream: false, enum_class: true, private_default_tagged_enum_constructor: false, + merge_generic_tags: false, } } } diff --git a/src/bindgen/dependencies.rs b/src/bindgen/dependencies.rs index 6a9873818..78dab6df2 100644 --- a/src/bindgen/dependencies.rs +++ b/src/bindgen/dependencies.rs @@ -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 { @@ -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. diff --git a/src/bindgen/ir/enumeration.rs b/src/bindgen/ir/enumeration.rs index b1b6c044f..50ca06f2f 100644 --- a/src/bindgen/ir/enumeration.rs +++ b/src/bindgen/ir/enumeration.rs @@ -325,6 +325,7 @@ pub struct Enum { pub repr: Repr, pub variants: Vec, pub tag: Option, + pub external_tag: bool, pub cfg: Option, pub annotations: AnnotationSet, pub documentation: Documentation, @@ -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), @@ -451,6 +453,7 @@ impl Enum { repr: Repr, variants: Vec, tag: Option, + external_tag: bool, cfg: Option, annotations: AnnotationSet, documentation: Documentation, @@ -463,6 +466,7 @@ impl Enum { repr, variants, tag, + external_tag, cfg, annotations, documentation, @@ -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 { @@ -553,6 +557,8 @@ 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 { @@ -560,11 +566,9 @@ impl Item for Enum { }; 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()); } } } @@ -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 { @@ -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(), @@ -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); } @@ -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 @@ -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 { @@ -898,6 +943,8 @@ impl Enum { if config.language != Language::Cython { condition.write_after(config, out); } + out.new_line(); + out.new_line(); } } } diff --git a/src/bindgen/ir/ty.rs b/src/bindgen/ir/ty.rs index 5a31fb646..aefcd6d43 100644 --- a/src/bindgen/ir/ty.rs +++ b/src/bindgen/ir/ty.rs @@ -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(_) => {} diff --git a/template.toml b/template.toml index bc414290e..38d04366d 100644 --- a/template.toml +++ b/template.toml @@ -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 diff --git a/tests/expectations/merge_generic_tags.both.c b/tests/expectations/merge_generic_tags.both.c new file mode 100644 index 000000000..d4227fdab --- /dev/null +++ b/tests/expectations/merge_generic_tags.both.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +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); diff --git a/tests/expectations/merge_generic_tags.both.compat.c b/tests/expectations/merge_generic_tags.both.compat.c new file mode 100644 index 000000000..eea6b2f16 --- /dev/null +++ b/tests/expectations/merge_generic_tags.both.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +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 diff --git a/tests/expectations/merge_generic_tags.c b/tests/expectations/merge_generic_tags.c new file mode 100644 index 000000000..3aa8827b7 --- /dev/null +++ b/tests/expectations/merge_generic_tags.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +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); diff --git a/tests/expectations/merge_generic_tags.compat.c b/tests/expectations/merge_generic_tags.compat.c new file mode 100644 index 000000000..dbbff1ff1 --- /dev/null +++ b/tests/expectations/merge_generic_tags.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +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; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void root(COption_u8 a, COption_u32 b); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/merge_generic_tags.cpp b/tests/expectations/merge_generic_tags.cpp new file mode 100644 index 000000000..4b0e148f0 --- /dev/null +++ b/tests/expectations/merge_generic_tags.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +template +struct COption { + enum class Tag { + Some, + None, + }; + + struct Some_Body { + T _0; + }; + + Tag tag; + union { + Some_Body some; + }; +}; + +extern "C" { + +void root(COption a, COption b); + +} // extern "C" diff --git a/tests/expectations/merge_generic_tags.pyx b/tests/expectations/merge_generic_tags.pyx new file mode 100644 index 000000000..ab0c318d8 --- /dev/null +++ b/tests/expectations/merge_generic_tags.pyx @@ -0,0 +1,21 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + ctypedef enum COption_Tag: + Some, + None, + + ctypedef struct COption_u8: + COption_Tag tag; + uint8_t some; + + ctypedef struct COption_u32: + COption_Tag tag; + uint32_t some; + + void root(COption_u8 a, COption_u32 b); diff --git a/tests/expectations/merge_generic_tags.tag.c b/tests/expectations/merge_generic_tags.tag.c new file mode 100644 index 000000000..455089007 --- /dev/null +++ b/tests/expectations/merge_generic_tags.tag.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +enum COption_Tag { + Some, + None, +}; + +struct COption_u8 { + enum COption_Tag tag; + union { + struct { + uint8_t some; + }; + }; +}; + +struct COption_u32 { + enum COption_Tag tag; + union { + struct { + uint32_t some; + }; + }; +}; + +void root(struct COption_u8 a, struct COption_u32 b); diff --git a/tests/expectations/merge_generic_tags.tag.compat.c b/tests/expectations/merge_generic_tags.tag.compat.c new file mode 100644 index 000000000..dee2d2a51 --- /dev/null +++ b/tests/expectations/merge_generic_tags.tag.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +enum COption_Tag { + Some, + None, +}; + +struct COption_u8 { + enum COption_Tag tag; + union { + struct { + uint8_t some; + }; + }; +}; + +struct COption_u32 { + enum COption_Tag tag; + union { + struct { + uint32_t some; + }; + }; +}; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void root(struct COption_u8 a, struct COption_u32 b); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/merge_generic_tags.tag.pyx b/tests/expectations/merge_generic_tags.tag.pyx new file mode 100644 index 000000000..5029d2104 --- /dev/null +++ b/tests/expectations/merge_generic_tags.tag.pyx @@ -0,0 +1,21 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + cdef enum COption_Tag: + Some, + None, + + cdef struct COption_u8: + COption_Tag tag; + uint8_t some; + + cdef struct COption_u32: + COption_Tag tag; + uint32_t some; + + void root(COption_u8 a, COption_u32 b); diff --git a/tests/expectations/merge_generic_tags_with_prefix.both.c b/tests/expectations/merge_generic_tags_with_prefix.both.c new file mode 100644 index 000000000..f0c3078d4 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.both.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +typedef enum COption_Tag { + COption_Some, + COption_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); diff --git a/tests/expectations/merge_generic_tags_with_prefix.both.compat.c b/tests/expectations/merge_generic_tags_with_prefix.both.compat.c new file mode 100644 index 000000000..ab9ce6c77 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.both.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +typedef enum COption_Tag { + COption_Some, + COption_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 diff --git a/tests/expectations/merge_generic_tags_with_prefix.c b/tests/expectations/merge_generic_tags_with_prefix.c new file mode 100644 index 000000000..b28f82ede --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +typedef enum { + COption_Some, + COption_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); diff --git a/tests/expectations/merge_generic_tags_with_prefix.compat.c b/tests/expectations/merge_generic_tags_with_prefix.compat.c new file mode 100644 index 000000000..30d91cfb7 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +typedef enum { + COption_Some, + COption_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; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void root(COption_u8 a, COption_u32 b); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/merge_generic_tags_with_prefix.cpp b/tests/expectations/merge_generic_tags_with_prefix.cpp new file mode 100644 index 000000000..464bf3179 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +template +struct COption { + enum class Tag { + COption_Some, + COption_None, + }; + + struct COption_Some_Body { + T _0; + }; + + Tag tag; + union { + COption_Some_Body some; + }; +}; + +extern "C" { + +void root(COption a, COption b); + +} // extern "C" diff --git a/tests/expectations/merge_generic_tags_with_prefix.pyx b/tests/expectations/merge_generic_tags_with_prefix.pyx new file mode 100644 index 000000000..031bc9f42 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.pyx @@ -0,0 +1,21 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + ctypedef enum COption_Tag: + COption_Some, + COption_None, + + ctypedef struct COption_u8: + COption_Tag tag; + uint8_t some; + + ctypedef struct COption_u32: + COption_Tag tag; + uint32_t some; + + void root(COption_u8 a, COption_u32 b); diff --git a/tests/expectations/merge_generic_tags_with_prefix.tag.c b/tests/expectations/merge_generic_tags_with_prefix.tag.c new file mode 100644 index 000000000..a8318f88e --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.tag.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +enum COption_Tag { + COption_Some, + COption_None, +}; + +struct COption_u8 { + enum COption_Tag tag; + union { + struct { + uint8_t some; + }; + }; +}; + +struct COption_u32 { + enum COption_Tag tag; + union { + struct { + uint32_t some; + }; + }; +}; + +void root(struct COption_u8 a, struct COption_u32 b); diff --git a/tests/expectations/merge_generic_tags_with_prefix.tag.compat.c b/tests/expectations/merge_generic_tags_with_prefix.tag.compat.c new file mode 100644 index 000000000..fa9297fb3 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.tag.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +enum COption_Tag { + COption_Some, + COption_None, +}; + +struct COption_u8 { + enum COption_Tag tag; + union { + struct { + uint8_t some; + }; + }; +}; + +struct COption_u32 { + enum COption_Tag tag; + union { + struct { + uint32_t some; + }; + }; +}; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void root(struct COption_u8 a, struct COption_u32 b); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/merge_generic_tags_with_prefix.tag.pyx b/tests/expectations/merge_generic_tags_with_prefix.tag.pyx new file mode 100644 index 000000000..64482ebb8 --- /dev/null +++ b/tests/expectations/merge_generic_tags_with_prefix.tag.pyx @@ -0,0 +1,21 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + cdef enum COption_Tag: + COption_Some, + COption_None, + + cdef struct COption_u8: + COption_Tag tag; + uint8_t some; + + cdef struct COption_u32: + COption_Tag tag; + uint32_t some; + + void root(COption_u8 a, COption_u32 b); diff --git a/tests/rust/merge_generic_tags.rs b/tests/rust/merge_generic_tags.rs new file mode 100644 index 000000000..d82164c0b --- /dev/null +++ b/tests/rust/merge_generic_tags.rs @@ -0,0 +1,8 @@ +#[repr(C)] +pub enum COption { + Some(T), + None, +} + +#[no_mangle] +pub extern "C" fn root(a: COption, b: COption) {} diff --git a/tests/rust/merge_generic_tags.toml b/tests/rust/merge_generic_tags.toml new file mode 100644 index 000000000..5014534dd --- /dev/null +++ b/tests/rust/merge_generic_tags.toml @@ -0,0 +1,2 @@ +[enum] +merge_generic_tags = true diff --git a/tests/rust/merge_generic_tags_with_prefix.rs b/tests/rust/merge_generic_tags_with_prefix.rs new file mode 100644 index 000000000..d82164c0b --- /dev/null +++ b/tests/rust/merge_generic_tags_with_prefix.rs @@ -0,0 +1,8 @@ +#[repr(C)] +pub enum COption { + Some(T), + None, +} + +#[no_mangle] +pub extern "C" fn root(a: COption, b: COption) {} diff --git a/tests/rust/merge_generic_tags_with_prefix.toml b/tests/rust/merge_generic_tags_with_prefix.toml new file mode 100644 index 000000000..57e15f6ce --- /dev/null +++ b/tests/rust/merge_generic_tags_with_prefix.toml @@ -0,0 +1,3 @@ +[enum] +merge_generic_tags = true +prefix_with_name = true