Skip to content

Commit 241278c

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 241278c

File tree

7 files changed

+150
-1
lines changed

7 files changed

+150
-1
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"
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

+22
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,33 @@ impl Enum {
193193
EnumVariation::NewType {
194194
is_bitfield: true,
195195
is_global: false,
196+
is_result_type: false,
197+
}
198+
} else if self.is_matching_enum(
199+
ctx,
200+
&ctx.options().result_error_enums,
201+
item,
202+
) {
203+
let zero_variant = self.variants.iter().find(|x| {
204+
matches!(
205+
x.val,
206+
EnumVariantValue::Signed(0) | EnumVariantValue::Unsigned(0)
207+
)
208+
});
209+
if zero_variant.is_none() {
210+
panic!("Result-error-enum must have a zero variant. (Item: {item:?})");
211+
}
212+
EnumVariation::NewType {
213+
is_bitfield: false,
214+
is_global: false,
215+
is_result_type: true,
196216
}
197217
} else if self.is_matching_enum(ctx, &ctx.options().newtype_enums, item)
198218
{
199219
EnumVariation::NewType {
200220
is_bitfield: false,
201221
is_global: false,
222+
is_result_type: false,
202223
}
203224
} else if self.is_matching_enum(
204225
ctx,
@@ -208,6 +229,7 @@ impl Enum {
208229
EnumVariation::NewType {
209230
is_bitfield: false,
210231
is_global: true,
232+
is_result_type: false,
211233
}
212234
} else if self.is_matching_enum(
213235
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

+18
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,24 @@ 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+
pub fn result_error_enum<T: AsRef<str>>(mut self, arg: T) -> Builder {
452+
self.options.result_error_enums.insert(arg);
453+
self
454+
}
455+
}
456+
},
457+
as_args: "--result-error-enum",
458+
},
441459
/// `enum`s marked as global newtypes .
442460
newtype_global_enums: RegexSet {
443461
methods: {

0 commit comments

Comments
 (0)