You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
add support for deriving NoUninit on enums with fields (#292)
* add support for deriving NoUninit on enums with fields
We check for padding like we do for structs except that we also
consider the enum discriminant when calculating the unpadded size.
* improve support for #[repr(C)] enums
Unfortunately, the integer discriminant used for #[repr(C)] doesn't
have a name (it's not always core::ffi::c_int), but we can use some
compile-time tricks to get the integer type. Use that instead of hard-
coding core::ffi::c_int and add support for deriving NoUninit
for #[repr(C)] enums.
* simplify get_enum_discriminant for enums w/o fields
* add more comments
Co-authored-by: Gray Olson <[email protected]>
* update requirements for NoUninit
* link type layout section in NoUninit docs
* small wording change on no uninit docs
* inline type alias
This makes the generated code a bit harder to read, but also has the
advantage of not unnecessarily adding a type to the global namespace
for CheckedBitPattern.
---------
Co-authored-by: Gray Olson <[email protected]>
@@ -237,10 +237,18 @@ impl Derivable for NoUninit {
237
237
Repr::C | Repr::Transparent => Ok(()),
238
238
_ => bail!("NoUninit requires the struct to be #[repr(C)] or #[repr(transparent)]"),
239
239
},
240
-
Data::Enum(_) => if repr.repr.is_integer(){
241
-
Ok(())
242
-
}else{
243
-
bail!("NoUninit requires the enum to be an explicit #[repr(Int)]")
240
+
Data::Enum(DataEnum{ variants,.. }) => {
241
+
if !enum_has_fields(variants.iter()){
242
+
ifmatches!(repr.repr,Repr::C | Repr::Integer(_)){
243
+
Ok(())
244
+
}else{
245
+
bail!("NoUninit requires the enum to be #[repr(C)] or #[repr(Int)]")
246
+
}
247
+
}elseifmatches!(repr.repr,Repr::Rust){
248
+
bail!("NoUninit requires an explicit repr annotation because `repr(Rust)` doesn't have a specified type layout")
249
+
}else{
250
+
Ok(())
251
+
}
244
252
},
245
253
Data::Union(_) => bail!("NoUninit can only be derived on enums and structs")
246
254
}
@@ -255,7 +263,7 @@ impl Derivable for NoUninit {
255
263
256
264
match&input.data{
257
265
Data::Struct(DataStruct{ .. }) => {
258
-
let assert_no_padding = generate_assert_no_padding(&input)?;
266
+
let assert_no_padding = generate_assert_no_padding(&input,None)?;
259
267
let assert_fields_are_no_padding = generate_fields_are_trait(
260
268
&input,
261
269
None,
@@ -268,8 +276,61 @@ impl Derivable for NoUninit {
268
276
))
269
277
}
270
278
Data::Enum(DataEnum{ variants, .. }) => {
271
-
if variants.iter().any(|variant| !variant.fields.is_empty()){
272
-
bail!("Only fieldless enums are supported for NoUninit")
279
+
ifenum_has_fields(variants.iter()){
280
+
// There are two different C representations for enums with fields:
281
+
// There's `#[repr(C)]`/`[repr(C, int)]` and `#[repr(int)]`.
282
+
// `#[repr(C)]` is equivalent to a struct containing the discriminant
283
+
// and a union of structs representing each variant's fields.
284
+
// `#[repr(C)]` is equivalent to a union containing structs of the
285
+
// discriminant and the fields.
286
+
//
287
+
// See https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.adt
288
+
// and https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.primitive.adt
289
+
//
290
+
// In practice the only difference between the two is whether and
291
+
// where padding bytes are placed. For `#[repr(C)]` enums, the first
292
+
// enum fields of all variants start at the same location (the first
293
+
// byte in the union). For `#[repr(int)]` enums, the structs
294
+
// representing each variant are layed out individually and padding
295
+
// does not depend on other variants, but only on the size of the
296
+
// discriminant and the alignment of the first field. The location of
297
+
// the first field might differ between variants, potentially
298
+
// resulting in less padding or padding placed later in the enum.
299
+
//
300
+
// The `NoUninit` derive macro asserts that no padding exists by
301
+
// removing all padding with `#[repr(packed)]` and checking that this
302
+
// doesn't change the size. Since the location and presence of
303
+
// padding bytes is the only difference between the two
304
+
// representations and we're removing all padding bytes, the resuling
305
+
// layout would identical for both representations. This means that
306
+
// we can just pick one of the representations and don't have to
307
+
// implement desugaring for both. We chose to implement the
308
+
// desugaring for `#[repr(int)]`.
309
+
310
+
let enum_discriminant = generate_enum_discriminant(input)?;
/// For `repr(int)` and `repr(C, int)` enums, this will return the known bare
1105
+
/// integer type specified.
1106
+
///
1107
+
/// For `repr(C)` enums, this will extract the underlying size chosen by rustc.
1108
+
/// It will return a token stream which is a type expression that evaluates to
1109
+
/// a primitive integer type of this size, using our `EnumTagIntegerBytes`
1110
+
/// trait.
1111
+
///
1112
+
/// For fieldless `repr(C)` enums, we can feed the size of the enum directly
1113
+
/// into the trait.
1114
+
///
1115
+
/// For `repr(C)` enums with fields, we generate a new fieldless `repr(C)` enum
1116
+
/// with the same variants, then use that in the calculation. This is the
1117
+
/// specified behavior, see https://doc.rust-lang.org/stable/reference/type-layout.html#reprc-enums-with-fields
1118
+
///
1119
+
/// Returns a tuple of (type ident, auxiliary definitions)
1120
+
fnget_enum_discriminant(
1121
+
input:&DeriveInput,crate_name:&TokenStream,
1122
+
) -> Result<(TokenStream,TokenStream)>{
1123
+
let repr = get_repr(&input.attrs)?;
1124
+
match repr.repr{
1125
+
Repr::C => {
1126
+
let e = ifletData::Enum(e) = &input.data{ e }else{unreachable!()};
1127
+
ifenum_has_fields(e.variants.iter()){
1128
+
// If the enum has fields, we must first isolate the discriminant by
1129
+
// removing all the fields.
1130
+
let enum_discriminant = generate_enum_discriminant(input)?;
0 commit comments