Skip to content

Support mapping Enums to Result type #3102

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
@@ -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;

@@ -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)
}
53 changes: 53 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
@@ -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,
74 changes: 73 additions & 1 deletion bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
@@ -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]
@@ -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"
@@ -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,
@@ -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>,
@@ -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,
@@ -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! {
@@ -3317,6 +3361,7 @@ impl<'a> EnumBuilder<'a> {
},
is_bitfield,
is_global,
result_error_enum_ident: None,
},

EnumVariation::Rust { .. } => {
@@ -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,
@@ -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();
29 changes: 28 additions & 1 deletion bindgen/ir/enum_ty.rs
Original file line number Diff line number Diff line change
@@ -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)]
@@ -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,
@@ -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,
4 changes: 3 additions & 1 deletion bindgen/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
@@ -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,
@@ -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",
5 changes: 5 additions & 0 deletions bindgen/options/cli.rs
Original file line number Diff line number Diff line change
@@ -162,6 +162,9 @@ struct BindgenCommand {
/// Mark any enum whose name matches REGEX as a global newtype.
#[arg(long, value_name = "REGEX")]
newtype_global_enum: Vec<String>,
/// Mark any enum whose name matches REGEX as a `Result<(), NonZero<Newtype>>`.
#[arg(long, value_name = "REGEX")]
result_error_enum: Vec<String>,
/// Mark any enum whose name matches REGEX as a Rust enum.
#[arg(long, value_name = "REGEX")]
rustified_enum: Vec<String>,
@@ -537,6 +540,7 @@ where
bitfield_enum,
newtype_enum,
newtype_global_enum,
result_error_enum,
rustified_enum,
rustified_non_exhaustive_enum,
constified_enum,
@@ -839,6 +843,7 @@ where
default_enum_style,
bitfield_enum,
newtype_enum,
result_error_enum,
newtype_global_enum,
rustified_enum,
rustified_non_exhaustive_enum,
21 changes: 21 additions & 0 deletions bindgen/options/mod.rs
Original file line number Diff line number Diff line change
@@ -438,6 +438,27 @@ options! {
},
as_args: "--newtype-enum",
},
/// `enum`s marked as `Result<(), ErrorEnum>`.
result_error_enums: RegexSet {
methods: {
regex_option! {
/// Mark the given `enum` as a `Result<(), ErrorEnum>`, where ErrorEnum is a newtupe.
///
/// This means that a NonZero integer newtype will be declared to represent the `enum`
/// type, without the zero variant, and the error variants will be represented as
/// constants inside of this type's `impl` block. The 0 variant of the enum will
/// be mapped to the `Ok(())` variant of the Result.
///
/// Note: Using This API requires --rust-target 1.79 or newer due to the usage
/// of `NonZero`!
pub fn result_error_enum<T: AsRef<str>>(mut self, arg: T) -> Builder {
self.options.result_error_enums.insert(arg);
self
}
}
},
as_args: "--result-error-enum",
},
/// `enum`s marked as global newtypes .
newtype_global_enums: RegexSet {
methods: {