diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs index 6ad07ef2c..16a4adce2 100644 --- a/engine/src/conversion/analysis/fun/mod.rs +++ b/engine/src/conversion/analysis/fun/mod.rs @@ -2049,6 +2049,7 @@ impl<'a> FnAnalyzer<'a> { /// Also fills out the [`PodAndConstructorAnalysis::constructors`] fields with information useful /// for further analysis phases. fn add_constructors_present(&mut self, mut apis: ApiVec) -> ApiVec { + let enums = self.config.get_enums(); let all_items_found = find_constructors_present(&apis); for (self_ty, items_found) in all_items_found.iter() { if self.config.exclude_impls { @@ -2057,10 +2058,8 @@ impl<'a> FnAnalyzer<'a> { // messy, see the comment on this function for why. continue; } - if self - .config - .is_on_constructor_blocklist(&self_ty.to_cpp_name()) - { + let cpp_name = self_ty.to_cpp_name(); + if self.config.is_on_constructor_blocklist(&cpp_name) || enums.contains(&cpp_name) { continue; } let path = self_ty.to_type_path(); diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 26a126de8..53bfced0c 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -381,6 +381,22 @@ impl IncludeCppEngine { .allowlist_var(&a); } } + + for (style, enums) in &self.config.enum_styles.0 { + use autocxx_parser::EnumStyle::*; + let apply: fn(bindgen::Builder, &String) -> bindgen::Builder = match style { + BitfieldEnum => |b, e| b.bitfield_enum(e), + NewtypeEnum => |b, e| b.newtype_enum(e), + // NewtypeGlobalEnum => |b, e| b.newtype_global_enum(e), + RustifiedEnum => |b, e| b.rustified_enum(e), + RustifiedNonExhaustiveEnum => |b, e| b.rustified_non_exhaustive_enum(e), + // ConstifiedEnumModule => |b, e| b.constified_enum_module(e), + // ConstifiedEnum => |b, e| b.constified_enum(e), + }; + for name in enums { + builder = apply(builder, name); + } + } for item in &self.config.opaquelist { builder = builder.opaque_type(item); diff --git a/integration-tests/tests/integration_test.rs b/integration-tests/tests/integration_test.rs index 9d6590023..415828e33 100644 --- a/integration-tests/tests/integration_test.rs +++ b/integration-tests/tests/integration_test.rs @@ -1115,6 +1115,179 @@ fn test_enum_with_funcs() { run_test(cxx, hdr, rs, &["Bob", "give_bob"], &[]); } +#[test] +fn test_bitfield_enum() { + let hdr = indoc! {" + #include + enum SomeFlags : int { + FLAG_A = 1 << 0, // 0x1 + FLAG_B = 1 << 2, // 0x4 + FLAG_C = FLAG_A | FLAG_B, // 0x5 + }; + "}; + let hexathorpe = Token![#](Span::call_site()); + let rs = quote! { + use autocxx::prelude::*; + include_cpp! { + #hexathorpe include "input.h" + safety!(unsafe_ffi) + enum_style!(BitfieldEnum, "SomeFlags") + generate_pod!("SomeFlags") + } + + fn main() { + let a = ffi::SomeFlags::FLAG_A; + let b = ffi::SomeFlags::FLAG_B; + let c = ffi::SomeFlags::FLAG_C; + assert_eq!(a.0, 0x1); + assert_eq!(b.0, 0x4); + assert_eq!(c.0, 0x5); + + let aob = ffi::SomeFlags::FLAG_A | ffi::SomeFlags::FLAG_B; + assert_eq!(aob.0, 0x5); + assert_eq!(aob.0, ffi::SomeFlags::FLAG_C.0); + + let anb = ffi::SomeFlags::FLAG_A & ffi::SomeFlags::FLAG_B; + assert_eq!(anb.0, 0x0); + } + }; + do_run_test_manual("", hdr, rs, None, None).unwrap(); +} + +#[test] +fn test_newtype_enum() { + let hdr = indoc! {" + #include + enum SomeFlags : int { + FLAG_A = 1 << 0, // 0x1 + FLAG_B = 1 << 2, // 0x4 + FLAG_C = FLAG_A | FLAG_B, // 0x5 + }; + "}; + let hexathorpe = Token![#](Span::call_site()); + let rs = quote! { + use autocxx::prelude::*; + include_cpp! { + #hexathorpe include "input.h" + safety!(unsafe_ffi) + enum_style!(NewtypeEnum, "SomeFlags") + generate_pod!("SomeFlags") + } + + fn main() { + let a = ffi::SomeFlags::FLAG_A; + let b = ffi::SomeFlags::FLAG_B; + let c = ffi::SomeFlags::FLAG_C; + assert_eq!(a.0, 0x1); + assert_eq!(b.0, 0x4); + assert_eq!(c.0, 0x5); + } + }; + do_run_test_manual("", hdr, rs, None, None).unwrap(); +} + +#[test] +fn test_rustified_enum() { + let hdr = indoc! {" + enum Bob { + BOB_VALUE_1, + BOB_VALUE_2, + }; + "}; + let hexathorpe = Token![#](Span::call_site()); + let rs = quote! { + use autocxx::prelude::*; + include_cpp! { + #hexathorpe include "input.h" + safety!(unsafe_ffi) + enum_style!(RustifiedEnum, "Bob") + generate_pod!("Bob") + } + + fn main() { + let a = ffi::Bob::BOB_VALUE_1; + let b = ffi::Bob::BOB_VALUE_2; + assert!(a != b); + } + }; + do_run_test_manual("", hdr, rs, None, None).unwrap(); +} + +#[test] +fn test_rustified_nonexhaustive_enum() { + let hdr = indoc! {" + enum Bob { + BOB_VALUE_1, + BOB_VALUE_2, + }; + "}; + let hexathorpe = Token![#](Span::call_site()); + let rs = quote! { + use autocxx::prelude::*; + include_cpp! { + #hexathorpe include "input.h" + safety!(unsafe_ffi) + enum_style!(RustifiedEnum, "Bob") + generate_pod!("Bob") + } + + fn main() { + let a = ffi::Bob::BOB_VALUE_1; + let b = ffi::Bob::BOB_VALUE_2; + assert!(a != b); + } + }; + do_run_test_manual("", hdr, rs, None, None).unwrap(); +} + +#[test] +fn test_several_enums() { + let hdr = indoc! {" + enum First : int { + FIRST_A = 5, + FIRST_B = 6 + }; + enum Second { + SECOND_A, + SECOND_B + }; + enum Default : int { + DEFAULT_A = 1 << 1, + DEFAULT_B = 1 << 3 + }; + enum Newtype { + NEWTYPE_A, + NEWTYPE_B + }; + "}; + let hexathorpe = Token![#](Span::call_site()); + let rs = quote! { + use autocxx::prelude::*; + include_cpp! { + #hexathorpe include "input.h" + safety!(unsafe_ffi) + enum_style!(BitfieldEnum, "First", "Second") + enum_style!(NewtypeEnum, "Newtype") + generate_pod!("First") + generate_pod!("Second") + generate_pod!("Newtype") + generate!("Default") + } + + fn main() { + let first_a = ffi::First::FIRST_A; + let first_b = ffi::First::FIRST_B; + assert_eq!((first_a & first_b).0, 0x4); + let second_a = ffi::Second::SECOND_A; + let second_b = ffi::Second::SECOND_B; + assert_eq!((second_a | second_b).0, 0x1); + assert!(ffi::Default::DEFAULT_A != ffi::Default::DEFAULT_B); + assert!(ffi::Newtype::NEWTYPE_A != ffi::Newtype::NEWTYPE_B); + } + }; + do_run_test_manual("", hdr, rs, None, None).unwrap(); +} + #[test] fn test_re_export() { let cxx = indoc! {" diff --git a/parser/src/config.rs b/parser/src/config.rs index 925039c16..39adc2f57 100644 --- a/parser/src/config.rs +++ b/parser/src/config.rs @@ -25,6 +25,7 @@ use syn::{ use syn::{Ident, Result as ParseResult}; use thiserror::Error; +use crate::enum_style::EnumStyleMap; use crate::{directives::get_directives, RustPath}; use quote::quote; @@ -228,6 +229,7 @@ pub struct IncludeCppConfig { pub concretes: ConcretesMap, pub externs: ExternCppTypeMap, pub opaquelist: Vec, + pub enum_styles: EnumStyleMap, } impl Parse for IncludeCppConfig { @@ -384,6 +386,10 @@ impl IncludeCppConfig { self.constructor_blocklist.contains(&cpp_name.to_string()) } + pub fn get_enums(&self) -> HashSet<&String> { + self.enum_styles.get_enum_names() + } + pub fn get_blocklist(&self) -> impl Iterator { self.blocklist.iter() } diff --git a/parser/src/directives.rs b/parser/src/directives.rs index a76cb0656..1802b0372 100644 --- a/parser/src/directives.rs +++ b/parser/src/directives.rs @@ -24,6 +24,7 @@ use crate::config::AllowlistErr; use crate::config::Allowlist; use crate::directive_names::{EXTERN_RUST_FUN, EXTERN_RUST_TYPE, SUBCLASS}; +use crate::enum_style::EnumStyleMap; use crate::{AllowlistEntry, IncludeCppConfig}; use crate::{ParseResult, RustFun, RustPath}; @@ -114,6 +115,13 @@ pub(crate) fn get_directives() -> &'static DirectivesMap { "extern_cpp_opaque_type".into(), Box::new(ExternCppType { opaque: true }), ); + need_exclamation.insert( + "enum_style".into(), + Box::new(EnumStyle( + |config| &mut config.enum_styles, + |config| &config.enum_styles, + )), + ); DirectivesMap { need_hexathorpe, @@ -567,3 +575,51 @@ impl Directive for ExternCppType { ) } } + +struct EnumStyle(SET, GET) +where + SET: Fn(&mut IncludeCppConfig) -> &mut EnumStyleMap, + GET: Fn(&IncludeCppConfig) -> &EnumStyleMap; + +impl Directive for EnumStyle +where + SET: Fn(&mut IncludeCppConfig) -> &mut EnumStyleMap + Sync + Send, + GET: Fn(&IncludeCppConfig) -> &EnumStyleMap + Sync + Send, +{ + fn parse( + &self, + args: ParseStream, + config: &mut IncludeCppConfig, + _ident_span: &Span, + ) -> ParseResult<()> { + let style: crate::EnumStyle = args.parse()?; + args.parse::()?; + let litstrs: syn::punctuated::Punctuated = + syn::punctuated::Punctuated::parse_separated_nonempty(args)?; + let enums = litstrs.into_iter().map(|s| s.value()); + + config + .enum_styles + .0 + .entry(style) + .or_insert_with(Vec::new) + .extend(enums); + Ok(()) + } + + #[cfg(feature = "reproduction_case")] + fn output<'a>( + &self, + config: &'a IncludeCppConfig, + ) -> Box + 'a> { + Box::new(config.enum_styles.0.iter().map(|(style, enums)| { + let lits: Vec = enums + .iter() + .map(|s| syn::LitStr::new(s, Span::call_site())) + .collect(); + quote! { + #(#lits),* , #style + } + })) + } +} diff --git a/parser/src/enum_style.rs b/parser/src/enum_style.rs new file mode 100644 index 000000000..52e390b5d --- /dev/null +++ b/parser/src/enum_style.rs @@ -0,0 +1,78 @@ +use proc_macro2::{Ident, Span}; +use quote::{quote, ToTokens}; +use indexmap::set::IndexSet as HashSet; +use indexmap::map::IndexMap as HashMap; +use syn::parse::Parse; + +#[derive(Debug, Hash, PartialEq, Eq)] +pub enum EnumStyle { + BitfieldEnum, + NewtypeEnum, + // NewtypeGlobalEnum, + RustifiedEnum, + RustifiedNonExhaustiveEnum, + // ConstifiedEnumModule, + // ConstifiedEnum, +} + +#[derive(Debug, Default)] +pub struct EnumStyleMap(pub HashMap>); + +impl std::hash::Hash for EnumStyleMap { + fn hash(&self, state: &mut H) { + for (k, v) in &self.0 { + k.hash(state); + v.hash(state); + } + } +} + +impl EnumStyleMap { + pub fn get_enum_names(&self) -> HashSet<&String> { + self.0.values().flat_map(|v| v.iter()).collect() + } +} + +impl EnumStyle { + fn from_str(s: &str) -> Option { + use EnumStyle::*; + Some(match s { + "BitfieldEnum" => BitfieldEnum, + "NewtypeEnum" => NewtypeEnum, + // "NewtypeGlobalEnum" => NewtypeGlobalEnum, + "RustifiedEnum" => RustifiedEnum, + "RustifiedNonExhaustiveEnum" => RustifiedNonExhaustiveEnum, + // "ConstifiedEnumModule" => ConstifiedEnumModule, + // "ConstifiedEnum" => ConstifiedEnum, + _ => return None, + }) + } +} + +impl Parse for EnumStyle { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let style_ident: Ident = input.parse()?; + let style = style_ident.to_string(); + EnumStyle::from_str(&style).ok_or(syn::Error::new( + style_ident.span(), + format!("unknown enum style `{}`", style), + )) + } +} + +impl ToTokens for EnumStyle { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use EnumStyle::*; + let variant_ident = match self { + BitfieldEnum => "BitfieldEnum", + NewtypeEnum => "NewtypeEnum", + // NewtypeGlobalEnum => "NewtypeGlobalEnum", + RustifiedEnum => "RustifiedEnum", + RustifiedNonExhaustiveEnum => "RustifiedNonExhaustiveEnum", + // ConstifiedEnumModule => "ConstifiedEnumModule", + // ConstifiedEnum => "ConstifiedEnum", + }; + let var = Ident::new(variant_ident, Span::call_site()); + tokens.extend(quote! { #var }); + } +} diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 9e3ba7ed3..4e3e9e2fe 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -14,10 +14,12 @@ pub mod file_locations; mod multi_bindings; mod path; mod subclass_attrs; +mod enum_style; pub use config::{ AllowlistEntry, ExternCppType, IncludeCppConfig, RustFun, Subclass, UnsafePolicy, }; +pub use enum_style::EnumStyle; use file_locations::FileLocationStrategy; pub use multi_bindings::{MultiBindings, MultiBindingsErr}; pub use path::RustPath; diff --git a/src/lib.rs b/src/lib.rs index 6f42fe52f..c5fd0d7e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,6 +402,51 @@ macro_rules! subclass { ($($tt:tt)*) => { $crate::usage!{$($tt)*} }; } +/// Instruct the code generator to apply a specific enum-variation style to +/// one or more C++ enums. +/// +/// By default, C++ enums use the style set to `RustifiedEnum`. +/// To override that on a per-enum basis, place `enum_style!` inside your +/// [include_cpp] block. +/// +/// The syntax is: +/// enum_style!(BitfieldEnum, "FirstEnum", "SecondEnum"); +/// generate_pod!("ThirdEnum") +/// enum_style!(RustifiedEnum, "ThirdEnum"); +/// generate_pod!("OtherEnum") +/// generate_pod!("MyEnum") +/// +/// Style variant must be one of: +/// - NewtypeEnum +/// Generate integer newtype representing the `enum` type and its variants +/// will be represented as constants inside of this type's `impl` block. +/// - BitfieldEnum +/// Same as `NewtypeEnum` with bitwise ops (`&`, `|`) +/// - RustifiedEnum +/// Generate a native Rust `enum` (unsafe if invalid discriminant) +/// - RustifiedNonExhaustiveEnum +/// Same as `RustifiedEnum`, but annotated `#[non_exhaustive]` +/// +/// Rust bindings generation +/// - Enums styled via BitfieldEnum or NewtypeEnum must be emitted through +/// [generate_pod]. +/// - Enums styled as RustifiedEnum or RustifiedNonExhaustiveEnum can be +/// emitted via either [generate_pod] or the standard [generate] macro. +/// - Since the default style is RustifiedEnum, enums can be +/// emitted via [generate] or [generate_pod]. +/// +/// Notes +/// - You can repeat [enum_style] to style multiple, disjoint sets of enums. +/// - Styles not yet exposed (`NewtypeGlobalEnum`, `ConstifiedEnum`, +/// `ConstifiedEnumModule`) are commented out in the code and unavailable. +/// +/// A directive to be included inside +/// [include_cpp] - see [include_cpp] for general information. +#[macro_export] +macro_rules! enum_style { + ($($tt:tt)*) => { $crate::usage!{$($tt)*} }; +} + /// Indicates that a C++ type can definitely be instantiated. This has effect /// only in a very specific case: /// * the type is a typedef to something else