From 85819386733567b773291733a9407e1316339dd5 Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Tue, 6 Aug 2024 15:29:26 -0400 Subject: [PATCH] Add option to generate safe and unsafe conversions for rustified enums --- bindgen-integration/build.rs | 2 + .../tests/expectations/tests/issue-2646.rs | 100 ++++ bindgen-tests/tests/headers/issue-2646.h | 30 ++ bindgen/codegen/mod.rs | 437 ++++++++++++++---- bindgen/ir/enum_ty.rs | 102 +++- bindgen/lib.rs | 18 +- bindgen/options/cli.rs | 32 +- bindgen/options/mod.rs | 17 +- 8 files changed, 599 insertions(+), 139 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/issue-2646.rs create mode 100644 bindgen-tests/tests/headers/issue-2646.h diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index 583f46ea93..7bf70366d7 100644 --- a/bindgen-integration/build.rs +++ b/bindgen-integration/build.rs @@ -192,6 +192,8 @@ fn setup_macro_test() { .enable_cxx_namespaces() .default_enum_style(EnumVariation::Rust { non_exhaustive: false, + safe_conversion: false, + unsafe_conversion: false, }) .raw_line("pub use self::root::*;") .raw_line("extern { fn my_prefixed_function_to_remove(i: i32); }") diff --git a/bindgen-tests/tests/expectations/tests/issue-2646.rs b/bindgen-tests/tests/expectations/tests/issue-2646.rs new file mode 100644 index 0000000000..46a5c463a4 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/issue-2646.rs @@ -0,0 +1,100 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Plain { + Plain1 = 0, + Plain2 = 1, + Plain3 = 2, +} +pub type TryFromRaw_ctype = ::std::os::raw::c_int; +impl TryFromRaw { + pub const TFR1: TryFromRaw_ctype = -1; + pub const TFR2: TryFromRaw_ctype = 5; + pub const TFR3: TryFromRaw_ctype = 6; +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum TryFromRaw { + TFR1 = -1, + TFR2 = 5, + TFR3 = 6, +} +pub struct TryFromRawError(TryFromRaw_ctype); +impl TryFromRawError { + #[must_use] + pub fn value(&self) -> TryFromRaw_ctype { + self.0 + } +} +impl std::convert::TryFrom for TryFromRaw { + type Error = TryFromRawError; + fn try_from(v: TryFromRaw_ctype) -> Result { + match v { + -1 => Ok(TryFromRaw::TFR1), + 5 => Ok(TryFromRaw::TFR2), + 6 => Ok(TryFromRaw::TFR3), + _ => Err(TryFromRawError(v)), + } + } +} +pub type FromRawUnchecked_ctype = ::std::os::raw::c_uint; +impl FromRawUnchecked { + pub const FRU1: FromRawUnchecked_ctype = 6; + pub const FRU2: FromRawUnchecked_ctype = 10; + pub const FRU3: FromRawUnchecked_ctype = 11; +} +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum FromRawUnchecked { + FRU1 = 6, + FRU2 = 10, + FRU3 = 11, +} +impl FromRawUnchecked { + const unsafe fn from_ctype_unchecked(v: FromRawUnchecked_ctype) -> Self { + std::mem::transmute(v) + } +} +impl Both { + pub const Both3: Both = Both::Both1; +} +pub type Both_ctype = ::std::os::raw::c_int; +impl Both { + pub const Both1: Both_ctype = 0; + pub const Both2: Both_ctype = -1; +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Both { + Both1 = 0, + Both2 = -1, +} +pub struct BothError(Both_ctype); +impl BothError { + #[must_use] + pub fn value(&self) -> Both_ctype { + self.0 + } +} +impl std::convert::TryFrom for Both { + type Error = BothError; + fn try_from(v: Both_ctype) -> Result { + match v { + 0 => Ok(Both::Both1), + -1 => Ok(Both::Both2), + _ => Err(BothError(v)), + } + } +} +impl Both { + const unsafe fn from_ctype_unchecked(v: Both_ctype) -> Self { + std::mem::transmute(v) + } +} +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum NonExhaustive { + Ex1 = 0, + Ex2 = 1, +} diff --git a/bindgen-tests/tests/headers/issue-2646.h b/bindgen-tests/tests/headers/issue-2646.h new file mode 100644 index 0000000000..63068780eb --- /dev/null +++ b/bindgen-tests/tests/headers/issue-2646.h @@ -0,0 +1,30 @@ +// bindgen-flags: --rustified-enum 'Plain.*' --rustified-enum 'TryFromRaw.*=try_from_raw' --rustified-enum='FromRawUnchecked.*=from_raw_unchecked' --rustified-enum='Both.*=try_from_raw,from_raw_unchecked' --rustified-enum 'NonExhaustive.*=non_exhaustive' + +enum Plain { + Plain1, + Plain2, + Plain3 +}; + +enum TryFromRaw { + TFR1 = -1, + TFR2 = 5, + TFR3 +}; + +enum FromRawUnchecked { + FRU1 = 6, + FRU2 = 10, + FRU3 = 11, +}; + +enum Both { + Both1, + Both2 = -1, + Both3, +}; + +enum NonExhaustive { + Ex1, + Ex2, +}; diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 403ac42839..8c4da08b79 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -3171,6 +3171,12 @@ pub enum EnumVariation { Rust { /// Indicates whether the generated struct should be `#[non_exhaustive]` non_exhaustive: bool, + /// Indicates whether the generated struct should have a safe conversion from an integer + /// value. + safe_conversion: bool, + /// Indicates whether the generated struct should have an unsafe conversion from an integer + /// value. + unsafe_conversion: bool, }, /// The code for this enum will use a newtype NewType { @@ -3202,11 +3208,20 @@ impl fmt::Display for EnumVariation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Rust { - non_exhaustive: false, - } => "rust", - Self::Rust { - non_exhaustive: true, - } => "rust_non_exhaustive", + non_exhaustive, + safe_conversion, + unsafe_conversion, + .. + } => match (non_exhaustive, safe_conversion, unsafe_conversion) { + (false, false, false) => "rust", + (false, true, true) => "rust_conversions", + (false, true, _) => "rust_safe_conversion", + (false, _, true) => "rust_unsafe_conversion", + (true, false, false) => "rust_non_exhaustive", + (true, true, true) => "rust_non_exhaustive_conversions", + (true, true, _) => "rust_non_exhaustive_safe_conversion", + (true, _, true) => "rust_non_exhaustive_unsafe_conversion", + }, Self::NewType { is_bitfield: true, .. } => "bitfield", @@ -3235,10 +3250,46 @@ impl FromStr for EnumVariation { match s { "rust" => Ok(EnumVariation::Rust { non_exhaustive: false, + safe_conversion: false, + unsafe_conversion: false, + }), + "rust_conversions" => Ok(EnumVariation::Rust { + non_exhaustive: false, + safe_conversion: true, + unsafe_conversion: true, + }), + "rust_safe_conversion" => Ok(EnumVariation::Rust { + non_exhaustive: false, + safe_conversion: true, + unsafe_conversion: false, + }), + "rust_unsafe_conversion" => Ok(EnumVariation::Rust { + non_exhaustive: false, + safe_conversion: false, + unsafe_conversion: true, }), "rust_non_exhaustive" => Ok(EnumVariation::Rust { non_exhaustive: true, + safe_conversion: false, + unsafe_conversion: false, + }), + "rust_non_exhaustive_conversions" => Ok(EnumVariation::Rust { + non_exhaustive: true, + safe_conversion: true, + unsafe_conversion: true, + }), + "rust_non_exhaustive_safe_conversion" => Ok(EnumVariation::Rust { + non_exhaustive: true, + safe_conversion: true, + unsafe_conversion: false, }), + "rust_non_exhaustive_unsafe_conversion" => { + Ok(EnumVariation::Rust { + non_exhaustive: true, + safe_conversion: false, + unsafe_conversion: true, + }) + } "bitfield" => Ok(EnumVariation::NewType { is_bitfield: true, is_global: false, @@ -3270,8 +3321,14 @@ enum EnumBuilder<'a> { Rust { attrs: Vec, ident: Ident, - tokens: proc_macro2::TokenStream, + typedef: Option, + typedef_tokens: proc_macro2::TokenStream, + const_tokens: Vec, + rustified_tokens: Vec, + safe_conversion_tokens: Vec, emitted_any_variants: bool, + safe_conversion: bool, + unsafe_conversion: bool, }, NewType { canonical_name: &'a str, @@ -3299,7 +3356,7 @@ impl<'a> EnumBuilder<'a> { fn new( name: &'a str, mut attrs: Vec, - repr: syn::Type, + repr: EnumRepr, enum_variation: EnumVariation, has_typedef: bool, ) -> Self { @@ -3309,29 +3366,73 @@ impl<'a> EnumBuilder<'a> { EnumVariation::NewType { is_bitfield, is_global, - } => EnumBuilder::NewType { - canonical_name: name, - tokens: quote! { - #( #attrs )* - pub struct #ident (pub #repr); - }, - is_bitfield, - is_global, - }, + } => { + let repr = match repr { + EnumRepr::Other(r) => r, + _ => panic!( + "Should never get this variant for new type enum" + ), + }; + EnumBuilder::NewType { + canonical_name: name, + tokens: quote! { + #( #attrs )* + pub struct #ident (pub #repr); + }, + is_bitfield, + is_global, + } + } - EnumVariation::Rust { .. } => { + EnumVariation::Rust { + safe_conversion, + unsafe_conversion, + .. + } => { + let (untranslated_repr, translated_repr) = match repr { + EnumRepr::Rust(un_r, t_r) => (un_r, t_r), + _ => panic!( + "Should never get this variant for rustified enum" + ), + }; + let ctype = if safe_conversion || unsafe_conversion { + Some(Ident::new( + format!("{}_ctype", ident.to_string()).as_str(), + Span::call_site(), + )) + } else { + None + }; // `repr` is guaranteed to be Rustified in Enum::codegen - attrs.insert(0, quote! { #[repr( #repr )] }); - let tokens = quote!(); + attrs.insert(0, quote! { #[repr( #translated_repr)] }); EnumBuilder::Rust { attrs, + const_tokens: vec![], + typedef_tokens: if ctype.is_some() { + quote! { + pub type #ctype = #untranslated_repr; + } + } else { + quote!() + }, ident, - tokens, + typedef: ctype, + rustified_tokens: vec![], + safe_conversion_tokens: vec![], emitted_any_variants: false, + safe_conversion, + unsafe_conversion, } } EnumVariation::Consts => { + let repr = match repr { + EnumRepr::Other(r) => r, + _ => { + panic!("Should never get this variant for consts enum") + } + }; + let mut variants = Vec::new(); if !has_typedef { @@ -3345,6 +3446,13 @@ impl<'a> EnumBuilder<'a> { } EnumVariation::ModuleConsts => { + let repr = match repr { + EnumRepr::Other(r) => r, + _ => { + panic!("Should never get this variant for module consts enum") + } + }; + let ident = Ident::new( CONSTIFIED_ENUM_MODULE_REPR_NAME, Span::call_site(), @@ -3364,7 +3472,7 @@ impl<'a> EnumBuilder<'a> { /// Add a variant to this enum. fn with_variant( - self, + mut self, ctx: &BindgenContext, variant: &EnumVariant, mangling_prefix: Option<&str>, @@ -3374,13 +3482,17 @@ impl<'a> EnumBuilder<'a> { ) -> Self { let variant_name = ctx.rust_mangle(variant.name()); let is_rust_enum = self.is_rust_enum(); - let expr = match variant.val() { + let (is_bool, expr) = match variant.val() { EnumVariantValue::Boolean(v) if is_rust_enum => { - helpers::ast_ty::uint_expr(u64::from(v)) + (true, helpers::ast_ty::uint_expr(u64::from(v))) + } + EnumVariantValue::Boolean(v) => (true, quote!(#v)), + EnumVariantValue::Signed(v) => { + (false, helpers::ast_ty::int_expr(v)) + } + EnumVariantValue::Unsigned(v) => { + (false, helpers::ast_ty::uint_expr(v)) } - EnumVariantValue::Boolean(v) => quote!(#v), - EnumVariantValue::Signed(v) => helpers::ast_ty::int_expr(v), - EnumVariantValue::Unsigned(v) => helpers::ast_ty::uint_expr(v), }; let mut doc = quote! {}; @@ -3393,22 +3505,43 @@ impl<'a> EnumBuilder<'a> { match self { EnumBuilder::Rust { - attrs, - ident, - tokens, - emitted_any_variants: _, + ref ident, + ref typedef, + ref mut const_tokens, + ref mut rustified_tokens, + ref mut safe_conversion_tokens, + ref mut emitted_any_variants, + safe_conversion, + unsafe_conversion, + .. } => { let name = ctx.rust_ident(variant_name); - EnumBuilder::Rust { - attrs, - ident, - tokens: quote! { - #tokens + if safe_conversion || unsafe_conversion { + let const_name = Ident::new( + name.to_string().as_str(), + Span::call_site(), + ); + let add_cmp = if is_bool { + quote! { != 0 } + } else { + quote!() + }; + const_tokens.push(quote! { #doc - #name = #expr, - }, - emitted_any_variants: true, + pub const #const_name: #typedef = #expr #add_cmp; + }); } + rustified_tokens.push(quote! { + #doc + #name = #expr , + }); + safe_conversion_tokens.push(quote! { + #expr => Ok(#ident::#name) , + }); + + *emitted_any_variants = true; + + self } EnumBuilder::NewType { @@ -3487,21 +3620,87 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::Rust { attrs, ident, - tokens, + typedef, + typedef_tokens, + const_tokens, + rustified_tokens, + safe_conversion_tokens, emitted_any_variants, + safe_conversion, + unsafe_conversion, .. } => { - let variants = if emitted_any_variants { - tokens + let variants = if !emitted_any_variants { + vec![quote!(__bindgen_cannot_repr_c_on_empty_enum = 0)] + } else { + rustified_tokens + }; + + let consts = if safe_conversion || unsafe_conversion { + quote! { + impl #ident { + #( #const_tokens )* + } + } + } else { + quote!() + }; + + let safe_conversion = if safe_conversion { + let prefix = ctx.trait_prefix(); + let error_type = Ident::new( + format!("{}Error", ident.to_string()).as_str(), + Span::call_site(), + ); + quote! { + pub struct #error_type(#typedef); + + impl #error_type { + #[must_use] + pub fn value(&self) -> #typedef { + self.0 + } + } + + impl #prefix::convert::TryFrom<#typedef> for #ident { + type Error = #error_type; + + fn try_from(v: #typedef) -> Result { + match v { + #( #safe_conversion_tokens )* + _ => Err(#error_type(v)) + } + } + } + } } else { - quote!(__bindgen_cannot_repr_c_on_empty_enum = 0) + quote!() + }; + + let unsafe_conversion = if unsafe_conversion { + quote! { + impl #ident { + const unsafe fn from_ctype_unchecked(v: #typedef) -> Self { + std::mem::transmute(v) + } + } + } + } else { + quote!() }; quote! { + #typedef_tokens + #consts + #( #attrs )* pub enum #ident { - #variants + #( #variants )* } + + #safe_conversion + + #unsafe_conversion } } EnumBuilder::NewType { @@ -3576,6 +3775,73 @@ impl<'a> EnumBuilder<'a> { } } +fn handle_translation<'a>( + ctx: &BindgenContext, + layout: Option<&Layout>, + item: &Item, + repr: Option<&Type>, + translate: bool, +) -> syn::Type { + match repr { + Some(repr) + if !ctx.options().translate_enum_integer_types && !translate => + { + repr.to_rust_ty_or_opaque(ctx, item) + } + repr => { + // An enum's integer type is translated to a native Rust + // integer type in 3 cases: + // * the enum is Rustified and we need a translated type for + // the repr attribute + // * the representation couldn't be determined from the C source + // * it was explicitly requested as a bindgen option + + let kind = match repr { + Some(repr) => match *repr.canonical_type(ctx).kind() { + TypeKind::Int(int_kind) => int_kind, + _ => panic!("Unexpected type as enum repr"), + }, + None => { + warn!( + "Guessing type of enum! Forward declarations of enums \ + shouldn't be legal!" + ); + IntKind::Int + } + }; + + let signed = kind.is_signed(); + let size = layout + .map(|l| l.size) + .or_else(|| kind.known_size()) + .unwrap_or(0); + + let translated = match (signed, size) { + (true, 1) => IntKind::I8, + (false, 1) => IntKind::U8, + (true, 2) => IntKind::I16, + (false, 2) => IntKind::U16, + (true, 4) => IntKind::I32, + (false, 4) => IntKind::U32, + (true, 8) => IntKind::I64, + (false, 8) => IntKind::U64, + _ => { + warn!("invalid enum decl: signed: {signed}, size: {size}"); + IntKind::I32 + } + }; + + Type::new(None, None, TypeKind::Int(translated), false) + .to_rust_ty_or_opaque(ctx, item) + } + } +} + +enum EnumRepr { + Rust(syn::Type, syn::Type), + Other(syn::Type), +} + impl CodeGenerator for Enum { type Extra = Item; type Return = (); @@ -3595,69 +3861,37 @@ impl CodeGenerator for Enum { let layout = enum_ty.layout(ctx); let variation = self.computed_enum_variation(ctx, item); - let repr_translated; - let repr = match self.repr().map(|repr| ctx.resolve_type(repr)) { - Some(repr) - if !ctx.options().translate_enum_integer_types && - !variation.is_rust() => - { - repr - } - repr => { - // An enum's integer type is translated to a native Rust - // integer type in 3 cases: - // * the enum is Rustified and we need a translated type for - // the repr attribute - // * the representation couldn't be determined from the C source - // * it was explicitly requested as a bindgen option - - let kind = if let Some(repr) = repr { - match *repr.canonical_type(ctx).kind() { - TypeKind::Int(int_kind) => int_kind, - _ => panic!("Unexpected type as enum repr"), - } - } else { - warn!( - "Guessing type of enum! Forward declarations of enums \ - shouldn't be legal!" - ); - IntKind::Int - }; - - let signed = kind.is_signed(); - let size = layout - .map(|l| l.size) - .or_else(|| kind.known_size()) - .unwrap_or(0); - - let translated = match (signed, size) { - (true, 1) => IntKind::I8, - (false, 1) => IntKind::U8, - (true, 2) => IntKind::I16, - (false, 2) => IntKind::U16, - (true, 4) => IntKind::I32, - (false, 4) => IntKind::U32, - (true, 8) => IntKind::I64, - (false, 8) => IntKind::U64, - _ => { - warn!( - "invalid enum decl: signed: {signed}, size: {size}" - ); - IntKind::I32 - } - }; - - repr_translated = - Type::new(None, None, TypeKind::Int(translated), false); - &repr_translated - } + let repr = if variation.is_rust() { + EnumRepr::Rust( + handle_translation( + ctx, + layout.as_ref(), + item, + self.repr().map(|repr| ctx.resolve_type(repr)), + false, + ), + handle_translation( + ctx, + layout.as_ref(), + item, + self.repr().map(|repr| ctx.resolve_type(repr)), + true, + ), + ) + } else { + EnumRepr::Other(handle_translation( + ctx, + layout.as_ref(), + item, + self.repr().map(|repr| ctx.resolve_type(repr)), + false, + )) }; - let mut attrs = vec![]; // TODO(emilio): Delegate this to the builders? match variation { - EnumVariation::Rust { non_exhaustive } => { + EnumVariation::Rust { non_exhaustive, .. } => { if non_exhaustive && ctx.options().rust_features().non_exhaustive { @@ -3766,7 +4000,6 @@ impl CodeGenerator for Enum { }); } - let repr = repr.to_rust_ty_or_opaque(ctx, item); let has_typedef = ctx.is_enum_typedef_combo(item.id()); let mut builder = diff --git a/bindgen/ir/enum_ty.rs b/bindgen/ir/enum_ty.rs index 9b08da3bce..fb9dad0747 100644 --- a/bindgen/ir/enum_ty.rs +++ b/bindgen/ir/enum_ty.rs @@ -9,6 +9,75 @@ use crate::ir::annotations::Annotations; use crate::parse::ParseError; use crate::regex_set::RegexSet; +use std::fmt::{self, Display}; +use std::ops::Deref; +use std::str::FromStr; + +/// Represents option for rustified enum generation. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum RustEnumOption { + /// Add non-exhaustive attribute to Rust enum. + NonExhaustive, + /// Add safe TryFrom conversion from integer value. + TryFromRaw, + /// Provide an unsafe wrapper for transmute from integer value. + FromRawUnchecked, +} + +impl FromStr for RustEnumOption { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "non_exhaustive" => Ok(Self::NonExhaustive), + "try_from_raw" => Ok(Self::TryFromRaw), + "from_raw_unchecked" => Ok(Self::FromRawUnchecked), + _ => Err(format!( + "Invalid or unknown rustified struct option {:?}", + s + )), + } + } +} + +/// Collection of RustEnumOption values. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RustEnumOptions(Vec); + +impl Deref for RustEnumOptions { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for RustEnumOptions { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(RustEnumOptions( + s.split(",").filter(|s| s != &"").try_fold( + Vec::new(), + |mut vec, opt| { + vec.push(RustEnumOption::from_str(opt)?); + Result::<_, String>::Ok(vec) + }, + )?, + )) + } +} + +impl Display for RustEnumOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RustEnumOption::NonExhaustive => write!(f, "non_exhaustive"), + RustEnumOption::TryFromRaw => write!(f, "try_from_raw"), + RustEnumOption::FromRawUnchecked => write!(f, "from_raw_unchecked"), + } + } +} + /// An enum representing custom handling that can be given to a variant. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum EnumVariantCustomBehavior { @@ -209,22 +278,6 @@ impl Enum { is_bitfield: false, is_global: true, } - } else if self.is_matching_enum( - ctx, - &ctx.options().rustified_enums, - item, - ) { - EnumVariation::Rust { - non_exhaustive: false, - } - } else if self.is_matching_enum( - ctx, - &ctx.options().rustified_non_exhaustive_enums, - item, - ) { - EnumVariation::Rust { - non_exhaustive: true, - } } else if self.is_matching_enum( ctx, &ctx.options().constified_enums, @@ -232,7 +285,22 @@ impl Enum { ) { EnumVariation::Consts } else { - ctx.options().default_enum_style + let matches = ctx + .options() + .rustified_enums + .iter() + .find(|(_, regex)| self.is_matching_enum(ctx, regex, item)); + match matches { + Some((options, _)) => EnumVariation::Rust { + non_exhaustive: options + .contains(&RustEnumOption::NonExhaustive), + safe_conversion: options + .contains(&RustEnumOption::TryFromRaw), + unsafe_conversion: options + .contains(&RustEnumOption::FromRawUnchecked), + }, + None => ctx.options().default_enum_style, + } } } } diff --git a/bindgen/lib.rs b/bindgen/lib.rs index c9b529ac66..2aea26b3e9 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -52,6 +52,7 @@ pub use codegen::{ }; pub use features::{RustEdition, RustTarget, LATEST_STABLE_RUST}; pub use ir::annotations::FieldVisibilityKind; +pub use ir::enum_ty::RustEnumOptions; pub use ir::function::Abi; #[cfg(feature = "__cli")] pub use options::cli::builder_from_flags; @@ -455,7 +456,7 @@ impl Builder { impl BindgenOptions { fn build(&mut self) { - const REGEX_SETS_LEN: usize = 29; + const REGEX_SETS_LEN: usize = 27; let regex_sets: [_; REGEX_SETS_LEN] = [ &mut self.blocklisted_types, @@ -474,8 +475,6 @@ impl BindgenOptions { &mut self.constified_enum_modules, &mut self.newtype_enums, &mut self.newtype_global_enums, - &mut self.rustified_enums, - &mut self.rustified_non_exhaustive_enums, &mut self.type_alias, &mut self.new_type_alias, &mut self.new_type_alias_deref, @@ -492,7 +491,9 @@ impl BindgenOptions { let record_matches = self.record_matches; #[cfg(feature = "experimental")] { - let sets_len = REGEX_SETS_LEN + self.abi_overrides.len(); + let sets_len = REGEX_SETS_LEN + + self.abi_overrides.len() + + self.rustified_enums.len(); let names = if self.emit_diagnostics { <[&str; REGEX_SETS_LEN]>::into_iter([ "--blocklist-type", @@ -509,8 +510,6 @@ impl BindgenOptions { "--bitfield-enum", "--newtype-enum", "--newtype-global-enum", - "--rustified-enum", - "--rustified-enum-non-exhaustive", "--constified-enum-module", "--constified-enum", "--type-alias", @@ -526,6 +525,9 @@ impl BindgenOptions { "--must-use", ]) .chain((0..self.abi_overrides.len()).map(|_| "--override-abi")) + .chain( + (0..self.rustified_enums.len()).map(|_| "--rustified-enum"), + ) .map(Some) .collect() } else { @@ -542,6 +544,10 @@ impl BindgenOptions { for regex_set in self.abi_overrides.values_mut().chain(regex_sets) { regex_set.build(record_matches); } + + for regex_set in self.rustified_enums.values_mut() { + regex_set.build(record_matches); + } } /// Update rust target version diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index 8c4c05bc84..700db6da2d 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -9,7 +9,7 @@ use crate::{ regex_set::RegexSet, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation, FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle, - RustTarget, + RustEnumOptions, RustTarget, }; use clap::{ error::{Error, ErrorKind}, @@ -137,6 +137,21 @@ fn parse_custom_attribute( Ok((attributes, regex.to_owned())) } +fn parse_rustified_enum( + rustified_enum: &str, +) -> Result<(RustEnumOptions, String), Error> { + let (regex, options) = match rustified_enum.rsplit_once('=') { + Some((regex, options)) => (regex, options), + None => (rustified_enum, ""), + }; + + let options = options + .parse() + .map_err(|err| Error::raw(ErrorKind::InvalidValue, err))?; + + Ok((options, regex.to_owned())) +} + #[derive(Parser, Debug)] #[clap( about = "Generates Rust bindings from C/C++ headers.", @@ -162,12 +177,11 @@ struct BindgenCommand { /// Mark any enum whose name matches REGEX as a global newtype. #[arg(long, value_name = "REGEX")] newtype_global_enum: Vec, - /// Mark any enum whose name matches REGEX as a Rust enum. - #[arg(long, value_name = "REGEX")] - rustified_enum: Vec, - /// Mark any enum whose name matches REGEX as a non-exhaustive Rust enum. - #[arg(long, value_name = "REGEX")] - rustified_non_exhaustive_enum: Vec, + /// Mark any enum whose name matches the provided regex as a Rust enum. This parameter takes + /// options in the shape REGEX or REGEX=OPTIONS where OPTIONS can be a comma separated list of + /// options from non_exhaustive, try_from_raw, and from_raw_unchecked. + #[arg(long, value_parser = parse_rustified_enum)] + rustified_enum: Vec<(RustEnumOptions, String)>, /// Mark any enum whose name matches REGEX as a series of constants. #[arg(long, value_name = "REGEX")] constified_enum: Vec, @@ -538,7 +552,6 @@ where newtype_enum, newtype_global_enum, rustified_enum, - rustified_non_exhaustive_enum, constified_enum, constified_enum_module, default_macro_constant_type, @@ -840,8 +853,7 @@ where bitfield_enum, newtype_enum, newtype_global_enum, - rustified_enum, - rustified_non_exhaustive_enum, + rustified_enum => |b, (rustified_enum, regex)| b.rustified_enum(rustified_enum, regex), constified_enum, constified_enum_module, default_macro_constant_type, diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index 6bf652d4e1..5652ee37ae 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -20,6 +20,7 @@ use crate::CodegenConfig; use crate::FieldVisibilityKind; use crate::Formatter; use crate::HashMap; +use crate::RustEnumOptions; use crate::DEFAULT_ANON_FIELDS_PREFIX; use std::env; @@ -456,7 +457,7 @@ options! { as_args: "--newtype-global-enum", }, /// `enum`s marked as Rust `enum`s. - rustified_enums: RegexSet { + rustified_enums: HashMap { methods: { regex_option! { /// Mark the given `enum` as a Rust `enum`. @@ -467,13 +468,21 @@ options! { /// **Use this with caution**, creating an instance of a Rust `enum` with an /// invalid value will cause undefined behaviour. To avoid this, use the /// [`Builder::newtype_enum`] style instead. - pub fn rustified_enum>(mut self, arg: T) -> Builder { - self.options.rustified_enums.insert(arg); + pub fn rustified_enum>(mut self, options: RustEnumOptions, arg: T) -> Builder { + self.options.rustified_enums.entry(options).or_default().insert(arg.into()); self } } }, - as_args: "--rustified-enum", + as_args: |overrides, args| { + for (options, set) in overrides { + let options = options.iter().map(|item| item.to_string()).collect::>(); + for item in set.get_items() { + args.push("--rustified-enum".to_owned()); + args.push(format!("{}={}", item, options.join(","))); + } + } + }, }, /// `enum`s marked as non-exhaustive Rust `enum`s. rustified_non_exhaustive_enums: RegexSet {