Skip to content

Commit e50722e

Browse files
authored
Merge pull request #4420 from tgross35/enum-macro
Introduce a new `c_enum` macro
2 parents ed84048 + 62051ca commit e50722e

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

src/macros.rs

+116
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ macro_rules! missing {
193193

194194
/// Implement `Clone` and `Copy` for an enum, as well as `Debug`, `Eq`, `Hash`, and
195195
/// `PartialEq` if the `extra_traits` feature is enabled.
196+
// FIXME(#4419): Replace all uses of `e!` with `c_enum!`
196197
macro_rules! e {
197198
($(
198199
$(#[$attr:meta])*
@@ -210,6 +211,48 @@ macro_rules! e {
210211
)*);
211212
}
212213

214+
/// Represent a C enum as Rust constants and a type.
215+
///
216+
/// C enums can't soundly be mapped to Rust enums since C enums are allowed to have duplicates or
217+
/// unlisted values, but this is UB in Rust. This enum doesn't implement any traits, its main
218+
/// purpose is to calculate the correct enum values.
219+
///
220+
/// See <https://github.com/rust-lang/libc/issues/4419> for more.
221+
macro_rules! c_enum {
222+
(
223+
$(#[repr($repr:ty)])?
224+
$ty_name:ident {
225+
$($variant:ident $(= $value:literal)?,)+
226+
}
227+
) => {
228+
pub type $ty_name = c_enum!(@ty $($repr)?);
229+
c_enum!(@one; $ty_name; 0; $($variant $(= $value)?,)+);
230+
};
231+
232+
// Matcher for a single variant
233+
(@one; $_ty_name:ident; $_idx:expr;) => {};
234+
(
235+
@one; $ty_name:ident; $default_val:expr;
236+
$variant:ident $(= $value:literal)?,
237+
$($tail:tt)*
238+
) => {
239+
pub const $variant: $ty_name = {
240+
#[allow(unused_variables)]
241+
let r = $default_val;
242+
$(let r = $value;)?
243+
r
244+
};
245+
246+
// The next value is always one more than the previous value, unless
247+
// set explicitly.
248+
c_enum!(@one; $ty_name; $variant + 1; $($tail)*);
249+
};
250+
251+
// Use a specific type if provided, otherwise default to `c_uint`
252+
(@ty $repr:ty) => { $repr };
253+
(@ty) => { $crate::c_uint };
254+
}
255+
213256
// This is a pretty horrible hack to allow us to conditionally mark some functions as 'const',
214257
// without requiring users of this macro to care "libc_const_extern_fn".
215258
//
@@ -325,3 +368,76 @@ macro_rules! __item {
325368
$i
326369
};
327370
}
371+
372+
#[cfg(test)]
373+
mod tests {
374+
#[test]
375+
fn c_enumbasic() {
376+
// By default, variants get sequential values.
377+
c_enum! {
378+
e {
379+
VAR0,
380+
VAR1,
381+
VAR2,
382+
}
383+
}
384+
385+
assert_eq!(VAR0, 0_u32);
386+
assert_eq!(VAR1, 1_u32);
387+
assert_eq!(VAR2, 2_u32);
388+
}
389+
390+
#[test]
391+
fn c_enumrepr() {
392+
// By default, variants get sequential values.
393+
c_enum! {
394+
#[repr(u16)]
395+
e {
396+
VAR0,
397+
}
398+
}
399+
400+
assert_eq!(VAR0, 0_u16);
401+
}
402+
403+
#[test]
404+
fn c_enumset_value() {
405+
// Setting an explicit value resets the count.
406+
c_enum! {
407+
e {
408+
VAR2 = 2,
409+
VAR3,
410+
VAR4,
411+
}
412+
}
413+
414+
assert_eq!(VAR2, 2_u32);
415+
assert_eq!(VAR3, 3_u32);
416+
assert_eq!(VAR4, 4_u32);
417+
}
418+
419+
#[test]
420+
fn c_enummultiple_set_value() {
421+
// C enums always take one more than the previous value, unless set to a specific
422+
// value. Duplicates are allowed.
423+
c_enum! {
424+
e {
425+
VAR0,
426+
VAR2_0 = 2,
427+
VAR3_0,
428+
VAR4_0,
429+
VAR2_1 = 2,
430+
VAR3_1,
431+
VAR4_1,
432+
}
433+
}
434+
435+
assert_eq!(VAR0, 0_u32);
436+
assert_eq!(VAR2_0, 2_u32);
437+
assert_eq!(VAR3_0, 3_u32);
438+
assert_eq!(VAR4_0, 4_u32);
439+
assert_eq!(VAR2_1, 2_u32);
440+
assert_eq!(VAR3_1, 3_u32);
441+
assert_eq!(VAR4_1, 4_u32);
442+
}
443+
}

0 commit comments

Comments
 (0)