Skip to content

Commit 021ee00

Browse files
committed
Add support for data-carrying enums
1 parent b37557f commit 021ee00

File tree

16 files changed

+2162
-450
lines changed

16 files changed

+2162
-450
lines changed

.vscode/settings.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
{
2-
"rust-analyzer.cargo.cfgs": {
3-
// This adds an extra cfg to rust-analyzer so we can prevent it from
4-
// analyzing tests with a lot of generated code. These can cause it to
5-
// hang or run slowly in general, which is bad for workflows.
6-
"rust_analyzer": null,
7-
}
8-
}
1+
{
2+
"rust-analyzer.cargo.cfgs": {
3+
// This adds an extra cfg to rust-analyzer so we can prevent it from
4+
// analyzing tests with a lot of generated code. These can cause it to
5+
// hang or run slowly in general, which is bad for workflows.
6+
"rust_analyzer": null,
7+
}
8+
}

src/impls.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1731,7 +1731,15 @@ mod tests {
17311731
Unaligned,
17321732
!FromBytes
17331733
);
1734-
assert_impls!([NotZerocopy]: KnownLayout, !Immutable, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
1734+
assert_impls!(
1735+
[NotZerocopy]: KnownLayout,
1736+
!Immutable,
1737+
!TryFromBytes,
1738+
!FromZeros,
1739+
!FromBytes,
1740+
!IntoBytes,
1741+
!Unaligned
1742+
);
17351743
assert_impls!(
17361744
[u8; 0]: KnownLayout,
17371745
Immutable,

src/macro_util.rs

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
273299
macro_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]
293319
macro_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
}

src/pointer/ptr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,10 +835,12 @@ mod _transitions {
835835
}
836836

837837
/// Recalls that `self`'s referent is bit-valid for `T`.
838+
#[doc(hidden)]
839+
#[must_use]
838840
#[inline]
839841
// TODO(#859): Reconsider the name of this method before making it
840842
// public.
841-
pub(crate) fn bikeshed_recall_valid(self) -> Ptr<'a, T, (I::Aliasing, I::Alignment, Valid)>
843+
pub fn bikeshed_recall_valid(self) -> Ptr<'a, T, (I::Aliasing, I::Alignment, Valid)>
842844
where
843845
T: crate::FromBytes,
844846
I: Invariants<Validity = Initialized>,

0 commit comments

Comments
 (0)