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/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 b/bindgen/codegen/mod.rs index 403ac42839..908a410642 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -429,21 +429,21 @@ impl WithImplicitTemplateParams for syn::Type { unreachable!("we resolved item through type refs") } // None of these types ever have implicit template parameters. - TypeKind::Void | - TypeKind::NullPtr | - TypeKind::Pointer(..) | - TypeKind::Reference(..) | - TypeKind::Int(..) | - TypeKind::Float(..) | - TypeKind::Complex(..) | - TypeKind::Array(..) | - TypeKind::TypeParam | - TypeKind::Opaque | - TypeKind::Function(..) | - TypeKind::Enum(..) | - TypeKind::ObjCId | - TypeKind::ObjCSel | - TypeKind::TemplateInstantiation(..) => None, + TypeKind::Void + | TypeKind::NullPtr + | TypeKind::Pointer(..) + | TypeKind::Reference(..) + | TypeKind::Int(..) + | TypeKind::Float(..) + | TypeKind::Complex(..) + | TypeKind::Array(..) + | TypeKind::TypeParam + | TypeKind::Opaque + | TypeKind::Function(..) + | TypeKind::Enum(..) + | TypeKind::ObjCId + | TypeKind::ObjCSel + | TypeKind::TemplateInstantiation(..) => None, _ => { let params = item.used_template_params(ctx); if params.is_empty() { @@ -593,9 +593,9 @@ impl CodeGenerator for Module { } }; - if !ctx.options().enable_cxx_namespaces || - (self.is_inline() && - !ctx.options().conservative_inline_namespaces) + if !ctx.options().enable_cxx_namespaces + || (self.is_inline() + && !ctx.options().conservative_inline_namespaces) { codegen_self(result, &mut false); return; @@ -843,19 +843,19 @@ impl CodeGenerator for Type { debug_assert!(item.is_enabled_for_codegen(ctx)); match *self.kind() { - TypeKind::Void | - TypeKind::NullPtr | - TypeKind::Int(..) | - TypeKind::Float(..) | - TypeKind::Complex(..) | - TypeKind::Array(..) | - TypeKind::Vector(..) | - TypeKind::Pointer(..) | - TypeKind::Reference(..) | - TypeKind::Function(..) | - TypeKind::ResolvedTypeRef(..) | - TypeKind::Opaque | - TypeKind::TypeParam => { + TypeKind::Void + | TypeKind::NullPtr + | TypeKind::Int(..) + | TypeKind::Float(..) + | TypeKind::Complex(..) + | TypeKind::Array(..) + | TypeKind::Vector(..) + | TypeKind::Pointer(..) + | TypeKind::Reference(..) + | TypeKind::Function(..) + | TypeKind::ResolvedTypeRef(..) + | TypeKind::Opaque + | TypeKind::TypeParam => { // These items don't need code generation, they only need to be // converted to rust types in fields, arguments, and such. // NOTE(emilio): If you add to this list, make sure to also add @@ -1023,11 +1023,11 @@ impl CodeGenerator for Type { // We prefer using `pub use` over `pub type` because of: // https://github.com/rust-lang/rust/issues/26264 - if matches!(inner_rust_type, syn::Type::Path(_)) && - outer_params.is_empty() && - !is_opaque && - alias_style == AliasVariation::TypeAlias && - inner_item.expect_type().canonical_type(ctx).is_enum() + if matches!(inner_rust_type, syn::Type::Path(_)) + && outer_params.is_empty() + && !is_opaque + && alias_style == AliasVariation::TypeAlias + && inner_item.expect_type().canonical_type(ctx).is_enum() { tokens.append_all(quote! { pub use @@ -1205,9 +1205,9 @@ impl CodeGenerator for Vtable<'_> { // For now, we will only generate vtables for classes that: // - do not inherit from others (compilers merge VTable from primary parent class). // - do not contain a virtual destructor (requires ordering; platforms generate different vtables). - if ctx.options().vtable_generation && - self.comp_info.base_members().is_empty() && - self.comp_info.destructor().is_none() + if ctx.options().vtable_generation + && self.comp_info.base_members().is_empty() + && self.comp_info.destructor().is_none() { let class_ident = ctx.rust_ident(self.item_id.canonical_name(ctx)); @@ -1812,8 +1812,8 @@ impl FieldCodegen<'_> for BitfieldUnit { continue; } - if layout.size > RUST_DERIVE_IN_ARRAY_LIMIT && - !ctx.options().rust_features().larger_arrays + if layout.size > RUST_DERIVE_IN_ARRAY_LIMIT + && !ctx.options().rust_features().larger_arrays { continue; } @@ -2414,10 +2414,10 @@ impl CodeGenerator for CompInfo { // if a type has both a "packed" attribute and an "align(N)" attribute, then check if the // "packed" attr is redundant, and do not include it if so. - if packed && - !is_opaque && - !(explicit_align.is_some() && - self.already_packed(ctx).unwrap_or(false)) + if packed + && !is_opaque + && !(explicit_align.is_some() + && self.already_packed(ctx).unwrap_or(false)) { let n = layout.map_or(1, |l| l.align); assert!(ctx.options().rust_features().repr_packed_n || n == 1); @@ -2444,32 +2444,32 @@ impl CodeGenerator for CompInfo { let derivable_traits = derives_of_item(item, ctx, packed); if !derivable_traits.contains(DerivableTraits::DEBUG) { - needs_debug_impl = ctx.options().derive_debug && - ctx.options().impl_debug && - !ctx.no_debug_by_name(item) && - !item.annotations().disallow_debug(); + needs_debug_impl = ctx.options().derive_debug + && ctx.options().impl_debug + && !ctx.no_debug_by_name(item) + && !item.annotations().disallow_debug(); } if !derivable_traits.contains(DerivableTraits::DEFAULT) { - needs_default_impl = ctx.options().derive_default && - !self.is_forward_declaration() && - !ctx.no_default_by_name(item) && - !item.annotations().disallow_default(); + needs_default_impl = ctx.options().derive_default + && !self.is_forward_declaration() + && !ctx.no_default_by_name(item) + && !item.annotations().disallow_default(); } let all_template_params = item.all_template_params(ctx); - if derivable_traits.contains(DerivableTraits::COPY) && - !derivable_traits.contains(DerivableTraits::CLONE) + if derivable_traits.contains(DerivableTraits::COPY) + && !derivable_traits.contains(DerivableTraits::CLONE) { needs_clone_impl = true; } if !derivable_traits.contains(DerivableTraits::PARTIAL_EQ) { - needs_partialeq_impl = ctx.options().derive_partialeq && - ctx.options().impl_partialeq && - ctx.lookup_can_derive_partialeq_or_partialord(item.id()) == - CanDerive::Manually; + needs_partialeq_impl = ctx.options().derive_partialeq + && ctx.options().impl_partialeq + && ctx.lookup_can_derive_partialeq_or_partialord(item.id()) + == CanDerive::Manually; } let mut derives: Vec<_> = derivable_traits.into(); @@ -2661,8 +2661,8 @@ impl CodeGenerator for CompInfo { .collect() }; - let uninit_decl = if check_field_offset.is_empty() || - compile_time + let uninit_decl = if check_field_offset.is_empty() + || compile_time { None } else { @@ -3005,9 +3005,9 @@ impl Method { MethodKind::Constructor => cc.constructors(), MethodKind::Destructor => cc.destructors(), MethodKind::VirtualDestructor { .. } => cc.destructors(), - MethodKind::Static | - MethodKind::Normal | - MethodKind::Virtual { .. } => cc.methods(), + MethodKind::Static + | MethodKind::Normal + | MethodKind::Virtual { .. } => cc.methods(), } }); @@ -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 { - quote!(__bindgen_cannot_repr_c_on_empty_enum = 0) + 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!() + }; + + 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,75 +3861,43 @@ 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 } => { - if non_exhaustive && - ctx.options().rust_features().non_exhaustive + EnumVariation::Rust { non_exhaustive, .. } => { + if non_exhaustive + && ctx.options().rust_features().non_exhaustive { attrs.push(attributes::non_exhaustive()); - } else if non_exhaustive && - !ctx.options().rust_features().non_exhaustive + } else if non_exhaustive + && !ctx.options().rust_features().non_exhaustive { panic!("The rust target you're using doesn't seem to support non_exhaustive enums"); } @@ -3693,10 +3927,10 @@ impl CodeGenerator for Enum { // Clone/Eq/PartialEq/Hash, even if we don't generate those by // default. derives.insert( - DerivableTraits::CLONE | - DerivableTraits::HASH | - DerivableTraits::PARTIAL_EQ | - DerivableTraits::EQ, + DerivableTraits::CLONE + | DerivableTraits::HASH + | DerivableTraits::PARTIAL_EQ + | DerivableTraits::EQ, ); let mut derives: Vec<_> = derives.into(); for derive in item.annotations().derives() { @@ -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 = @@ -3816,8 +4049,8 @@ impl CodeGenerator for Enum { Entry::Occupied(ref entry) => { if variation.is_rust() { let variant_name = ctx.rust_mangle(variant.name()); - let mangled_name = if is_toplevel || - enum_ty.name().is_some() + let mangled_name = if is_toplevel + || enum_ty.name().is_some() { variant_name } else { @@ -3876,8 +4109,8 @@ impl CodeGenerator for Enum { // If it's an unnamed enum, or constification is enforced, // we also generate a constant so it can be properly // accessed. - if (variation.is_rust() && enum_ty.name().is_none()) || - variant.force_constification() + if (variation.is_rust() && enum_ty.name().is_none()) + || variant.force_constification() { let mangled_name = if is_toplevel { variant_name.clone() @@ -4314,16 +4547,17 @@ impl TryToRustTy for Type { inst.try_to_rust_ty(ctx, item) } TypeKind::ResolvedTypeRef(inner) => inner.try_to_rust_ty(ctx, &()), - TypeKind::TemplateAlias(..) | - TypeKind::Alias(..) | - TypeKind::BlockPointer(..) => { + TypeKind::TemplateAlias(..) + | TypeKind::Alias(..) + | TypeKind::BlockPointer(..) => { if self.is_block_pointer() && !ctx.options().generate_block { let void = c_void(ctx); return Ok(void.to_ptr(/* is_const = */ false)); } - if item.is_opaque(ctx, &()) && - item.used_template_params(ctx) + if item.is_opaque(ctx, &()) + && item + .used_template_params(ctx) .into_iter() .any(|param| param.is_template_param(ctx, &())) { @@ -4339,8 +4573,8 @@ impl TryToRustTy for Type { } TypeKind::Comp(ref info) => { let template_params = item.all_template_params(ctx); - if info.has_non_type_template_params() || - (item.is_opaque(ctx, &()) && !template_params.is_empty()) + if info.has_non_type_template_params() + || (item.is_opaque(ctx, &()) && !template_params.is_empty()) { return self.try_to_opaque(ctx, item); } @@ -4647,8 +4881,8 @@ impl CodeGenerator for Function { has_link_name_attr = true; } else { let link_name = mangled_name.unwrap_or(name); - if !is_dynamic_function && - !utils::names_will_be_identical_after_mangling( + if !is_dynamic_function + && !utils::names_will_be_identical_after_mangling( &canonical_name, link_name, Some(abi), @@ -5208,8 +5442,8 @@ pub(crate) mod utils { std::fs::create_dir_all(dir)?; } - let is_cpp = args_are_cpp(&context.options().clang_args) || - context + let is_cpp = args_are_cpp(&context.options().clang_args) + || context .options() .input_headers .iter() @@ -5307,8 +5541,8 @@ pub(crate) mod utils { ctx: &BindgenContext, result: &mut Vec, ) { - if ctx.options().blocklisted_items.matches(BITFIELD_UNIT) || - ctx.options().blocklisted_types.matches(BITFIELD_UNIT) + if ctx.options().blocklisted_items.matches(BITFIELD_UNIT) + || ctx.options().blocklisted_types.matches(BITFIELD_UNIT) { return; } 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 9d5cea3dc6..2f3a710522 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, @@ -836,8 +849,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 {