Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mapping Enums to Result type #3102

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions bindgen-tests/tests/expectations/tests/result-error-enum.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions bindgen-tests/tests/headers/parsecb-result-error-enum-name.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// bindgen-flags: --result-error-enum MyResult --rust-target 1.79
// bindgen-parse-callbacks: result-error-enum-rename

enum MyResult {
MyResultOk = 0,
MyResultInvalid,
MyResultAnotherError,
};

typedef enum MyResult AnotherResult;

enum MyResult some_function(void);

AnotherResult another_function(void);
13 changes: 13 additions & 0 deletions bindgen-tests/tests/headers/result-error-enum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// bindgen-flags: --result-error-enum "MyResult" --rust-target 1.79

enum MyResult {
MyResultOk = 0,
MyResultErr1,
MyResultErr2,
};

typedef enum MyResult ResultTypedef;

enum MyResult do_something(void);

ResultTypedef do_something2(void);
15 changes: 15 additions & 0 deletions bindgen-tests/tests/parse_callbacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ impl ParseCallbacks for EnumVariantRename {
}
}

#[derive(Debug)]
struct ResultErrorEnumRename;

impl ParseCallbacks for ResultErrorEnumRename {
fn result_error_enum_name(
&self,
original_enum_name: &str,
) -> Option<String> {
original_enum_name
.strip_suffix("Result")
.map(|base| format!("{base}Error"))
}
}

#[derive(Debug)]
struct BlocklistedTypeImplementsTrait;

