diff --git a/ctest-test/build.rs b/ctest-test/build.rs index 7e7a36cce1e27..5f6118bb94688 100644 --- a/ctest-test/build.rs +++ b/ctest-test/build.rs @@ -42,6 +42,7 @@ fn test_ctest() { // public C typedefs have to manually be specified because they are identical to normal // structs on the Rust side. .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) + .alias_is_c_enum(|e| e == "enum_repr_too_small" || e == "enum_wrong_signedness") .skip_roundtrip(|_| true); ctest::generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap(); } diff --git a/ctest-test/src/t2.h b/ctest-test/src/t2.h index 9f99e11a1e79d..dddb6feef88d9 100644 --- a/ctest-test/src/t2.h +++ b/ctest-test/src/t2.h @@ -21,3 +21,11 @@ static void T2a(void) {} #define T2C 4 #define T2S "a" + +enum enum_repr_too_small { + ENUM_REPR_TOO_SMALL_A +}; + +enum enum_wrong_signedness { + ENUM_WRONG_SIGNEDNESS_A +}; \ No newline at end of file diff --git a/ctest-test/src/t2.rs b/ctest-test/src/t2.rs index 45eb3339ab84e..0639753bbeff5 100644 --- a/ctest-test/src/t2.rs +++ b/ctest-test/src/t2.rs @@ -1,3 +1,5 @@ +#![allow(non_camel_case_types)] + use std::ffi::{c_char, c_int}; pub type T2Foo = u32; @@ -34,3 +36,15 @@ i! { extern "C" { pub fn T2a(); } + +#[cfg(target_env = "msvc")] +pub type enum_repr_too_small = i16; +#[cfg(not(target_env = "msvc"))] +pub type enum_repr_too_small = u16; +pub const ENUM_REPR_TOO_SMALL_A: enum_repr_too_small = 0; + +#[cfg(target_env = "msvc")] +pub type enum_wrong_signedness = u32; +#[cfg(not(target_env = "msvc"))] +pub type enum_wrong_signedness = i32; +pub const ENUM_WRONG_SIGNEDNESS_A: enum_wrong_signedness = 0; diff --git a/ctest-test/tests/all.rs b/ctest-test/tests/all.rs index e840f99396c9d..f5476d1b3a961 100644 --- a/ctest-test/tests/all.rs +++ b/ctest-test/tests/all.rs @@ -58,6 +58,9 @@ fn t2() { "bad T2Union size", "bad field type b of T2Union", "bad field offset b of T2Union", + "bad enum_wrong_signedness signed", + "bad enum_repr_too_small size", + "bad enum_repr_too_small align", ]; let mut errors = errors.iter().cloned().collect::>(); diff --git a/ctest/src/generator.rs b/ctest/src/generator.rs index 2bff1f69e5193..0051a0c38d905 100644 --- a/ctest/src/generator.rs +++ b/ctest/src/generator.rs @@ -26,6 +26,8 @@ type VolatileItem = Box bool>; type ArrayArg = Box bool>; /// A function that determines whether to skip a test, taking in the identifier name. type SkipTest = Box bool>; +/// A function that determines whether a type alias is a c enum. +type CEnum = Box bool>; /// A builder used to generate a test suite. #[derive(Default)] @@ -42,6 +44,7 @@ pub struct TestGenerator { pub(crate) skips: Vec, pub(crate) verbose_skip: bool, pub(crate) volatile_items: Vec, + pub(crate) c_enums: Vec, pub(crate) array_arg: Option, pub(crate) skip_private: bool, pub(crate) skip_roundtrip: Option, @@ -206,6 +209,20 @@ impl TestGenerator { self } + /// Indicate that a type alias is actually a C enum. + /// + /// # Examples + /// ```no_run + /// use ctest::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.alias_is_c_enum(|e| e == "pid_type"); + /// ``` + pub fn alias_is_c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self { + self.c_enums.push(Box::new(f)); + self + } + /// Indicate that a struct field should be marked `volatile`. /// /// # Examples @@ -516,6 +533,30 @@ impl TestGenerator { self } + /// Configures whether tests for a C enum are generated. + /// + /// A C enum consists of a type alias, as well as constants that have the same type. Tests + /// for both the alias as well as the constants are skipped. + /// + /// # Examples + /// + /// ```no_run + /// use ctest::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_c_enum(|e| e == "pid_type"); + /// ``` + pub fn skip_c_enum(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::CEnumType(e) = item { + f(e) + } else { + false + } + })); + self + } + /// Add a flag to the C compiler invocation. /// /// # Examples @@ -976,6 +1017,7 @@ impl TestGenerator { MapInput::UnionField(_, f) => f.ident().to_string(), MapInput::StructType(ty) => format!("struct {ty}"), MapInput::UnionType(ty) => format!("union {ty}"), + MapInput::CEnumType(ty) => format!("enum {ty}"), MapInput::StructFieldType(_, f) => f.ident().to_string(), MapInput::UnionFieldType(_, f) => f.ident().to_string(), MapInput::Type(ty) => translate_primitive_type(ty), diff --git a/ctest/src/lib.rs b/ctest/src/lib.rs index 8e4f764b72b70..ce0b50bd06cc4 100644 --- a/ctest/src/lib.rs +++ b/ctest/src/lib.rs @@ -66,6 +66,7 @@ pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct type. StructType(&'a str), UnionType(&'a str), + CEnumType(&'a str), StructFieldType(&'a Struct, &'a Field), UnionFieldType(&'a Union, &'a Field), } diff --git a/ctest/src/template.rs b/ctest/src/template.rs index a720215cbc3d5..ae610ee513caa 100644 --- a/ctest/src/template.rs +++ b/ctest/src/template.rs @@ -597,6 +597,36 @@ impl<'a> TranslateHelper<'a> { fn filter_ffi_items(&mut self) { let verbose = self.generator.verbose_skip; + let skipped = self.filtered_ffi_items.aliases.extract_if(.., |alias| { + self.generator + .skips + .iter() + .any(|f| f(&MapInput::CEnumType(alias.ident()))) + }); + + for item in skipped { + if verbose { + eprintln!("Skipping C enum type {}", item.ident()); + } + } + + let skipped = self + .filtered_ffi_items + .constants + .extract_if(.., |constant| { + self.generator.skips.iter().any(|f| { + f(&MapInput::CEnumType( + &constant.ty.to_token_stream().to_string(), + )) + }) + }); + + for item in skipped { + if verbose { + eprintln!("Skipping C enum constant {}", item.ident()); + } + } + macro_rules! filter { ($field:ident, $variant:ident, $label:literal) => {{ let skipped = self.filtered_ffi_items.$field.extract_if(.., |item| { @@ -647,6 +677,7 @@ impl<'a> TranslateHelper<'a> { MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), + MapInput::CEnumType(_) => panic!("MapInput::CEnumType is not allowed!"), MapInput::StructFieldType(_, _) => panic!("MapInput::StructFieldType is not allowed!"), MapInput::UnionFieldType(_, _) => panic!("MapInput::UnionFieldType is not allowed!"), MapInput::Type(_) => panic!("MapInput::Type is not allowed!"), @@ -664,6 +695,8 @@ impl<'a> TranslateHelper<'a> { MapInput::StructType(&ty) } else if self.ffi_items.contains_union(ident) { MapInput::UnionType(&ty) + } else if self.generator.c_enums.iter().any(|f| f(&ty)) { + MapInput::CEnumType(&ty) } else { MapInput::Type(&ty) }; diff --git a/ctest/tests/basic.rs b/ctest/tests/basic.rs index 8f8e091636bdd..a3311bf322fa9 100644 --- a/ctest/tests/basic.rs +++ b/ctest/tests/basic.rs @@ -92,6 +92,7 @@ fn test_skip_simple() { let (mut gen_, out_dir) = default_generator(1, "simple.h").unwrap(); gen_.skip_const(|c| c.ident() == "B" || c.ident() == "A") + .skip_c_enum(|e| e == "Color") .skip_alias(|a| a.ident() == "Byte") .skip_struct(|s| s.ident() == "Person") .skip_union(|u| u.ident() == "Word") @@ -108,7 +109,9 @@ fn test_map_simple() { let library_path = "simple.out.with-renames.a"; let (mut gen_, out_dir) = default_generator(1, "simple.h").unwrap(); - gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string())); + gen_.rename_constant(|c| (c.ident() == "B").then(|| "C_B".to_string())) + .alias_is_c_enum(|e| e == "Color") + .skip_signededness(|ty| ty == "Color"); check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path); } diff --git a/ctest/tests/input/simple.h b/ctest/tests/input/simple.h index d3b77dbda9eef..8ab62ac451037 100644 --- a/ctest/tests/input/simple.h +++ b/ctest/tests/input/simple.h @@ -20,5 +20,12 @@ union Word #define A "abc" #define C_B "bac" +enum Color +{ + RED, + BLUE, + GREEN +}; + extern void *calloc(size_t num, size_t size); extern Byte byte; diff --git a/ctest/tests/input/simple.out.with-renames.c b/ctest/tests/input/simple.out.with-renames.c index 837c3e573f5da..7427c752cb78f 100644 --- a/ctest/tests/input/simple.out.with-renames.c +++ b/ctest/tests/input/simple.out.with-renames.c @@ -25,12 +25,42 @@ char *ctest_const_cstr__B(void) { return ctest_const_B_val_static; } +static enum Color ctest_const_RED_val_static = RED; + +// Define a function that returns a pointer to the value of the constant to test. +// This will later be called on the Rust side via FFI. +enum Color *ctest_const__RED(void) { + return &ctest_const_RED_val_static; +} + +static enum Color ctest_const_BLUE_val_static = BLUE; + +// Define a function that returns a pointer to the value of the constant to test. +// This will later be called on the Rust side via FFI. +enum Color *ctest_const__BLUE(void) { + return &ctest_const_BLUE_val_static; +} + +static enum Color ctest_const_GREEN_val_static = GREEN; + +// Define a function that returns a pointer to the value of the constant to test. +// This will later be called on the Rust side via FFI. +enum Color *ctest_const__GREEN(void) { + return &ctest_const_GREEN_val_static; +} + // Return the size of a type. uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } // Return the alignment of a type. uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); } +// Return the size of a type. +uint64_t ctest_size_of__Color(void) { return sizeof(enum Color); } + +// Return the alignment of a type. +uint64_t ctest_align_of__Color(void) { return _Alignof(enum Color); } + // Return the size of a type. uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } @@ -178,6 +208,33 @@ Byte ctest_roundtrip__Byte( return value; } +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +enum Color ctest_roundtrip__Color( + enum Color value, + const uint8_t is_padding_byte[sizeof(enum Color)], + uint8_t value_bytes[sizeof(enum Color)] +) { + int size = (int)sizeof(enum Color); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + // Tests whether the struct/union/alias `x` when passed by value to C and back to Rust // remains unchanged. // It checks if the size is the same as well as if the padding bytes are all in the correct place. diff --git a/ctest/tests/input/simple.out.with-renames.rs b/ctest/tests/input/simple.out.with-renames.rs index e4f6361fa8646..faefa23a2d443 100644 --- a/ctest/tests/input/simple.out.with-renames.rs +++ b/ctest/tests/input/simple.out.with-renames.rs @@ -91,6 +91,84 @@ mod generated_tests { check_same(r_val, c_val, "const B string"); } + // Test that the value of the constant is the same in both Rust and C. + // This performs a byte by byte comparison of the constant value. + pub fn ctest_const_RED() { + type T = Color; + extern "C" { + fn ctest_const__RED() -> *const T; + } + + /* HACK: The slices may contain uninitialized data! We do this because + * there isn't a good way to recursively iterate all fields. */ + + let r_val: T = RED; + let r_bytes = unsafe { + slice::from_raw_parts(ptr::from_ref(&r_val).cast::(), size_of::()) + }; + + let c_bytes = unsafe { + let c_ptr: *const T = ctest_const__RED(); + slice::from_raw_parts(c_ptr.cast::(), size_of::()) + }; + + for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() { + check_same_hex(b1, b2, &format!("RED value at byte {}", i)); + } + } + + // Test that the value of the constant is the same in both Rust and C. + // This performs a byte by byte comparison of the constant value. + pub fn ctest_const_BLUE() { + type T = Color; + extern "C" { + fn ctest_const__BLUE() -> *const T; + } + + /* HACK: The slices may contain uninitialized data! We do this because + * there isn't a good way to recursively iterate all fields. */ + + let r_val: T = BLUE; + let r_bytes = unsafe { + slice::from_raw_parts(ptr::from_ref(&r_val).cast::(), size_of::()) + }; + + let c_bytes = unsafe { + let c_ptr: *const T = ctest_const__BLUE(); + slice::from_raw_parts(c_ptr.cast::(), size_of::()) + }; + + for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() { + check_same_hex(b1, b2, &format!("BLUE value at byte {}", i)); + } + } + + // Test that the value of the constant is the same in both Rust and C. + // This performs a byte by byte comparison of the constant value. + pub fn ctest_const_GREEN() { + type T = Color; + extern "C" { + fn ctest_const__GREEN() -> *const T; + } + + /* HACK: The slices may contain uninitialized data! We do this because + * there isn't a good way to recursively iterate all fields. */ + + let r_val: T = GREEN; + let r_bytes = unsafe { + slice::from_raw_parts(ptr::from_ref(&r_val).cast::(), size_of::()) + }; + + let c_bytes = unsafe { + let c_ptr: *const T = ctest_const__GREEN(); + slice::from_raw_parts(c_ptr.cast::(), size_of::()) + }; + + for (i, (&b1, &b2)) in r_bytes.iter().zip(c_bytes.iter()).enumerate() { + check_same_hex(b1, b2, &format!("GREEN value at byte {}", i)); + } + } + /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_Byte() { extern "C" { @@ -108,6 +186,23 @@ mod generated_tests { check_same(rust_align, c_align, "Byte align"); } + /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + pub fn ctest_size_align_Color() { + extern "C" { + fn ctest_size_of__Color() -> u64; + fn ctest_align_of__Color() -> u64; + } + + let rust_size = size_of::() as u64; + let c_size = unsafe { ctest_size_of__Color() }; + + let rust_align = align_of::() as u64; + let c_align = unsafe { ctest_align_of__Color() }; + + check_same(rust_size, c_size, "Color size"); + check_same(rust_align, c_align, "Color align"); + } + /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_Person() { extern "C" { @@ -488,6 +583,116 @@ mod generated_tests { } } + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Color() -> Vec { + if 0 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!true; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Color() { + type U = Color; + extern "C" { + fn ctest_size_of__Color() -> u64; + fn ctest_roundtrip__Color( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Color(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the uninitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Color() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of enum Color is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Color(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Color\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Color\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + /// Generates a padding map for a specific type. /// /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in @@ -785,7 +990,11 @@ fn main() { fn run_all() { ctest_const_cstr_A(); ctest_const_cstr_B(); + ctest_const_RED(); + ctest_const_BLUE(); + ctest_const_GREEN(); ctest_size_align_Byte(); + ctest_size_align_Color(); ctest_size_align_Person(); ctest_size_align_Word(); ctest_signededness_Byte(); @@ -800,6 +1009,7 @@ fn run_all() { ctest_field_ptr_Word_word(); ctest_field_ptr_Word_byte(); ctest_roundtrip_Byte(); + ctest_roundtrip_Color(); ctest_roundtrip_Person(); ctest_roundtrip_Word(); ctest_foreign_fn_calloc(); diff --git a/ctest/tests/input/simple.rs b/ctest/tests/input/simple.rs index 979afe8b22aff..c9fff3d7d6853 100644 --- a/ctest/tests/input/simple.rs +++ b/ctest/tests/input/simple.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_char, c_void}; +use std::ffi::{c_char, c_int, c_void}; pub type Byte = u8; @@ -18,6 +18,11 @@ pub union Word { const A: *const c_char = c"abc".as_ptr(); const B: *const c_char = c"bac".as_ptr(); +pub type Color = c_int; +pub const RED: Color = 0; +pub const BLUE: Color = RED + 1; +pub const GREEN: Color = BLUE + 1; + unsafe extern "C" { pub fn calloc(num: usize, size: usize) -> *mut c_void; diff --git a/libc-test/build.rs b/libc-test/build.rs index 1c149a4371359..148de054e428c 100644 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -2948,8 +2948,7 @@ fn test_freebsd(target: &str) { }); } - // FIXME(ctest): The original ctest bypassed this requirement somehow. - cfg.rename_type(move |ty| (ty == "dot3Vendors").then_some(format!("enum {ty}"))); + cfg.alias_is_c_enum(|ty| ty == "dot3Vendors"); ctest::generate_test(&mut cfg, "../src/lib.rs", "ctest_output.rs").unwrap(); } diff --git a/src/macros.rs b/src/macros.rs index 8b723966dc761..afa8b23ed00f0 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -87,7 +87,7 @@ macro_rules! prelude { pub(crate) use mem::{align_of, align_of_val, size_of, size_of_val}; #[allow(unused_imports)] - pub(crate) use crate::types::Padding; + pub(crate) use crate::types::{CEnumRepr, Padding}; // Commonly used types defined in this crate #[allow(unused_imports)] pub(crate) use crate::{ @@ -274,9 +274,9 @@ macro_rules! c_enum { c_enum!(@one; $ty_name; $variant + 1; $($tail)*); }; - // Use a specific type if provided, otherwise default to `c_uint` + // Use a specific type if provided, otherwise default to `CEnumRepr` (@ty $repr:ty) => { $repr }; - (@ty) => { $crate::c_uint }; + (@ty) => { $crate::prelude::CEnumRepr }; } // This is a pretty horrible hack to allow us to conditionally mark some functions as 'const', @@ -397,6 +397,8 @@ macro_rules! __item { #[cfg(test)] mod tests { + use crate::types::CEnumRepr; + #[test] fn c_enumbasic() { // By default, variants get sequential values. @@ -408,9 +410,9 @@ mod tests { } } - assert_eq!(VAR0, 0_u32); - assert_eq!(VAR1, 1_u32); - assert_eq!(VAR2, 2_u32); + assert_eq!(VAR0, 0 as CEnumRepr); + assert_eq!(VAR1, 1 as CEnumRepr); + assert_eq!(VAR2, 2 as CEnumRepr); } #[test] @@ -437,9 +439,9 @@ mod tests { } } - assert_eq!(VAR2, 2_u32); - assert_eq!(VAR3, 3_u32); - assert_eq!(VAR4, 4_u32); + assert_eq!(VAR2, 2 as CEnumRepr); + assert_eq!(VAR3, 3 as CEnumRepr); + assert_eq!(VAR4, 4 as CEnumRepr); } #[test] @@ -458,12 +460,12 @@ mod tests { } } - assert_eq!(VAR0, 0_u32); - assert_eq!(VAR2_0, 2_u32); - assert_eq!(VAR3_0, 3_u32); - assert_eq!(VAR4_0, 4_u32); - assert_eq!(VAR2_1, 2_u32); - assert_eq!(VAR3_1, 3_u32); - assert_eq!(VAR4_1, 4_u32); + assert_eq!(VAR0, 0 as CEnumRepr); + assert_eq!(VAR2_0, 2 as CEnumRepr); + assert_eq!(VAR3_0, 3 as CEnumRepr); + assert_eq!(VAR4_0, 4 as CEnumRepr); + assert_eq!(VAR2_1, 2 as CEnumRepr); + assert_eq!(VAR3_1, 3 as CEnumRepr); + assert_eq!(VAR4_1, 4 as CEnumRepr); } } diff --git a/src/types.rs b/src/types.rs index d972d2390c9cd..3ef8177c5feca 100644 --- a/src/types.rs +++ b/src/types.rs @@ -16,3 +16,11 @@ impl Default for Padding { Self(MaybeUninit::zeroed()) } } + +/// The default repr type used for C style enums in Rust. +#[cfg(target_env = "msvc")] +#[allow(unused)] +pub(crate) type CEnumRepr = c_int; +#[cfg(not(target_env = "msvc"))] +#[allow(unused)] +pub(crate) type CEnumRepr = c_uint;