vortex_dtype/
ptype.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Physical type definitions and behavior.
5
6use std::cmp::Ordering;
7use std::fmt::{Debug, Display, Formatter};
8use std::hash::Hash;
9use std::ops::AddAssign;
10use std::panic::RefUnwindSafe;
11
12use num_traits::bounds::UpperBounded;
13use num_traits::{AsPrimitive, Bounded, Num, NumCast, PrimInt, ToPrimitive, Unsigned};
14use vortex_error::{VortexError, VortexResult, vortex_err};
15
16use crate::half::f16;
17use crate::nullability::Nullability::NonNullable;
18use crate::{DType, FromPrimitiveOrF16};
19
20/// Physical type enum, represents the in-memory physical layout but might represent a different logical type.
21#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash, prost::Enumeration)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
24#[repr(u8)]
25pub enum PType {
26    /// An 8-bit unsigned integer
27    U8 = 0,
28    /// A 16-bit unsigned integer
29    U16 = 1,
30    /// A 32-bit unsigned integer
31    U32 = 2,
32    /// A 64-bit unsigned integer
33    U64 = 3,
34    /// An 8-bit signed integer
35    I8 = 4,
36    /// A 16-bit signed integer
37    I16 = 5,
38    /// A 32-bit signed integer
39    I32 = 6,
40    /// A 64-bit signed integer
41    I64 = 7,
42    /// A 16-bit floating point number
43    F16 = 8,
44    /// A 32-bit floating point number
45    F32 = 9,
46    /// A 64-bit floating point number
47    F64 = 10,
48}
49
50/// Trait for integer primitive types that can be used as indices, offsets, or codes.
51///
52/// Includes all signed and unsigned integer types (u8, u16, u32, u64, i8, i16, i32, i64).
53///
54/// You can use the `match_each_integer_ptype` macro to help with writing "generic" code over
55/// dynamically typed code.
56pub trait IntegerPType:
57    NativePType + PrimInt + ToPrimitive + Bounded + AddAssign + AsPrimitive<usize>
58{
59    /// Returns the maximum offset value that can be represented by this type.
60    fn max_value_as_u64() -> u64 {
61        Self::PTYPE.max_value_as_u64()
62    }
63}
64
65/// Implements [`IntegerPType`] for all possible `T` that have the correct bounds.
66impl<T> IntegerPType for T where
67    T: NativePType + PrimInt + ToPrimitive + Bounded + AddAssign + AsPrimitive<usize>
68{
69}
70
71/// Trait for unsigned integer primitive types used where non-negative values are required.
72///
73/// Includes only unsigned integer types (u8, u16, u32, u64).
74///
75/// You can use the `match_each_unsigned_integer_ptype` macro to help with writing "generic" code
76/// over dynamically typed code.
77pub trait UnsignedPType: IntegerPType + Unsigned {}
78
79/// Implements [`UnsignedPType`] for all possible `T` that have the correct bounds.
80impl<T> UnsignedPType for T where T: IntegerPType + Unsigned {}
81
82/// A trait for native Rust types that correspond 1:1 to a PType.
83///
84/// You can use the `match_each_native_ptype` macro to help with writing "generic" code over
85/// dynamically typed code.
86pub trait NativePType:
87    Send
88    + Sync
89    + Clone
90    + Copy
91    + Debug
92    + Display
93    + Default
94    + RefUnwindSafe
95    + Num
96    + NumCast
97    + FromPrimitiveOrF16
98    + ToBytes
99    + TryFromBytes
100    + private::Sealed
101    + 'static
102{
103    /// The PType that corresponds to this native type
104    const PTYPE: PType;
105
106    /// Whether this instance (`self`) is NaN
107    /// For integer types, this is always `false`
108    fn is_nan(self) -> bool;
109
110    /// Whether this instance (`self`) is Infinite
111    /// For integer types, this is always `false`
112    fn is_infinite(self) -> bool;
113
114    /// Compare another instance of this type to `self`, providing a total ordering
115    fn total_compare(self, other: Self) -> Ordering;
116
117    /// Test whether self is less than or equal to the other
118    #[inline]
119    fn is_le(self, other: Self) -> bool {
120        self.total_compare(other).is_le()
121    }
122
123    /// Test whether self is less than the other
124    #[inline]
125    fn is_lt(self, other: Self) -> bool {
126        self.total_compare(other).is_lt()
127    }
128
129    /// Test whether self is greater than or equal to the other
130    #[inline]
131    fn is_ge(self, other: Self) -> bool {
132        self.total_compare(other).is_ge()
133    }
134
135    /// Test whether self is greater than the other
136    #[inline]
137    fn is_gt(self, other: Self) -> bool {
138        self.total_compare(other).is_gt()
139    }
140
141    /// Whether another instance of this type (`other`) is bitwise equal to `self`
142    fn is_eq(self, other: Self) -> bool;
143
144    /// Downcast the provided object to a type-specific instance.
145    fn downcast<V: PTypeDowncast>(visitor: V) -> V::Output<Self>;
146
147    /// Upcast a type-specific instance to a generic instance.
148    fn upcast<V: PTypeUpcast>(input: V::Input<Self>) -> V;
149}
150
151mod private {
152    use half::f16;
153
154    /// A private trait to prevent external implementations of `NativePType`.
155    pub trait Sealed {}
156
157    impl Sealed for u8 {}
158    impl Sealed for u16 {}
159    impl Sealed for u32 {}
160    impl Sealed for u64 {}
161    impl Sealed for i8 {}
162    impl Sealed for i16 {}
163    impl Sealed for i32 {}
164    impl Sealed for i64 {}
165    impl Sealed for f16 {}
166    impl Sealed for f32 {}
167    impl Sealed for f64 {}
168}
169
170/// A visitor trait for converting a `NativePType` to another parameterized type.
171#[allow(missing_docs)] // Kind of obvious.
172pub trait PTypeDowncast {
173    type Output<T: NativePType>;
174
175    fn into_u8(self) -> Self::Output<u8>;
176    fn into_u16(self) -> Self::Output<u16>;
177    fn into_u32(self) -> Self::Output<u32>;
178    fn into_u64(self) -> Self::Output<u64>;
179    fn into_i8(self) -> Self::Output<i8>;
180    fn into_i16(self) -> Self::Output<i16>;
181    fn into_i32(self) -> Self::Output<i32>;
182    fn into_i64(self) -> Self::Output<i64>;
183    fn into_f16(self) -> Self::Output<f16>;
184    fn into_f32(self) -> Self::Output<f32>;
185    fn into_f64(self) -> Self::Output<f64>;
186}
187
188/// Extension trait to provide generic downcasting for [`PTypeDowncast`].
189pub trait PTypeDowncastExt: PTypeDowncast {
190    /// Downcast the object to a specific primitive type.
191    fn downcast<T: NativePType>(self) -> Self::Output<T>
192    where
193        Self: Sized,
194    {
195        T::downcast(self)
196    }
197}
198
199impl<T: PTypeDowncast> PTypeDowncastExt for T {}
200
201macro_rules! impl_ptype_downcast {
202    ($T:ty) => {
203        #[inline]
204        fn downcast<V: PTypeDowncast>(visitor: V) -> V::Output<Self> {
205            paste::paste! { visitor.[<into_ $T>]() }
206        }
207
208        #[inline]
209        fn upcast<V: PTypeUpcast>(input: V::Input<Self>) -> V {
210            paste::paste! { V::[<from_ $T>](input) }
211        }
212    };
213}
214
215/// A visitor trait for converting a generic `NativePType` into a non-parameterized type.
216#[allow(missing_docs)] // Kind of obvious.
217pub trait PTypeUpcast {
218    type Input<T: NativePType>;
219
220    fn from_u8(input: Self::Input<u8>) -> Self;
221    fn from_u16(input: Self::Input<u16>) -> Self;
222    fn from_u32(input: Self::Input<u32>) -> Self;
223    fn from_u64(input: Self::Input<u64>) -> Self;
224    fn from_i8(input: Self::Input<i8>) -> Self;
225    fn from_i16(input: Self::Input<i16>) -> Self;
226    fn from_i32(input: Self::Input<i32>) -> Self;
227    fn from_i64(input: Self::Input<i64>) -> Self;
228    fn from_f16(input: Self::Input<f16>) -> Self;
229    fn from_f32(input: Self::Input<f32>) -> Self;
230    fn from_f64(input: Self::Input<f64>) -> Self;
231}
232
233macro_rules! native_ptype {
234    ($T:ty, $ptype:tt) => {
235        impl crate::NativeDType for $T {
236            fn dtype() -> DType {
237                DType::Primitive(PType::$ptype, crate::Nullability::NonNullable)
238            }
239        }
240
241        impl NativePType for $T {
242            const PTYPE: PType = PType::$ptype;
243
244            #[inline]
245            fn is_nan(self) -> bool {
246                false
247            }
248
249            #[inline]
250            fn is_infinite(self) -> bool {
251                false
252            }
253
254            #[inline]
255            fn total_compare(self, other: Self) -> Ordering {
256                self.cmp(&other)
257            }
258
259            #[inline]
260            fn is_eq(self, other: Self) -> bool {
261                self == other
262            }
263
264            impl_ptype_downcast!($T);
265        }
266    };
267}
268
269macro_rules! native_float_ptype {
270    ($T:ty, $ptype:tt) => {
271        impl crate::NativeDType for $T {
272            fn dtype() -> DType {
273                DType::Primitive(PType::$ptype, crate::Nullability::NonNullable)
274            }
275        }
276
277        impl NativePType for $T {
278            const PTYPE: PType = PType::$ptype;
279
280            #[inline]
281            fn is_nan(self) -> bool {
282                <$T>::is_nan(self)
283            }
284
285            #[inline]
286            fn is_infinite(self) -> bool {
287                <$T>::is_infinite(self)
288            }
289
290            #[inline]
291            fn total_compare(self, other: Self) -> Ordering {
292                self.total_cmp(&other)
293            }
294
295            #[inline]
296            fn is_eq(self, other: Self) -> bool {
297                self.to_bits() == other.to_bits()
298            }
299
300            impl_ptype_downcast!($T);
301        }
302    };
303}
304
305native_ptype!(u8, U8);
306native_ptype!(u16, U16);
307native_ptype!(u32, U32);
308native_ptype!(u64, U64);
309native_ptype!(i8, I8);
310native_ptype!(i16, I16);
311native_ptype!(i32, I32);
312native_ptype!(i64, I64);
313native_float_ptype!(f16, F16);
314native_float_ptype!(f32, F32);
315native_float_ptype!(f64, F64);
316
317/// Macro to match over each PType, binding the corresponding native type (from `NativePType`)
318#[macro_export]
319macro_rules! match_each_native_ptype {
320    (
321        $self:expr,integral: |
322        $integral_enc:ident |
323        $intbody:block,floating: |
324        $floating_point_enc:ident |
325        $floatbody:block
326    ) => {{
327        use $crate::PType;
328        use $crate::half::f16;
329        match $self {
330            PType::I8 => {
331                type $integral_enc = i8;
332                $intbody
333            }
334            PType::I16 => {
335                type $integral_enc = i16;
336                $intbody
337            }
338            PType::I32 => {
339                type $integral_enc = i32;
340                $intbody
341            }
342            PType::I64 => {
343                type $integral_enc = i64;
344                $intbody
345            }
346            PType::U8 => {
347                type $integral_enc = u8;
348                $intbody
349            }
350            PType::U16 => {
351                type $integral_enc = u16;
352                $intbody
353            }
354            PType::U32 => {
355                type $integral_enc = u32;
356                $intbody
357            }
358            PType::U64 => {
359                type $integral_enc = u64;
360                $intbody
361            }
362            PType::F16 => {
363                type $floating_point_enc = f16;
364                $floatbody
365            }
366            PType::F32 => {
367                type $floating_point_enc = f32;
368                $floatbody
369            }
370            PType::F64 => {
371                type $floating_point_enc = f64;
372                $floatbody
373            }
374        }
375    }};
376    (
377        $self:expr,unsigned: |
378        $unsigned_enc:ident |
379        $unsigned_body:block,signed: |
380        $signed_enc:ident |
381        $signed_body:block,floating: |
382        $floating_point_enc:ident |
383        $floating_point_body:block
384    ) => {{
385        use $crate::PType;
386        use $crate::half::f16;
387        match $self {
388            PType::U8 => {
389                type $unsigned_enc = u8;
390                $unsigned_body
391            }
392            PType::U16 => {
393                type $unsigned_enc = u16;
394                $unsigned_body
395            }
396            PType::U32 => {
397                type $unsigned_enc = u32;
398                $unsigned_body
399            }
400            PType::U64 => {
401                type $unsigned_enc = u64;
402                $unsigned_body
403            }
404            PType::I8 => {
405                type $signed_enc = i8;
406                $signed_body
407            }
408            PType::I16 => {
409                type $signed_enc = i16;
410                $signed_body
411            }
412            PType::I32 => {
413                type $signed_enc = i32;
414                $signed_body
415            }
416            PType::I64 => {
417                type $signed_enc = i64;
418                $signed_body
419            }
420            PType::F16 => {
421                type $floating_point_enc = f16;
422                $floating_point_body
423            }
424            PType::F32 => {
425                type $floating_point_enc = f32;
426                $floating_point_body
427            }
428            PType::F64 => {
429                type $floating_point_enc = f64;
430                $floating_point_body
431            }
432        }
433    }};
434    ($self:expr, | $tname:ident | $body:block) => {{
435        use $crate::PType;
436        use $crate::half::f16;
437        match $self {
438            PType::I8 => {
439                type $tname = i8;
440                $body
441            }
442            PType::I16 => {
443                type $tname = i16;
444                $body
445            }
446            PType::I32 => {
447                type $tname = i32;
448                $body
449            }
450            PType::I64 => {
451                type $tname = i64;
452                $body
453            }
454            PType::U8 => {
455                type $tname = u8;
456                $body
457            }
458            PType::U16 => {
459                type $tname = u16;
460                $body
461            }
462            PType::U32 => {
463                type $tname = u32;
464                $body
465            }
466            PType::U64 => {
467                type $tname = u64;
468                $body
469            }
470            PType::F16 => {
471                type $tname = f16;
472                $body
473            }
474            PType::F32 => {
475                type $tname = f32;
476                $body
477            }
478            PType::F64 => {
479                type $tname = f64;
480                $body
481            }
482        }
483    }};
484}
485
486/// Macro to match over each integer PType, binding the corresponding native type (from `NativePType`)
487#[macro_export]
488macro_rules! match_each_integer_ptype {
489    ($self:expr, | $enc:ident | $body:block) => {{
490        use $crate::PType;
491        match $self {
492            PType::I8 => {
493                type $enc = i8;
494                $body
495            }
496            PType::I16 => {
497                type $enc = i16;
498                $body
499            }
500            PType::I32 => {
501                type $enc = i32;
502                $body
503            }
504            PType::I64 => {
505                type $enc = i64;
506                $body
507            }
508            PType::U8 => {
509                type $enc = u8;
510                $body
511            }
512            PType::U16 => {
513                type $enc = u16;
514                $body
515            }
516            PType::U32 => {
517                type $enc = u32;
518                $body
519            }
520            PType::U64 => {
521                type $enc = u64;
522                $body
523            }
524            other => panic!("Unsupported ptype {other}"),
525        }
526    }};
527}
528
529/// Macro to match over each unsigned integer type, binding the corresponding native type (from `NativePType`)
530#[macro_export]
531macro_rules! match_each_unsigned_integer_ptype {
532    ($self:expr, | $enc:ident | $body:block) => {{
533        use $crate::PType;
534        match $self {
535            PType::U8 => {
536                type $enc = u8;
537                $body
538            }
539            PType::U16 => {
540                type $enc = u16;
541                $body
542            }
543            PType::U32 => {
544                type $enc = u32;
545                $body
546            }
547            PType::U64 => {
548                type $enc = u64;
549                $body
550            }
551            other => panic!("Unsupported ptype {other}"),
552        }
553    }};
554}
555
556/// Macro to match over each signed integer type, binding the corresponding native type (from `NativePType`)
557#[macro_export]
558macro_rules! match_each_signed_integer_ptype {
559    ($self:expr, | $enc:ident | $body:block) => {{
560        use $crate::PType;
561        match $self {
562            PType::I8 => {
563                type $enc = i8;
564                $body
565            }
566            PType::I16 => {
567                type $enc = i16;
568                $body
569            }
570            PType::I32 => {
571                type $enc = i32;
572                $body
573            }
574            PType::I64 => {
575                type $enc = i64;
576                $body
577            }
578            other => panic!("Unsupported ptype {other}"),
579        }
580    }};
581}
582
583/// Macro to match over each floating point type, binding the corresponding native type (from `NativePType`)
584#[macro_export]
585macro_rules! match_each_float_ptype {
586    ($self:expr, | $enc:ident | $body:block) => {{
587        use vortex_dtype::half::f16;
588        use $crate::PType;
589        match $self {
590            PType::F16 => {
591                type $enc = f16;
592                $body
593            }
594            PType::F32 => {
595                type $enc = f32;
596                $body
597            }
598            PType::F64 => {
599                type $enc = f64;
600                $body
601            }
602            other => panic!("Unsupported ptype {other}"),
603        }
604    }};
605}
606
607/// Macro to match over each SIMD capable `PType`, binding the corresponding native type (from `NativePType`)
608///
609/// Note: The match will panic in case of `PType::F16`.
610#[macro_export]
611macro_rules! match_each_native_simd_ptype {
612    ($self:expr, | $enc:ident | $body:block) => {{
613        use $crate::PType;
614        match $self {
615            PType::I8 => {
616                type $enc = i8;
617                $body
618            }
619            PType::I16 => {
620                type $enc = i16;
621                $body
622            }
623            PType::I32 => {
624                type $enc = i32;
625                $body
626            }
627            PType::I64 => {
628                type $enc = i64;
629                $body
630            }
631            PType::U8 => {
632                type $enc = u8;
633                $body
634            }
635            PType::U16 => {
636                type $enc = u16;
637                $body
638            }
639            PType::U32 => {
640                type $enc = u32;
641                $body
642            }
643            PType::U64 => {
644                type $enc = u64;
645                $body
646            }
647            PType::F16 => panic!("f16 does not implement simd::SimdElement"),
648            PType::F32 => {
649                type $enc = f32;
650                $body
651            }
652            PType::F64 => {
653                type $enc = f64;
654                $body
655            }
656        }
657    }};
658}
659
660impl PType {
661    /// Returns `true` iff this PType is an unsigned integer type
662    #[inline]
663    pub const fn is_unsigned_int(self) -> bool {
664        matches!(self, Self::U8 | Self::U16 | Self::U32 | Self::U64)
665    }
666
667    /// Returns `true` iff this PType is a signed integer type
668    #[inline]
669    pub const fn is_signed_int(self) -> bool {
670        matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64)
671    }
672
673    /// Returns `true` iff this PType is an integer type
674    /// Equivalent to `self.is_unsigned_int() || self.is_signed_int()`
675    #[inline]
676    pub const fn is_int(self) -> bool {
677        self.is_unsigned_int() || self.is_signed_int()
678    }
679
680    /// Returns `true` iff this PType is a floating point type
681    #[inline]
682    pub const fn is_float(self) -> bool {
683        matches!(self, Self::F16 | Self::F32 | Self::F64)
684    }
685
686    /// Returns the number of bytes in this PType
687    #[inline]
688    pub const fn byte_width(&self) -> usize {
689        match_each_native_ptype!(self, |T| { size_of::<T>() })
690    }
691
692    /// Returns the number of bits in this PType
693    #[inline]
694    pub const fn bit_width(&self) -> usize {
695        self.byte_width() * 8
696    }
697
698    /// Returns the maximum value of this PType if it is an integer type
699    /// Returns `u64::MAX` if the value is too large to fit in a `u64`
700    #[inline]
701    pub fn max_value_as_u64(&self) -> u64 {
702        match_each_native_ptype!(self, |T| {
703            <T as UpperBounded>::max_value()
704                .to_u64()
705                .unwrap_or(u64::MAX)
706        })
707    }
708
709    /// Returns the PType that corresponds to the signed version of this PType
710    #[inline]
711    pub const fn to_signed(self) -> Self {
712        match self {
713            Self::U8 => Self::I8,
714            Self::U16 => Self::I16,
715            Self::U32 => Self::I32,
716            Self::U64 => Self::I64,
717            Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::F16 | Self::F32 | Self::F64 => {
718                self
719            }
720        }
721    }
722
723    /// Returns the PType that corresponds to the unsigned version of this PType
724    /// For floating point types, this will simply return `self`
725    #[inline]
726    pub const fn to_unsigned(self) -> Self {
727        match self {
728            Self::I8 => Self::U8,
729            Self::I16 => Self::U16,
730            Self::I32 => Self::U32,
731            Self::I64 => Self::U64,
732            Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::F16 | Self::F32 | Self::F64 => {
733                self
734            }
735        }
736    }
737}
738
739impl Display for PType {
740    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
741        match self {
742            Self::U8 => write!(f, "u8"),
743            Self::U16 => write!(f, "u16"),
744            Self::U32 => write!(f, "u32"),
745            Self::U64 => write!(f, "u64"),
746            Self::I8 => write!(f, "i8"),
747            Self::I16 => write!(f, "i16"),
748            Self::I32 => write!(f, "i32"),
749            Self::I64 => write!(f, "i64"),
750            Self::F16 => write!(f, "f16"),
751            Self::F32 => write!(f, "f32"),
752            Self::F64 => write!(f, "f64"),
753        }
754    }
755}
756
757impl TryFrom<&DType> for PType {
758    type Error = VortexError;
759
760    #[inline]
761    fn try_from(value: &DType) -> VortexResult<Self> {
762        if let DType::Primitive(p, _) = value {
763            Ok(*p)
764        } else {
765            Err(vortex_err!("Cannot convert DType {} into PType", value))
766        }
767    }
768}
769
770impl From<PType> for &DType {
771    fn from(item: PType) -> Self {
772        // We expand this match statement so that we can return a static reference.
773        match item {
774            PType::I8 => &DType::Primitive(PType::I8, NonNullable),
775            PType::I16 => &DType::Primitive(PType::I16, NonNullable),
776            PType::I32 => &DType::Primitive(PType::I32, NonNullable),
777            PType::I64 => &DType::Primitive(PType::I64, NonNullable),
778            PType::U8 => &DType::Primitive(PType::U8, NonNullable),
779            PType::U16 => &DType::Primitive(PType::U16, NonNullable),
780            PType::U32 => &DType::Primitive(PType::U32, NonNullable),
781            PType::U64 => &DType::Primitive(PType::U64, NonNullable),
782            PType::F16 => &DType::Primitive(PType::F16, NonNullable),
783            PType::F32 => &DType::Primitive(PType::F32, NonNullable),
784            PType::F64 => &DType::Primitive(PType::F64, NonNullable),
785        }
786    }
787}
788
789impl From<PType> for DType {
790    fn from(item: PType) -> Self {
791        DType::Primitive(item, NonNullable)
792    }
793}
794
795/// A trait for types that can be converted to a little-endian byte slice
796pub trait ToBytes: Sized {
797    /// Returns a slice of this type's bytes in little-endian order
798    fn to_le_bytes(&self) -> &[u8];
799}
800
801/// A trait for types that can be converted from a little-endian byte slice
802pub trait TryFromBytes: Sized {
803    /// Attempts to convert a slice of bytes in little-endian order to this type
804    fn try_from_le_bytes(bytes: &[u8]) -> VortexResult<Self>;
805}
806
807macro_rules! try_from_bytes {
808    ($T:ty) => {
809        impl ToBytes for $T {
810            #[inline]
811            #[allow(clippy::size_of_in_element_count)]
812            fn to_le_bytes(&self) -> &[u8] {
813                // NOTE(ngates): this assumes the platform is little-endian. Currently enforced
814                //  with a flag cfg(target_endian = "little")
815                let raw_ptr = self as *const $T as *const u8;
816                unsafe { std::slice::from_raw_parts(raw_ptr, std::mem::size_of::<$T>()) }
817            }
818        }
819
820        impl TryFromBytes for $T {
821            fn try_from_le_bytes(bytes: &[u8]) -> VortexResult<Self> {
822                Ok(<$T>::from_le_bytes(bytes.try_into().map_err(|_| {
823                    vortex_err!("Failed to convert bytes into {}", stringify!($T))
824                })?))
825            }
826        }
827    };
828}
829
830try_from_bytes!(u8);
831try_from_bytes!(u16);
832try_from_bytes!(u32);
833try_from_bytes!(u64);
834try_from_bytes!(i8);
835try_from_bytes!(i16);
836try_from_bytes!(i32);
837try_from_bytes!(i64);
838try_from_bytes!(f16);
839try_from_bytes!(f32);
840try_from_bytes!(f64);
841
842/// A trait that allows conversion from a PType to its physical representation (i.e., unsigned)
843pub trait PhysicalPType: NativePType {
844    /// The physical type that corresponds to this native type.
845    type Physical: NativePType + Unsigned;
846}
847
848macro_rules! physical_ptype {
849    ($T:ty, $U:ty) => {
850        impl PhysicalPType for $T {
851            type Physical = $U;
852        }
853    };
854}
855
856physical_ptype!(i8, u8);
857physical_ptype!(i16, u16);
858physical_ptype!(i32, u32);
859physical_ptype!(i64, u64);
860physical_ptype!(u8, u8);
861physical_ptype!(u16, u16);
862physical_ptype!(u32, u32);
863physical_ptype!(u64, u64);
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868
869    #[test]
870    fn try_from_bytes() {
871        assert_eq!(u8::try_from_le_bytes(&[0x01]).unwrap(), 0x01);
872        assert_eq!(u16::try_from_le_bytes(&[0x01, 0x02]).unwrap(), 0x0201);
873        assert_eq!(
874            u32::try_from_le_bytes(&[0x01, 0x02, 0x03, 0x04]).unwrap(),
875            0x04030201
876        );
877        assert_eq!(
878            u64::try_from_le_bytes(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]).unwrap(),
879            0x0807060504030201
880        );
881    }
882
883    #[test]
884    fn to_bytes_rt() {
885        assert_eq!(&0x01u8.to_le_bytes(), &[0x01]);
886        assert_eq!(&0x0201u16.to_le_bytes(), &[0x01, 0x02]);
887
888        assert_eq!(u8::try_from_le_bytes(&42_u8.to_le_bytes()).unwrap(), 42);
889        assert_eq!(u16::try_from_le_bytes(&42_u16.to_le_bytes()).unwrap(), 42);
890        assert_eq!(u32::try_from_le_bytes(&42_u32.to_le_bytes()).unwrap(), 42);
891        assert_eq!(u64::try_from_le_bytes(&42_u64.to_le_bytes()).unwrap(), 42);
892        assert_eq!(i8::try_from_le_bytes(&42_i8.to_le_bytes()).unwrap(), 42);
893        assert_eq!(i16::try_from_le_bytes(&42_i16.to_le_bytes()).unwrap(), 42);
894        assert_eq!(i32::try_from_le_bytes(&42_i32.to_le_bytes()).unwrap(), 42);
895        assert_eq!(i64::try_from_le_bytes(&42_i64.to_le_bytes()).unwrap(), 42);
896        assert_eq!(
897            f16::try_from_le_bytes(&f16::from_f32(42.0).to_le_bytes()).unwrap(),
898            f16::from_f32(42.0)
899        );
900        assert_eq!(
901            f32::try_from_le_bytes(&42.0_f32.to_le_bytes()).unwrap(),
902            42.0
903        );
904        assert_eq!(
905            f64::try_from_le_bytes(&42.0_f64.to_le_bytes()).unwrap(),
906            42.0
907        );
908    }
909
910    #[test]
911    fn max_value_u64() {
912        assert_eq!(PType::U8.max_value_as_u64(), u8::MAX as u64);
913        assert_eq!(PType::U16.max_value_as_u64(), u16::MAX as u64);
914        assert_eq!(PType::U32.max_value_as_u64(), u32::MAX as u64);
915        assert_eq!(PType::U64.max_value_as_u64(), u64::MAX);
916        assert_eq!(PType::I8.max_value_as_u64(), i8::MAX as u64);
917        assert_eq!(PType::I16.max_value_as_u64(), i16::MAX as u64);
918        assert_eq!(PType::I32.max_value_as_u64(), i32::MAX as u64);
919        assert_eq!(PType::I64.max_value_as_u64(), i64::MAX as u64);
920        assert_eq!(PType::F16.max_value_as_u64(), 65504); // f16 is a weird type...
921        assert_eq!(PType::F32.max_value_as_u64(), u64::MAX);
922        assert_eq!(PType::F64.max_value_as_u64(), u64::MAX);
923    }
924
925    #[test]
926    fn widths() {
927        assert_eq!(PType::U8.byte_width(), 1);
928        assert_eq!(PType::U16.byte_width(), 2);
929        assert_eq!(PType::U32.byte_width(), 4);
930        assert_eq!(PType::U64.byte_width(), 8);
931        assert_eq!(PType::I8.byte_width(), 1);
932        assert_eq!(PType::I16.byte_width(), 2);
933        assert_eq!(PType::I32.byte_width(), 4);
934        assert_eq!(PType::I64.byte_width(), 8);
935        assert_eq!(PType::F16.byte_width(), 2);
936        assert_eq!(PType::F32.byte_width(), 4);
937        assert_eq!(PType::F64.byte_width(), 8);
938
939        assert_eq!(PType::U8.bit_width(), 8);
940        assert_eq!(PType::U16.bit_width(), 16);
941        assert_eq!(PType::U32.bit_width(), 32);
942        assert_eq!(PType::U64.bit_width(), 64);
943        assert_eq!(PType::I8.bit_width(), 8);
944        assert_eq!(PType::I16.bit_width(), 16);
945        assert_eq!(PType::I32.bit_width(), 32);
946        assert_eq!(PType::I64.bit_width(), 64);
947        assert_eq!(PType::F16.bit_width(), 16);
948        assert_eq!(PType::F32.bit_width(), 32);
949        assert_eq!(PType::F64.bit_width(), 64);
950    }
951
952    #[test]
953    fn native_ptype_nan_handling() {
954        let a = f32::NAN;
955        let b = f32::NAN;
956        assert_ne!(a, b);
957        assert!(<f32 as NativePType>::is_nan(a));
958        assert!(<f32 as NativePType>::is_nan(b));
959        assert!(<f32 as NativePType>::is_eq(a, b));
960        assert!(<f32 as NativePType>::total_compare(a, b) == Ordering::Equal);
961    }
962
963    #[test]
964    fn to_signed() {
965        assert_eq!(PType::U8.to_signed(), PType::I8);
966        assert_eq!(PType::U16.to_signed(), PType::I16);
967        assert_eq!(PType::U32.to_signed(), PType::I32);
968        assert_eq!(PType::U64.to_signed(), PType::I64);
969        assert_eq!(PType::I8.to_signed(), PType::I8);
970        assert_eq!(PType::I16.to_signed(), PType::I16);
971        assert_eq!(PType::I32.to_signed(), PType::I32);
972        assert_eq!(PType::I64.to_signed(), PType::I64);
973        assert_eq!(PType::F16.to_signed(), PType::F16);
974        assert_eq!(PType::F32.to_signed(), PType::F32);
975        assert_eq!(PType::F64.to_signed(), PType::F64);
976    }
977
978    #[test]
979    fn to_unsigned() {
980        assert_eq!(PType::U8.to_unsigned(), PType::U8);
981        assert_eq!(PType::U16.to_unsigned(), PType::U16);
982        assert_eq!(PType::U32.to_unsigned(), PType::U32);
983        assert_eq!(PType::U64.to_unsigned(), PType::U64);
984        assert_eq!(PType::I8.to_unsigned(), PType::U8);
985        assert_eq!(PType::I16.to_unsigned(), PType::U16);
986        assert_eq!(PType::I32.to_unsigned(), PType::U32);
987        assert_eq!(PType::I64.to_unsigned(), PType::U64);
988        assert_eq!(PType::F16.to_unsigned(), PType::F16);
989        assert_eq!(PType::F32.to_unsigned(), PType::F32);
990        assert_eq!(PType::F64.to_unsigned(), PType::F64);
991    }
992
993    #[test]
994    fn to_dtype() {
995        assert_eq!(
996            DType::from(PType::U8),
997            DType::Primitive(PType::U8, NonNullable)
998        );
999        assert_eq!(
1000            DType::from(PType::U16),
1001            DType::Primitive(PType::U16, NonNullable)
1002        );
1003        assert_eq!(
1004            DType::from(PType::U32),
1005            DType::Primitive(PType::U32, NonNullable)
1006        );
1007        assert_eq!(
1008            DType::from(PType::U64),
1009            DType::Primitive(PType::U64, NonNullable)
1010        );
1011        assert_eq!(
1012            DType::from(PType::I8),
1013            DType::Primitive(PType::I8, NonNullable)
1014        );
1015        assert_eq!(
1016            DType::from(PType::I16),
1017            DType::Primitive(PType::I16, NonNullable)
1018        );
1019        assert_eq!(
1020            DType::from(PType::I32),
1021            DType::Primitive(PType::I32, NonNullable)
1022        );
1023        assert_eq!(
1024            DType::from(PType::I64),
1025            DType::Primitive(PType::I64, NonNullable)
1026        );
1027        assert_eq!(
1028            DType::from(PType::F16),
1029            DType::Primitive(PType::F16, NonNullable)
1030        );
1031        assert_eq!(
1032            DType::from(PType::F32),
1033            DType::Primitive(PType::F32, NonNullable)
1034        );
1035        assert_eq!(
1036            DType::from(PType::F64),
1037            DType::Primitive(PType::F64, NonNullable)
1038        );
1039    }
1040}