Expand Down Expand Up @@ -149,6 +163,7 @@ impl ParseCallbacks for WrapAsVariadicFn {
pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
match cb {
"enum-variant-rename" => Box::new(EnumVariantRename),
"result-error-enum-rename" => Box::new(ResultErrorEnumRename),
"blocklisted-type-implements-trait" => {
Box::new(BlocklistedTypeImplementsTrait)
}
Expand Down
53 changes: 53 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,59 @@ pub trait ParseCallbacks: fmt::Debug {
None
}

/// Allows to rename the error enum name for a result-error-enum
///
/// When the original C-enum is translated to a `Result<(), NonZero<ErrorEnum>>` representation,
/// the original enum name is used as a type alias for the result.
/// The error enum hence must have a different name. By default, we simply append an `Error` to
/// the typename, but this callback allows overriding the name for the error enum.
/// Please note that the new enum name returned by this callback must be distinct from the
/// original enum name.
///
/// ### Example
///
/// Given a header file like this:
/// ```c
/// enum MyResult {
/// MyResultOk = 0,
/// MyResultInvalid,
/// MyResultAnotherError,
/// };
/// ```
/// A user could the implement the following callback:
/// ```rust
/// # use bindgen::callbacks::ParseCallbacks;
/// #[derive(Debug)]
/// struct ResultErrorEnumRename;
///
/// impl ParseCallbacks for ResultErrorEnumRename {
/// fn result_error_enum_name(
/// &self,
/// original_enum_name: &str,
/// ) -> Option<String> {
/// original_enum_name
/// .strip_suffix("Result")
/// .map(|base| format!("{base}Error"))
/// }
/// }
/// ```
///
/// The above callback would result in the following rust binding:
///
/// ```ignore
/// pub type MyResult = Result<(), MyError>;
/// #[repr(transparent)]
/// #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
/// pub struct MyError(pub core::num::NonZero<::std::os::raw::c_uint>);
/// // <Definition of MyError variants>
/// ```
fn result_error_enum_name(
&self,
_original_enum_name: &str,
) -> Option<String> {
None
}

/// Allows to rename an enum variant, replacing `_original_variant_name`.
fn enum_variant_name(
&self,
Expand Down
74 changes: 73 additions & 1 deletion bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3178,6 +3178,9 @@ pub enum EnumVariation {
is_bitfield: bool,
/// Indicates whether the variants will be represented as global constants
is_global: bool,
/// Indicates whether this enum is a result type, where 0 indicates success.
/// The enum will then be a NonZero type, and usages wrapped in Result.
is_result_type: bool,
},
/// The code for this enum will use consts
#[default]
Expand Down Expand Up @@ -3210,9 +3213,15 @@ impl fmt::Display for EnumVariation {
Self::NewType {
is_bitfield: true, ..
} => "bitfield",
Self::NewType {
is_bitfield: false,
is_global: false,
is_result_type: true,
} => "result_error_enum",
Self::NewType {
is_bitfield: false,
is_global,
..
} => {
if *is_global {
"newtype_global"
Expand Down Expand Up @@ -3242,16 +3251,24 @@ impl FromStr for EnumVariation {
"bitfield" => Ok(EnumVariation::NewType {
is_bitfield: true,
is_global: false,
is_result_type: false,
}),
"consts" => Ok(EnumVariation::Consts),
"moduleconsts" => Ok(EnumVariation::ModuleConsts),
"newtype" => Ok(EnumVariation::NewType {
is_bitfield: false,
is_global: false,
is_result_type: false,
}),
"newtype_global" => Ok(EnumVariation::NewType {
is_bitfield: false,
is_global: true,
is_result_type: false,
}),
"result_error_enum" => Ok(EnumVariation::NewType {
is_bitfield: false,
is_global: false,
is_result_type: true,
}),
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
Expand All @@ -3278,6 +3295,7 @@ enum EnumBuilder<'a> {
tokens: proc_macro2::TokenStream,
is_bitfield: bool,
is_global: bool,
result_error_enum_ident: Option<Ident>,
},
Consts {
variants: Vec<proc_macro2::TokenStream>,
Expand All @@ -3298,6 +3316,7 @@ impl<'a> EnumBuilder<'a> {
/// the representation, and which variation it should be generated as.
fn new(
name: &'a str,
ctx: &BindgenContext,
mut attrs: Vec<proc_macro2::TokenStream>,
repr: syn::Type,
enum_variation: EnumVariation,
Expand All @@ -3309,6 +3328,31 @@ impl<'a> EnumBuilder<'a> {
EnumVariation::NewType {
is_bitfield,
is_global,
is_result_type: true,
} => {
let error_enum_name = ctx
.options()
.last_callback(|c| c.result_error_enum_name(&name))
.unwrap_or(format!("{name}Error"));
let error_ident =
Ident::new(&error_enum_name, Span::call_site());
EnumBuilder::NewType {
canonical_name: name,
tokens: quote! {
pub type #ident = Result<(), #error_ident>;

#( #attrs )*
pub struct #error_ident (pub core::num::NonZero<#repr>);
},
is_bitfield,
is_global,
result_error_enum_ident: Some(error_ident),
}
}
EnumVariation::NewType {
is_bitfield,
is_global,
..
} => EnumBuilder::NewType {
canonical_name: name,
tokens: quote! {
Expand All @@ -3317,6 +3361,7 @@ impl<'a> EnumBuilder<'a> {
},
is_bitfield,
is_global,
result_error_enum_ident: None,
},

EnumVariation::Rust { .. } => {
Expand Down Expand Up @@ -3411,6 +3456,33 @@ impl<'a> EnumBuilder<'a> {
}
}

EnumBuilder::NewType {
result_error_enum_ident: Some(ref enum_ident),
..
} => {
assert!(is_ty_named);
if matches!(
variant.val(),
EnumVariantValue::Signed(0) | EnumVariantValue::Unsigned(0)
) {
return self;
}

let variant_ident = ctx.rust_ident(variant_name);
// Wrapping the unwrap in the const block ensures we get
// a compile-time panic.
let expr = quote! { const { core::num::NonZero::new(#expr).unwrap() } };

result.push(quote! {
impl #enum_ident {
#doc
pub const #variant_ident : #enum_ident = #enum_ident ( #expr );
}
});

self
}

EnumBuilder::NewType {
canonical_name,
is_global,
Expand Down Expand Up @@ -3770,7 +3842,7 @@ impl CodeGenerator for Enum {
let has_typedef = ctx.is_enum_typedef_combo(item.id());

let mut builder =
EnumBuilder::new(&name, attrs, repr, variation, has_typedef);
EnumBuilder::new(&name, ctx, attrs, repr, variation, has_typedef);

// A map where we keep a value -> variant relation.
let mut seen_values = HashMap::<_, Ident>::default();
Expand Down
29 changes: 28 additions & 1 deletion bindgen/ir/enum_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use super::super::codegen::EnumVariation;
use super::context::{BindgenContext, TypeId};
use super::item::Item;
use super::ty::{Type, TypeKind};
use crate::clang;
use crate::ir::annotations::Annotations;
use crate::parse::ParseError;
use crate::regex_set::RegexSet;
use crate::{clang, RustTarget};
use std::str::FromStr;

/// An enum representing custom handling that can be given to a variant.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -193,12 +194,37 @@ impl Enum {
EnumVariation::NewType {
is_bitfield: true,
is_global: false,
is_result_type: false,
}
} else if self.is_matching_enum(
ctx,
&ctx.options().result_error_enums,
item,
) && ctx
.options()
.rust_target
.ge(&RustTarget::from_str("1.79").unwrap())
{
let zero_variant = self.variants.iter().find(|x| {
matches!(
x.val,
EnumVariantValue::Signed(0) | EnumVariantValue::Unsigned(0)
)
});
if zero_variant.is_none() {
panic!("Result-error-enum must have a zero variant. (Item: {item:?})");
}
EnumVariation::NewType {
is_bitfield: false,
is_global: false,
is_result_type: true,
}
} else if self.is_matching_enum(ctx, &ctx.options().newtype_enums, item)
{
EnumVariation::NewType {
is_bitfield: false,
is_global: false,
is_result_type: false,
}
} else if self.is_matching_enum(
ctx,
Expand All @@ -208,6 +234,7 @@ impl Enum {
EnumVariation::NewType {
is_bitfield: false,
is_global: true,
is_result_type: false,
}
} else if self.is_matching_enum(
ctx,
Expand Down
4 changes: 3 additions & 1 deletion bindgen/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ impl Builder {

impl BindgenOptions {
fn build(&mut self) {
const REGEX_SETS_LEN: usize = 29;
const REGEX_SETS_LEN: usize = 30;

let regex_sets: [_; REGEX_SETS_LEN] = [
&mut self.blocklisted_types,
Expand All @@ -476,6 +476,7 @@ impl BindgenOptions {
&mut self.constified_enum_modules,
&mut self.newtype_enums,
&mut self.newtype_global_enums,
&mut self.result_error_enums,
&mut self.rustified_enums,
&mut self.rustified_non_exhaustive_enums,
&mut self.type_alias,
Expand Down Expand Up @@ -511,6 +512,7 @@ impl BindgenOptions {
"--bitfield-enum",
"--newtype-enum",
"--newtype-global-enum",
"--result-error-enum",
"--rustified-enum",
"--rustified-enum-non-exhaustive",
"--constified-enum-module",
Expand Down
Loading