diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs index ad96664bd2..c1015f0c6b 100644 --- a/bindgen-cli/options.rs +++ b/bindgen-cli/options.rs @@ -2,7 +2,8 @@ use bindgen::callbacks::TypeKind; use bindgen::{ builder, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation, FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle, - RegexSet, RustTarget, DEFAULT_ANON_FIELDS_PREFIX, RUST_TARGET_STRINGS, + RegexSet, RustEnumOptions, RustTarget, DEFAULT_ANON_FIELDS_PREFIX, + RUST_TARGET_STRINGS, }; use clap::error::{Error, ErrorKind}; use clap::{CommandFactory, Parser}; @@ -77,6 +78,21 @@ fn parse_abi_override(abi_override: &str) -> Result<(Abi, String), Error> { Ok((abi, 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())) +} + fn parse_custom_derive( custom_derive: &str, ) -> Result<(Vec, String), Error> { @@ -150,12 +166,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, @@ -523,7 +538,6 @@ where newtype_enum, newtype_global_enum, rustified_enum, - rustified_non_exhaustive_enum, constified_enum, constified_enum_module, default_macro_constant_type, @@ -690,12 +704,8 @@ where builder = builder.newtype_global_enum(regex); } - for regex in rustified_enum { - builder = builder.rustified_enum(regex); - } - - for regex in rustified_non_exhaustive_enum { - builder = builder.rustified_non_exhaustive_enum(regex); + for (options, regex) in rustified_enum { + builder = builder.rustified_enum(options, regex); } for regex in constified_enum { diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index 88ba945366..993bdffb50 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..462e2eee4c --- /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, +} +impl TryFromRaw { + pub type TryFromRaw_ctype = ::std::os::raw::c_int; + 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), + _ => TryFromRawError(v), + } + } +} +impl FromRawUnchecked { + pub type FromRawUnchecked_ctype = ::std::os::raw::c_uint; + 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; +} +impl Both { + pub type Both_ctype = ::std::os::raw::c_int; + 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), + _ => 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/expectations/tests/libclang-9/struct_typedef_ns.rs b/bindgen-tests/tests/expectations/tests/libclang-9/struct_typedef_ns.rs index d93a62e746..b836b74a8a 100644 --- a/bindgen-tests/tests/expectations/tests/libclang-9/struct_typedef_ns.rs +++ b/bindgen-tests/tests/expectations/tests/libclang-9/struct_typedef_ns.rs @@ -21,6 +21,8 @@ pub mod root { "Offset of field: typedef_struct::foo", ][::std::mem::offset_of!(typedef_struct, foo) - 0usize]; }; + pub type typedef_enum_ctype = ::std::os::raw::c_uint; + pub const typedef_enum_BAR: typedef_enum_ctype = 1; #[repr(u32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum typedef_enum { @@ -47,6 +49,8 @@ pub mod root { }; pub type typedef_struct = root::_bindgen_mod_id_12::_bindgen_ty_1; pub const _bindgen_mod_id_12_BAR: root::_bindgen_mod_id_12::_bindgen_ty_2 = _bindgen_ty_2::BAR; + pub type _bindgen_ty_2_ctype = ::std::os::raw::c_uint; + pub const _bindgen_ty_2_BAR: _bindgen_ty_2_ctype = 1; #[repr(u32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum _bindgen_ty_2 { 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.swp b/bindgen/codegen/.mod.rs.swp new file mode 100644 index 0000000000..cca18dc6b5 Binary files /dev/null and b/bindgen/codegen/.mod.rs.swp differ diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index c6e3364234..db8089fa77 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -3134,6 +3134,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 { @@ -3165,11 +3171,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", @@ -3198,10 +3213,46 @@ impl std::str::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, @@ -3233,8 +3284,13 @@ enum EnumBuilder<'a> { Rust { attrs: Vec, ident: Ident, - tokens: proc_macro2::TokenStream, + typedef: Option, + 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, @@ -3262,7 +3318,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 { @@ -3272,29 +3328,72 @@ 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: if ctype.is_some() { + vec![quote! { + pub type #ctype = #untranslated_repr; + }] + } else { + vec![] + }, 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 { @@ -3308,6 +3407,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(), @@ -3327,7 +3433,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>, @@ -3337,13 +3443,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(v as u64) + (true, helpers::ast_ty::uint_expr(v as u64)) + } + 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! {}; @@ -3356,22 +3466,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 { @@ -3453,21 +3584,85 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::Rust { attrs, ident, - tokens, + typedef, + const_tokens, + rustified_tokens, + safe_conversion_tokens, emitted_any_variants, + safe_conversion, + unsafe_conversion, .. } => { let variants = if !emitted_any_variants { - quote!(__bindgen_cannot_repr_c_on_empty_enum = 0) + 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 )* + _ => #error_type(v) + } + } + } + } } else { - tokens + 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! { + #consts + #( #attrs )* pub enum #ident { - #variants + #( #variants )* } + + #safe_conversion + + #unsafe_conversion } } EnumBuilder::NewType { @@ -3542,6 +3737,76 @@ impl<'a> EnumBuilder<'a> { } } +fn handle_tranlation<'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: {}, size: {}", + signed, 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 = (); @@ -3561,71 +3826,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 = 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: {}, size: {}", - signed, size - ); - IntKind::I32 - } - }; - - repr_translated = - Type::new(None, None, TypeKind::Int(translated), false); - &repr_translated - } + let repr = if variation.is_rust() { + EnumRepr::Rust( + handle_tranlation( + ctx, + layout.as_ref(), + item, + self.repr().map(|repr| ctx.resolve_type(repr)), + false, + ), + handle_tranlation( + ctx, + layout.as_ref(), + item, + self.repr().map(|repr| ctx.resolve_type(repr)), + true, + ), + ) + } else { + EnumRepr::Other(handle_tranlation( + 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 { @@ -3734,7 +3965,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 70cf0eae88..c080668804 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 1a9932b534..09d83d01a2 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -54,6 +54,7 @@ pub use codegen::{ pub use features::RUST_TARGET_STRINGS; pub use features::{RustTarget, LATEST_STABLE_RUST}; pub use ir::annotations::FieldVisibilityKind; +pub use ir::enum_ty::RustEnumOptions; pub use ir::function::Abi; pub use regex_set::RegexSet; @@ -440,7 +441,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, @@ -459,8 +460,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, @@ -477,7 +476,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", @@ -494,8 +495,6 @@ impl BindgenOptions { "--bitfield-enum", "--newtype-enum", "--newtype-global-enum", - "--rustified-enum", - "--rustified-enum-non-exhaustive", "--constified-enum-module", "--constified-enum", "--type-alias", @@ -511,6 +510,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 { @@ -527,6 +529,9 @@ 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); + } let rust_target = self.rust_target; #[allow(deprecated)] diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index e9f4fb811c..080cbc378d 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -18,6 +18,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; @@ -454,7 +455,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`. @@ -465,13 +466,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 {