@@ -256,6 +256,32 @@ macro_rules! align_of {
256256 } } ;
257257}
258258
259+ mod size_to_tag {
260+ pub trait SizeToTag < const SIZE : usize > {
261+ type Tag ;
262+ }
263+
264+ impl SizeToTag < 1 > for ( ) {
265+ type Tag = u8 ;
266+ }
267+ impl SizeToTag < 2 > for ( ) {
268+ type Tag = u16 ;
269+ }
270+ impl SizeToTag < 4 > for ( ) {
271+ type Tag = u32 ;
272+ }
273+ impl SizeToTag < 8 > for ( ) {
274+ type Tag = u64 ;
275+ }
276+ impl SizeToTag < 16 > for ( ) {
277+ type Tag = u128 ;
278+ }
279+ }
280+
281+ /// An alias for the unsigned integer of the given size in bytes.
282+ #[ doc( hidden) ]
283+ pub type SizeToTag < const SIZE : usize > = <( ) as size_to_tag:: SizeToTag < SIZE > >:: Tag ;
284+
259285/// Does the struct type `$t` have padding?
260286///
261287/// `$ts` is the list of the type of every field in `$t`. `$t` must be a
@@ -271,7 +297,7 @@ macro_rules! align_of {
271297#[ doc( hidden) ] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
272298#[ macro_export]
273299macro_rules! struct_has_padding {
274- ( $t: ty, $( $ts: ty) ,* ) => {
300+ ( $t: ty, [ $( $ts: ty) ,* ] ) => {
275301 :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$t>( ) > 0 $( + :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$ts>( ) ) *
276302 } ;
277303}
@@ -291,11 +317,38 @@ macro_rules! struct_has_padding {
291317#[ doc( hidden) ] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
292318#[ macro_export]
293319macro_rules! union_has_padding {
294- ( $t: ty, $( $ts: ty) ,* ) => {
320+ ( $t: ty, [ $( $ts: ty) ,* ] ) => {
295321 false $( || :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$t>( ) != :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$ts>( ) ) *
296322 } ;
297323}
298324
325+ /// Does the enum type `$t` have padding?
326+ ///
327+ /// `$disc` is the type of the discriminant for the enum, and `$ts` is a list of
328+ /// fields in each square-bracket-deiminated variant. `$t` must be an enum, or
329+ /// else `enum_has_padding!`'s result may be meaningless. An enum has padding if
330+ /// any of its variant structs contain padding, and so all of the variants of an
331+ /// enum must be "full" in order for the enum to not have padding.
332+ ///
333+ /// The results of `enum_has_padding!` require that the enum is not
334+ /// `repr(Rust)`, as `repr(Rust)` enums may niche the enum's tag and reduce the
335+ /// total number of bytes required to represent the enum as a result. As long as
336+ /// the enum is `repr(C)`, `repr(int)`, or `repr(C, int)`, this will
337+ /// consistently return whether the enum contains any padding bytes.
338+ #[ doc( hidden) ] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
339+ #[ macro_export]
340+ macro_rules! enum_has_padding {
341+ ( $t: ty, $disc: ty, $( [ $( $ts: ty) ,* ] ) ,* ) => {
342+ false $(
343+ || :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$t>( )
344+ != (
345+ :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$disc>( )
346+ $( + :: zerocopy:: macro_util:: core_reexport:: mem:: size_of:: <$ts>( ) ) *
347+ )
348+ ) *
349+ }
350+ }
351+
299352/// Does `t` have alignment greater than or equal to `u`? If not, this macro
300353/// produces a compile error. It must be invoked in a dead codepath. This is
301354/// used in `transmute_ref!` and `transmute_mut!`.
@@ -717,6 +770,36 @@ mod tests {
717770 */
718771 }
719772
773+ #[ test]
774+ fn test_enum_casts ( ) {
775+ // Test that casting the variants of enums with signed integer reprs to
776+ // unsigned integers obeys expected signed -> unsigned casting rules.
777+
778+ #[ repr( i8 ) ]
779+ enum ReprI8 {
780+ MinusOne = -1 ,
781+ Zero = 0 ,
782+ Min = i8:: MIN ,
783+ Max = i8:: MAX ,
784+ }
785+
786+ #[ allow( clippy:: as_conversions) ]
787+ let x = ReprI8 :: MinusOne as u8 ;
788+ assert_eq ! ( x, u8 :: MAX ) ;
789+
790+ #[ allow( clippy:: as_conversions) ]
791+ let x = ReprI8 :: Zero as u8 ;
792+ assert_eq ! ( x, 0 ) ;
793+
794+ #[ allow( clippy:: as_conversions) ]
795+ let x = ReprI8 :: Min as u8 ;
796+ assert_eq ! ( x, 128 ) ;
797+
798+ #[ allow( clippy:: as_conversions) ]
799+ let x = ReprI8 :: Max as u8 ;
800+ assert_eq ! ( x, 127 ) ;
801+ }
802+
720803 #[ test]
721804 fn test_struct_has_padding ( ) {
722805 // Test that, for each provided repr, `struct_has_padding!` reports the
@@ -725,7 +808,7 @@ mod tests {
725808 ( #[ $cfg: meta] ( $( $ts: ty) ,* ) => $expect: expr) => { {
726809 #[ $cfg]
727810 struct Test ( $( #[ allow( dead_code) ] $ts) ,* ) ;
728- assert_eq!( struct_has_padding!( Test , $( $ts) ,* ) , $expect) ;
811+ assert_eq!( struct_has_padding!( Test , [ $( $ts) ,* ] ) , $expect) ;
729812 } } ;
730813 ( #[ $cfg: meta] $( #[ $cfgs: meta] ) * ( $( $ts: ty) ,* ) => $expect: expr) => {
731814 test!( #[ $cfg] ( $( $ts) ,* ) => $expect) ;
@@ -756,7 +839,7 @@ mod tests {
756839 #[ $cfg]
757840 #[ allow( unused) ] // fields are never read
758841 union Test { $( $fs: $ts) ,* }
759- assert_eq!( union_has_padding!( Test , $( $ts) ,* ) , $expect) ;
842+ assert_eq!( union_has_padding!( Test , [ $( $ts) ,* ] ) , $expect) ;
760843 } } ;
761844 ( #[ $cfg: meta] $( #[ $cfgs: meta] ) * { $( $fs: ident: $ts: ty) ,* } => $expect: expr) => {
762845 test!( #[ $cfg] { $( $fs: $ts) ,* } => $expect) ;
@@ -774,4 +857,78 @@ mod tests {
774857 // anyway.
775858 test ! ( #[ repr( C ) ] #[ repr( packed) ] { a: u8 , b: u64 } => true ) ;
776859 }
860+
861+ #[ test]
862+ fn test_enum_has_padding ( ) {
863+ // Test that, for each provided repr, `enum_has_padding!` reports the
864+ // expected value.
865+ macro_rules! test {
866+ ( #[ repr( $disc: ident $( , $c: ident) ?) ] { $( $vs: ident ( $( $ts: ty) ,* ) , ) * } => $expect: expr) => {
867+ test!( @case #[ repr( $disc $( , $c) ?) ] { $( $vs ( $( $ts) ,* ) , ) * } => $expect) ;
868+ } ;
869+ ( #[ repr( $disc: ident $( , $c: ident) ?) ] #[ $cfg: meta] $( #[ $cfgs: meta] ) * { $( $vs: ident ( $( $ts: ty) ,* ) , ) * } => $expect: expr) => {
870+ test!( @case #[ repr( $disc $( , $c) ?) ] #[ $cfg] { $( $vs ( $( $ts) ,* ) , ) * } => $expect) ;
871+ test!( #[ repr( $disc $( , $c) ?) ] $( #[ $cfgs] ) * { $( $vs ( $( $ts) ,* ) , ) * } => $expect) ;
872+ } ;
873+ ( @case #[ repr( $disc: ident $( , $c: ident) ?) ] $( #[ $cfg: meta] ) ? { $( $vs: ident ( $( $ts: ty) ,* ) , ) * } => $expect: expr) => { {
874+ #[ repr( $disc $( , $c) ?) ]
875+ $( #[ $cfg] ) ?
876+ #[ allow( unused) ] // variants and fields are never used
877+ enum Test {
878+ $( $vs ( $( $ts) ,* ) , ) *
879+ }
880+ assert_eq!(
881+ enum_has_padding!( Test , $disc, $( [ $( $ts) ,* ] ) ,* ) ,
882+ $expect
883+ ) ;
884+ } } ;
885+ }
886+
887+ #[ allow( unused) ]
888+ #[ repr( align( 2 ) ) ]
889+ struct U16 ( u16 ) ;
890+
891+ #[ allow( unused) ]
892+ #[ repr( align( 4 ) ) ]
893+ struct U32 ( u32 ) ;
894+
895+ test ! ( #[ repr( u8 ) ] #[ repr( C ) ] {
896+ A ( u8 ) ,
897+ } => false ) ;
898+ test ! ( #[ repr( u16 ) ] #[ repr( C ) ] {
899+ A ( u8 , u8 ) ,
900+ B ( U16 ) ,
901+ } => false ) ;
902+ test ! ( #[ repr( u32 ) ] #[ repr( C ) ] {
903+ A ( u8 , u8 , u8 , u8 ) ,
904+ B ( U16 , u8 , u8 ) ,
905+ C ( u8 , u8 , U16 ) ,
906+ D ( U16 , U16 ) ,
907+ E ( U32 ) ,
908+ } => false ) ;
909+
910+ // `repr(int)` can pack the discriminant more efficiently
911+ test ! ( #[ repr( u8 ) ] {
912+ A ( u8 , U16 ) ,
913+ } => false ) ;
914+ test ! ( #[ repr( u8 ) ] {
915+ A ( u8 , U16 , U32 ) ,
916+ } => false ) ;
917+
918+ // `repr(C)` cannot
919+ test ! ( #[ repr( u8 , C ) ] {
920+ A ( u8 , U16 ) ,
921+ } => true ) ;
922+ test ! ( #[ repr( u8 , C ) ] {
923+ A ( u8 , u8 , u8 , U32 ) ,
924+ } => true ) ;
925+
926+ // And field ordering can always cause problems
927+ test ! ( #[ repr( u8 ) ] #[ repr( C ) ] {
928+ A ( U16 , u8 ) ,
929+ } => true ) ;
930+ test ! ( #[ repr( u8 ) ] #[ repr( C ) ] {
931+ A ( U32 , u8 , u8 , u8 ) ,
932+ } => true ) ;
933+ }
777934}
0 commit comments