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