diff --git a/const-type-layout-derive/src/lib.rs b/const-type-layout-derive/src/lib.rs index db59873..53858bf 100644 --- a/const-type-layout-derive/src/lib.rs +++ b/const-type-layout-derive/src/lib.rs @@ -532,7 +532,7 @@ fn quote_enum_variants( let discriminant = quote! { #variant_inhabited.map( - #crate_path::Discriminant::new(#discriminant) + #crate_path::discriminant!(#discriminant) ) }; diff --git a/src/discriminant.rs b/src/discriminant.rs new file mode 100644 index 0000000..380d7f4 --- /dev/null +++ b/src/discriminant.rs @@ -0,0 +1,247 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +/// Discriminant of an enum variant. +/// +/// The [`Discriminant`] value can be constructed using the [`discriminant!`] +/// macro. +/// +/// [`discriminant!`]: crate::discriminant! +pub struct Discriminant<'a> { + /// The numeric value of the discriminant. + /// + /// The value is stored in [negabinary] representation in little-endian + /// order as a byte array. Unlike the two's complement representation, + /// [negabinary] has a unique representation for signed and unsigned + /// numbers that is independent of the size of the value type (e.g. [`i16`] + /// vs [`u64`]). + /// + /// Specifically, + /// - the `(-2)^0`th digit is stored in the least significant bit of + /// `value[0]` + /// - the `(-2)^7`th digit is stored in the most significant bit of + /// `value[0]` + /// - the `(-2)^8`th digit is stored in the least significant bit of + /// `value[1]` + /// - for all `i >= value.len()`, `value[i]` is implicitly all-zero + /// + /// [negabinary]: https://oeis.org/wiki/Negabinary + #[cfg_attr(feature = "serde", serde(borrow))] + pub value: &'a [u8], +} + +/// Helper macro to construct a [`Discriminant`] from its constant value. +/// +/// The [`discriminant!`] macro is invoked with the constant expression value +/// of the discriminant, e.g. +/// +/// [`discriminant!`]: crate::discriminant! +/// +/// ``` +/// # use const_type_layout::{discriminant, Discriminant}; +/// // unsigned literal with inferred type +/// const D1: Discriminant = discriminant!(4); +/// +/// // signed literal with inferred type +/// const D2: Discriminant = discriminant!(-2); +/// +/// // unsigned literal with explicit type +/// const D3: Discriminant = discriminant!(2_u8); +/// +/// // signed literal with explicit type +/// const D4: Discriminant = discriminant!(-4_i128); +/// +/// // constant expression with inferred type +/// const D5: Discriminant = discriminant!(-4 + 7); +/// +/// // constant value +/// const VALUE: isize = 42; +/// const D6: Discriminant = discriminant!(VALUE); +/// ``` +#[macro_export] +macro_rules! discriminant { + // https://oeis.org/wiki/Negabinary + // https://en.wikipedia.org/wiki/Negative_base#To_negabinary + ($x:expr) => {{ + const NBYTES: usize = { + let mut x = $x; + + // number of bits required to represent x in negabinary + let mut nbits = 0; + + // has the sign of the input been flipped? + // since we cannot multiply by -1 if the input is unsigned, we keep + // track of sign flips instead + let mut flipped_sign = false; + + while x != 0 { + nbits += 1; + + // x.div_floor(2) without requring type annotations + let (x_div_2, x_mod_2) = (x / 2, x % 2); + let floor_x_2 = if x_mod_2 < 0 { x_div_2 - 1 } else { x_div_2 }; + + // x := (x / -2) + ((x % -2) < 1) + // where x's sign is flipped before or after + if x_mod_2 == 0 { + x = floor_x_2; + } else if flipped_sign { + x = floor_x_2 + 1; + } else { + x = floor_x_2; + } + + // dividing by -2 will flip the sign + flipped_sign = !flipped_sign; + } + + // round up to the number of bytes required for x in negabinary + (nbits + 7) / 8 + }; + + const NEGABINARY: [u8; NBYTES] = { + let mut x = $x; + + // little endian byte array of the negabinary representation of x + let mut bytes = [0; NBYTES]; + // current index into bytes + let mut i = 0; + // current bit mask for bytes[i] + let mut bit = 0x1_u8; + + // has the sign of the input been flipped? + // since we cannot multiply by -1 if the input is unsigned, we keep + // track of sign flips instead + let mut flipped_sign = false; + + while x != 0 { + // if x is odd, output a 1 to the bit array + if (x % 2) != 0 { + bytes[i] |= bit; + } + + // rotate the bit mask left, and increment i if it wraps around + bit = bit.rotate_left(1); + if bit == 0x1_u8 { + i += 1; + } + + // x.div_floor(2) without requring type annotations + let floor_x_2 = if (x % 2) < 0 { (x / 2) - 1 } else { x / 2 }; + + // x := (x / -2) + ((x % -2) < 1) + // where x's sign is flipped before or after + if (x % 2) == 0 { + x = floor_x_2; + } else if flipped_sign { + x = floor_x_2 + 1; + } else { + x = floor_x_2; + } + + // dividing by -2 will flip the sign + flipped_sign = !flipped_sign; + } + + bytes + }; + + // const-construct the discriminant + $crate::Discriminant { value: &NEGABINARY } + }}; +} + +#[cfg(test)] +mod tests { + #[test] + fn negabinary() { + macro_rules! check { + ($($n:expr => [$($c:literal),*]),*) => { + $( + assert_eq!( + crate::discriminant!($n), crate::Discriminant { + value: &[$($c),*], + }, "wrong negabinary for {}", $n, + ); + )* + }; + } + + // https://oeis.org/A053985 + // https://oeis.org/A053985/b053985.txt + check! { + 0 => [], + 1 => [1], + -2 => [2], + -1 => [3], + 4 => [4], + 5 => [5], + 2 => [6], + 3 => [7], + -8 => [8], + -7 => [9], + -10 => [10], + -9 => [11], + -4 => [12], + -3 => [13], + -6 => [14], + -5 => [15], + 16 => [16], + 17 => [17], + 14 => [18], + 15 => [19], + 20 => [20], + 21 => [21], + 18 => [22], + 19 => [23], + 8 => [24], + 9 => [25], + 6 => [26], + 7 => [27], + 12 => [28], + 13 => [29], + 10 => [30], + 11 => [31], + -32 => [32], + -31 => [33], + -34 => [34], + -33 => [35], + -28 => [36], + -27 => [37], + -30 => [38], + -29 => [39], + -40 => [40], + -39 => [41], + -42 => [42], + -41 => [43], + -36 => [44], + -35 => [45], + -38 => [46], + -37 => [47], + -16 => [48], + -15 => [49], + -18 => [50], + -17 => [51], + -12 => [52], + -11 => [53], + -14 => [54], + -13 => [55], + -24 => [56], + -23 => [57], + -26 => [58], + -25 => [59], + -20 => [60], + -19 => [61], + -22 => [62], + -21 => [63], + 64 => [64], + // ... + 256 => [0, 1], + 257 => [1, 1], + 255 => [3, 1], + -256 => [0, 3], + -255 => [1, 3], + -257 => [3, 3] + // ... + } + } +} diff --git a/src/impls/core/cmp.rs b/src/impls/core/cmp.rs index b3d4c46..dd35acf 100644 --- a/src/impls/core/cmp.rs +++ b/src/impls/core/cmp.rs @@ -35,17 +35,17 @@ unsafe impl TypeLayout for core::cmp::Ordering { variants: &[ Variant { name: "Less", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(-1)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(-1)), fields: &[], }, Variant { name: "Equal", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(0)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(0)), fields: &[], }, Variant { name: "Greater", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::I8(1)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(1)), fields: &[], }, ], diff --git a/src/impls/core/ops.rs b/src/impls/core/ops.rs index ffd3085..5038ec8 100644 --- a/src/impls/core/ops.rs +++ b/src/impls/core/ops.rs @@ -122,7 +122,7 @@ unsafe impl TypeLayout for core::ops::Bound { variants: &[ Variant { name: "Included", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -133,7 +133,7 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Excluded", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -144,7 +144,7 @@ unsafe impl TypeLayout for core::ops::Bound { }, Variant { name: "Unbounded", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::Isize(2)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(2)), fields: &[], }, ], @@ -169,7 +169,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< variants: &[ Variant { name: "Continue", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!( @@ -180,7 +180,7 @@ unsafe impl TypeLayout for core::ops::ControlFlow< }, Variant { name: "Break", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Break.0)), diff --git a/src/impls/core/option.rs b/src/impls/core/option.rs index 82307f6..bb47a2e 100644 --- a/src/impls/core/option.rs +++ b/src/impls/core/option.rs @@ -14,12 +14,12 @@ unsafe impl TypeLayout for core::option::Option { variants: &[ Variant { name: "None", - discriminant: MaybeUninhabited::Inhabited(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::Inhabited(crate::discriminant!(0)), fields: &[], }, Variant { name: "Some", - discriminant: MaybeUninhabited::new::(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Some.0)), diff --git a/src/impls/core/result.rs b/src/impls/core/result.rs index 364a976..3df460a 100644 --- a/src/impls/core/result.rs +++ b/src/impls/core/result.rs @@ -14,7 +14,7 @@ unsafe impl TypeLayout for core::result::Result(crate::Discriminant::Isize(0)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(0)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Ok.0)), @@ -23,7 +23,7 @@ unsafe impl TypeLayout for core::result::Result(crate::Discriminant::Isize(1)), + discriminant: MaybeUninhabited::new::(crate::discriminant!(1)), fields: &[Field { name: "0", offset: MaybeUninhabited::new::(::core::mem::offset_of!(Self, Err.0)), diff --git a/src/lib.rs b/src/lib.rs index e5fd6fe..cb2b177 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,11 +158,14 @@ use core::ops::Deref; #[cfg(feature = "derive")] pub use const_type_layout_derive::TypeLayout; +mod discriminant; mod impls; pub mod inhabited; mod ser; pub mod typeset; +pub use discriminant::Discriminant; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize))] #[cfg_attr(feature = "serde", derive(::serde::Deserialize))] @@ -419,122 +422,12 @@ pub struct Variant<'a, F: Deref]> = &'a [Field<'a>]> { pub name: &'a str, /// The variant's descriminant, iff the variant is /// [inhabited](https://doc.rust-lang.org/reference/glossary.html#inhabited). - pub discriminant: MaybeUninhabited, + #[cfg_attr(feature = "serde", serde(borrow))] + pub discriminant: MaybeUninhabited>, /// The variant's fields. pub fields: F, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", allow(clippy::unsafe_derive_deserialize))] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -/// Discriminant value of a type. -pub enum Discriminant { - /// `#[repr(i8)]` discriminant. - I8(i8), - /// `#[repr(i16)]` discriminant. - I16(i16), - /// `#[repr(i32)]` discriminant. - I32(i32), - /// `#[repr(i64)]` discriminant. - I64(i64), - /// `#[repr(i128)]` discriminant. - I128(i128), - /// `#[repr(isize)]` discriminant (default). - Isize(isize), - /// `#[repr(u8)]` discriminant. - U8(u8), - /// `#[repr(u16)]` discriminant. - U16(u16), - /// `#[repr(u32)]` discriminant. - U32(u32), - /// `#[repr(u64)]` discriminant. - U64(u64), - /// `#[repr(u128)]` discriminant. - U128(u128), - /// `#[repr(usize)]` discriminant. - Usize(usize), -} - -impl Discriminant { - #[allow(clippy::missing_panics_doc)] - #[must_use] - /// Constructs a [`Discriminant`] value with the given value `v`. - /// - /// `T` must be a valid discriminant kind type, i.e. one of the variants - /// that [`Discriminant`] can represent. - pub const fn new(v: T) -> Self { - // TODO: can this constructor be written without panic or specialisation - #[repr(C)] - union Transmute { - v: T, - i8: i8, - i16: i16, - i32: i32, - i64: i64, - i128: i128, - isize: isize, - u8: u8, - u16: u16, - u32: u32, - u64: u64, - u128: u128, - usize: usize, - } - - if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i8 - return Self::I8(unsafe { Transmute { v }.i8 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i16 - return Self::I16(unsafe { Transmute { v }.i16 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i32 - return Self::I32(unsafe { Transmute { v }.i32 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i64 - return Self::I64(unsafe { Transmute { v }.i64 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to i128 - return Self::I128(unsafe { Transmute { v }.i128 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to isize - return Self::Isize(unsafe { Transmute { v }.isize }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u8 - return Self::U8(unsafe { Transmute { v }.u8 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u16 - return Self::U16(unsafe { Transmute { v }.u16 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u32 - return Self::U32(unsafe { Transmute { v }.u32 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u64 - return Self::U64(unsafe { Transmute { v }.u64 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to u128 - return Self::U128(unsafe { Transmute { v }.u128 }); - } else if >::EQ { - // SAFETY: v is of type T::TY which is equivalent to usize - return Self::Usize(unsafe { Transmute { v }.usize }); - } - - panic!("bug: unknown discriminant kind") - } -} - -trait Same { - const EQ: bool; -} - -impl Same for T { - default const EQ: bool = false; -} - -impl Same for T { - const EQ: bool = true; -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] /// Descriptor of the shallow layout of a field. diff --git a/src/ser.rs b/src/ser.rs index 244779e..b325a1c 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,6 +1,5 @@ use crate::{ - Discriminant, Field, MaybeUninhabited, TypeLayout, TypeLayoutGraph, TypeLayoutInfo, - TypeStructure, Variant, + Discriminant, Field, MaybeUninhabited, TypeLayoutGraph, TypeLayoutInfo, TypeStructure, Variant, }; pub enum Serialiser<'a> { @@ -131,56 +130,28 @@ impl Serialiser<'_> { } const fn serialise_discriminant_bytes(&mut self, value_bytes: &[u8]) { - let mut leading_zeroes = 0; + let mut trailing_zeroes = 0; - while leading_zeroes < value_bytes.len() { - if value_bytes[leading_zeroes] != 0_u8 { + while trailing_zeroes < value_bytes.len() { + if value_bytes[value_bytes.len() - 1 - trailing_zeroes] != 0_u8 { break; } - leading_zeroes += 1; + trailing_zeroes += 1; } - self.serialise_usize(value_bytes.len() - leading_zeroes); + self.serialise_usize(value_bytes.len() - trailing_zeroes); - let mut i = leading_zeroes; + let mut i = 0; - while i < value_bytes.len() { + while i < (value_bytes.len() - trailing_zeroes) { self.write_byte(value_bytes[i]); i += 1; } } pub const fn serialise_discriminant(&mut self, value: &Discriminant) { - match value { - Discriminant::I8(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I16(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I32(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I64(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::I128(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::Isize(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U8(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U16(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U32(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U64(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::U128(_) => self.serialise_str(::TYPE_LAYOUT.name), - Discriminant::Usize(_) => self.serialise_str(::TYPE_LAYOUT.name), - }; - - match value { - Discriminant::I8(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I16(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I32(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I64(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::I128(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::Isize(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U8(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U16(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U32(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U64(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::U128(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - Discriminant::Usize(v) => self.serialise_discriminant_bytes(&v.to_be_bytes()), - }; + self.serialise_discriminant_bytes(value.value); } pub const fn serialise_field(&mut self, value: &Field) { diff --git a/src/typeset.rs b/src/typeset.rs index 5c4b9e6..6becc0e 100644 --- a/src/typeset.rs +++ b/src/typeset.rs @@ -82,7 +82,7 @@ pub unsafe trait ComputeTypeSet: crate::TypeLayout { /// type links to. /// /// Enums implementing [`crate::TypeLayout`] and [`ComputeTypeSet`] - /// manually should include [`crate::ExtractDiscriminant::Discriminant`] in + /// manually should include [`core::mem::Discriminant`] in /// their [`ComputeTypeSet::Output`] using the [`tset`] helper macro. type Output: ExpandTypeSet; }