Skip to main content

ender/
opaque.rs

1use crate::{BitWidth, EncodingError, Signedness};
2use parse_display::Display;
3
4trait Sealed {}
5
6pub(crate) enum Signed {}
7pub(crate) enum Unsigned {}
8
9#[allow(private_bounds)]
10pub(crate) trait Sign: Sealed {
11    type Sign;
12}
13
14macro_rules! sign_impl {
15    ($($ty:ty => $ret:ident);* $(;)?) => {
16	    $(
17	    impl Sealed for $ty {}
18	    impl Sign for $ty {
19		    type Sign = $ret;
20	    }
21	    )*
22    };
23}
24
25sign_impl! {
26    u8 => Unsigned;
27    u16 => Unsigned;
28    u32 => Unsigned;
29    u64 => Unsigned;
30    u128 => Unsigned;
31    usize => Unsigned;
32    i8 => Signed;
33    i16 => Signed;
34    i32 => Signed;
35    i64 => Signed;
36    i128 => Signed;
37    isize => Signed;
38}
39
40#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
41#[non_exhaustive]
42enum OpaqueInner {
43    #[display("U-{0}")]
44    Unsigned(u128),
45    #[display("I-{0}")]
46    Signed(i128),
47}
48
49/// An opaque integer type, used to represent enum variants,
50/// `usize` and `isize`.
51///
52/// Supports converting to and from all integer primitive types.
53///
54/// Conversion errors where the value wouldn't fit in the requested
55/// type, are coerced to [`EncodingError`]s
56/// 
57/// **Beware of providing an [`Opaque`] with an integer literal without a specific type!**
58/// 
59/// By default, rust will infer `i32` as the type, thus it will be converted to a *Signed*
60/// enum variant value, and you will get a (bad) surprise when you try to then encode/decode
61/// an enum that uses *Unsigned* variant discriminants (most enums).
62/// 
63/// How to avoid this?
64/// - Write the type in the num literal E.G. `Opaque::from(3u32)` or `Opaque::from(3 as u32)`
65/// - Better yet, use an explicit opaque value ([`Opaque::signed`], [`Opaque::unsigned`])
66#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
67#[repr(transparent)]
68pub struct Opaque(OpaqueInner);
69
70impl Opaque {
71	/// Constructs a new opaque integer, enforcing at compile time that the number type is **signed**
72	#[allow(private_bounds)]
73	#[inline]
74	pub fn signed<V>(value: V) -> Self
75	where V: Sign<Sign = Signed>,
76	      Self: From<V>
77	{
78		Self::from(value)
79	}
80
81	/// Constructs a new opaque integer, enforcing at compile time that the number type is **unsigned**
82	#[allow(private_bounds)]
83	#[inline]
84	pub fn unsigned<V>(value: V) -> Self
85	where V: Sign<Sign = Unsigned>,
86	      Self: From<V>
87	{
88		Self::from(value)
89	}
90	
91    /// Returns the [`Signedness`] of the opaque integer.
92    #[inline]
93    pub fn sign(&self) -> Signedness {
94        match self.0 {
95            OpaqueInner::Unsigned(_) => Signedness::Unsigned,
96            OpaqueInner::Signed(_) => Signedness::Signed,
97        }
98    }
99}
100
101/* Int-to-Variant conversions always succeed */
102
103macro_rules! from_impl {
104    ($($ty:ty => $var:ident);* $(;)?) => {
105	    $(
106	    impl From<$ty> for Opaque {
107			#[inline]
108			fn from(value: $ty) -> Self {
109				Self(OpaqueInner::$var(value as _))
110			}
111		}
112	    )*
113    };
114}
115
116from_impl! {
117    u8 => Unsigned;
118    u16 => Unsigned;
119    u32 => Unsigned;
120    u64 => Unsigned;
121    u128 => Unsigned;
122    usize => Unsigned;
123    i8 => Signed;
124    i16 => Signed;
125    i32 => Signed;
126    i64 => Signed;
127    i128 => Signed;
128    isize => Signed;
129}
130
131/* Variant-to-Int conversions are fallible, because the integer might not fit */
132
133macro_rules! into_impl {
134    ($($($width:tt)::* => $ty:ty => $var:ident != $isnt:ident);* $(;)?) => {
135	    $(
136	    impl TryInto<$ty> for Opaque {
137			type Error = EncodingError;
138			#[inline]
139			fn try_into(self) -> Result<$ty, Self::Error> {
140				match self.0 {
141					OpaqueInner::$var(x)  => {
142						x.try_into().map_err(|_| EncodingError::TooLarge {
143							value: self,
144							requested_width: BitWidth::$($width)*,
145						})
146					},
147					OpaqueInner::$isnt(_) => Err(EncodingError::SignMismatch {
148						expected: Signedness::$var,
149						got: Signedness::$isnt,
150					}),
151				}
152			}
153		}
154	    )*
155    };
156}
157
158into_impl! {
159    Bit8 => u8 => Unsigned != Signed;
160    Bit16 => u16 => Unsigned != Signed;
161    Bit32 => u32 => Unsigned != Signed;
162    Bit64 => u64 => Unsigned != Signed;
163    Bit128 => u128 => Unsigned != Signed;
164    native::() => usize => Unsigned != Signed;
165    Bit8 => i8 => Signed != Unsigned;
166    Bit16 => i16 => Signed != Unsigned;
167    Bit32 => i32 => Signed != Unsigned;
168    Bit64 => i64 => Signed != Unsigned;
169    Bit128 => i128 => Signed != Unsigned;
170    native::() => isize => Signed != Unsigned;
171}