Skip to content

Commit 9930921

Browse files
committed
Support mapping Enums to Result type
Some enums, where the Ok variant is 0, can be mapped to a Rust Result, with a NonZero<NewType> enum representation. Future improvements could add a callback which allows customizing the name error enum.
1 parent b7c7964 commit 9930921

File tree

7 files changed

+159
-2
lines changed

7 files changed

+159
-2
lines changed

bindgen-tests/tests/expectations/tests/result-error-enum.rs

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// bindgen-flags: --result-error-enum "MyResult" --rust-target 1.79
2+
3+
enum MyResult {
4+
MyResultOk = 0,
5+
MyResultErr1,
6+
MyResultErr2,
7+
};
8+
9+
typedef enum MyResult ResultTypedef;
10+
11+
enum MyResult do_something(void);
12+
13+
ResultTypedef do_something2(void);

bindgen/codegen/mod.rs

+67
Original file line numberDiff line numberDiff line change
@@ -3178,6 +3178,9 @@ pub enum EnumVariation {
31783178
is_bitfield: bool,
31793179
/// Indicates whether the variants will be represented as global constants
31803180
is_global: bool,
3181+
/// Indicates whether this enum is a result type, where 0 indicates success.
3182+
/// The enum will then be a NonZero type, and usages wrapped in Result.
3183+
is_result_type: bool,
31813184
},
31823185
/// The code for this enum will use consts
31833186
#[default]
@@ -3210,9 +3213,15 @@ impl fmt::Display for EnumVariation {
32103213
Self::NewType {
32113214
is_bitfield: true, ..
32123215
} => "bitfield",
3216+
Self::NewType {
3217+
is_bitfield: false,
3218+
is_global: false,
3219+
is_result_type: true,
3220+
} => "result_error_enum",
32133221
Self::NewType {
32143222
is_bitfield: false,
32153223
is_global,
3224+
..
32163225
} => {
32173226
if *is_global {
32183227
"newtype_global"
@@ -3242,16 +3251,24 @@ impl FromStr for EnumVariation {
32423251
"bitfield" => Ok(EnumVariation::NewType {
32433252
is_bitfield: true,
32443253
is_global: false,
3254+
is_result_type: false,
32453255
}),
32463256
"consts" => Ok(EnumVariation::Consts),
32473257
"moduleconsts" => Ok(EnumVariation::ModuleConsts),
32483258
"newtype" => Ok(EnumVariation::NewType {
32493259
is_bitfield: false,
32503260
is_global: false,
3261+
is_result_type: false,
32513262
}),
32523263
"newtype_global" => Ok(EnumVariation::NewType {
32533264
is_bitfield: false,
32543265
is_global: true,
3266+
is_result_type: false,
3267+
}),
3268+
"result_error_enum" => Ok(EnumVariation::NewType {
3269+
is_bitfield: false,
3270+
is_global: false,
3271+
is_result_type: true,
32553272
}),
32563273
_ => Err(std::io::Error::new(
32573274
std::io::ErrorKind::InvalidInput,
@@ -3278,6 +3295,7 @@ enum EnumBuilder<'a> {
32783295
tokens: proc_macro2::TokenStream,
32793296
is_bitfield: bool,
32803297
is_global: bool,
3298+
result_error_enum_ident: Option<Ident>,
32813299
},
32823300
Consts {
32833301
variants: Vec<proc_macro2::TokenStream>,
@@ -3309,6 +3327,27 @@ impl<'a> EnumBuilder<'a> {
33093327
EnumVariation::NewType {
33103328
is_bitfield,
33113329
is_global,
3330+
is_result_type: true,
3331+
} => {
3332+
// Todo: This identifier perhaps could be determined by a ParseCallback.
3333+
let error_ident = format_ident!("{}Error", ident);
3334+
EnumBuilder::NewType {
3335+
canonical_name: name,
3336+
tokens: quote! {
3337+
pub type #ident = Result<(), #error_ident>;
3338+
3339+
#( #attrs )*
3340+
pub struct #error_ident (pub core::num::NonZero<#repr>);
3341+
},
3342+
is_bitfield,
3343+
is_global,
3344+
result_error_enum_ident: Some(error_ident),
3345+
}
3346+
}
3347+
EnumVariation::NewType {
3348+
is_bitfield,
3349+
is_global,
3350+
..
33123351
} => EnumBuilder::NewType {
33133352
canonical_name: name,
33143353
tokens: quote! {
@@ -3317,6 +3356,7 @@ impl<'a> EnumBuilder<'a> {
33173356
},
33183357
is_bitfield,
33193358
is_global,
3359+
result_error_enum_ident: None,
33203360
},
33213361

33223362
EnumVariation::Rust { .. } => {
@@ -3411,6 +3451,33 @@ impl<'a> EnumBuilder<'a> {
34113451
}
34123452
}
34133453

3454+
EnumBuilder::NewType {
3455+
result_error_enum_ident: Some(ref enum_ident),
3456+
..
3457+
} => {
3458+
assert!(is_ty_named);
3459+
if matches!(
3460+
variant.val(),
3461+
EnumVariantValue::Signed(0) | EnumVariantValue::Unsigned(0)
3462+
) {
3463+
return self;
3464+
}
3465+
3466+
let variant_ident = ctx.rust_ident(variant_name);
3467+
// Wrapping the unwrap in the const block ensures we get
3468+
// a compile-time panic.
3469+
let expr = quote! { const { core::num::NonZero::new(#expr).unwrap() } };
3470+
3471+
result.push(quote! {
3472+
impl #enum_ident {
3473+
#doc
3474+
pub const #variant_ident : #enum_ident = #enum_ident ( #expr );
3475+
}
3476+
});
3477+
3478+
self
3479+
}
3480+
34143481
EnumBuilder::NewType {
34153482
canonical_name,
34163483
is_global,

bindgen/ir/enum_ty.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use super::super::codegen::EnumVariation;
44
use super::context::{BindgenContext, TypeId};
55
use super::item::Item;
66
use super::ty::{Type, TypeKind};
7-
use crate::clang;
87
use crate::ir::annotations::Annotations;
98
use crate::parse::ParseError;
109
use crate::regex_set::RegexSet;
10+
use crate::{clang, RustTarget};
11+
use std::str::FromStr;
1112

1213
/// An enum representing custom handling that can be given to a variant.
1314
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -193,12 +194,37 @@ impl Enum {
193194
EnumVariation::NewType {
194195
is_bitfield: true,
195196
is_global: false,
197+
is_result_type: false,
198+
}
199+
} else if self.is_matching_enum(
200+
ctx,
201+
&ctx.options().result_error_enums,
202+
item,
203+
) && ctx
204+
.options()
205+
.rust_target
206+
.ge(&RustTarget::from_str("1.79").unwrap())
207+
{
208+
let zero_variant = self.variants.iter().find(|x| {
209+
matches!(
210+
x.val,
211+
EnumVariantValue::Signed(0) | EnumVariantValue::Unsigned(0)
212+
)
213+
});
214+
if zero_variant.is_none() {
215+
panic!("Result-error-enum must have a zero variant. (Item: {item:?})");
216+
}
217+
EnumVariation::NewType {
218+
is_bitfield: false,
219+
is_global: false,
220+
is_result_type: true,
196221
}
197222
} else if self.is_matching_enum(ctx, &ctx.options().newtype_enums, item)
198223
{
199224
EnumVariation::NewType {
200225
is_bitfield: false,
201226
is_global: false,
227+
is_result_type: false,
202228
}
203229
} else if self.is_matching_enum(
204230
ctx,
@@ -208,6 +234,7 @@ impl Enum {
208234
EnumVariation::NewType {
209235
is_bitfield: false,
210236
is_global: true,
237+
is_result_type: false,
211238
}
212239
} else if self.is_matching_enum(
213240
ctx,

bindgen/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ impl Builder {
457457

458458
impl BindgenOptions {
459459
fn build(&mut self) {
460-
const REGEX_SETS_LEN: usize = 29;
460+
const REGEX_SETS_LEN: usize = 30;
461461

462462
let regex_sets: [_; REGEX_SETS_LEN] = [
463463
&mut self.blocklisted_types,
@@ -476,6 +476,7 @@ impl BindgenOptions {
476476
&mut self.constified_enum_modules,
477477
&mut self.newtype_enums,
478478
&mut self.newtype_global_enums,
479+
&mut self.result_error_enums,
479480
&mut self.rustified_enums,
480481
&mut self.rustified_non_exhaustive_enums,
481482
&mut self.type_alias,
@@ -511,6 +512,7 @@ impl BindgenOptions {
511512
"--bitfield-enum",
512513
"--newtype-enum",
513514
"--newtype-global-enum",
515+
"--result-error-enum",
514516
"--rustified-enum",
515517
"--rustified-enum-non-exhaustive",
516518
"--constified-enum-module",

bindgen/options/cli.rs

+5
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ struct BindgenCommand {
162162
/// Mark any enum whose name matches REGEX as a global newtype.
163163
#[arg(long, value_name = "REGEX")]
164164
newtype_global_enum: Vec<String>,
165+
/// Mark any enum whose name matches REGEX as a `Result<(), NonZero<Newtype>>`.
166+
#[arg(long, value_name = "REGEX")]
167+
result_error_enum: Vec<String>,
165168
/// Mark any enum whose name matches REGEX as a Rust enum.
166169
#[arg(long, value_name = "REGEX")]
167170
rustified_enum: Vec<String>,
@@ -537,6 +540,7 @@ where
537540
bitfield_enum,
538541
newtype_enum,
539542
newtype_global_enum,
543+
result_error_enum,
540544
rustified_enum,
541545
rustified_non_exhaustive_enum,
542546
constified_enum,
@@ -839,6 +843,7 @@ where
839843
default_enum_style,
840844
bitfield_enum,
841845
newtype_enum,
846+
result_error_enum,
842847
newtype_global_enum,
843848
rustified_enum,
844849
rustified_non_exhaustive_enum,

bindgen/options/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,27 @@ options! {
438438
},
439439
as_args: "--newtype-enum",
440440
},
441+
/// `enum`s marked as `Result<(), ErrorEnum>`.
442+
result_error_enums: RegexSet {
443+
methods: {
444+
regex_option! {
445+
/// Mark the given `enum` as a `Result<(), ErrorEnum>`, where ErrorEnum is a newtupe.
446+
///
447+
/// This means that a NonZero integer newtype will be declared to represent the `enum`
448+
/// type, without the zero variant, and the error variants will be represented as
449+
/// constants inside of this type's `impl` block. The 0 variant of the enum will
450+
/// be mapped to the `Ok(())` variant of the Result.
451+
///
452+
/// Note: Using This API requires --rust-target 1.79 or newer due to the usage
453+
/// of `NonZero`!
454+
pub fn result_error_enum<T: AsRef<str>>(mut self, arg: T) -> Builder {
455+
self.options.result_error_enums.insert(arg);
456+
self
457+
}
458+
}
459+
},
460+
as_args: "--result-error-enum",
461+
},
441462
/// `enum`s marked as global newtypes .
442463
newtype_global_enums: RegexSet {
443464
methods: {

0 commit comments

Comments
 (0)