Skip to main content

parlance/
impl_bytemuck.rs

1// Copyright 2025 the Parley Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Optional `bytemuck` trait impls.
5
6#![allow(
7    unsafe_code,
8    reason = "The `bytemuck` marker traits are `unsafe` and require `unsafe impl`."
9)]
10
11use crate::{BidiLevel, GenericFamily, NormalizedCoord};
12use bytemuck::{
13    Contiguous, NoUninit, Pod, TransparentWrapper, Zeroable, checked::CheckedBitPattern,
14};
15
16// Safety: The enum is `repr(u8)` and has only fieldless variants.
17unsafe impl NoUninit for GenericFamily {}
18
19// Safety: The enum is `repr(u8)` and `0` is a valid value.
20unsafe impl Zeroable for GenericFamily {}
21
22// Safety: The enum is `repr(u8)`.
23unsafe impl CheckedBitPattern for GenericFamily {
24    type Bits = u8;
25
26    fn is_valid_bit_pattern(bits: &u8) -> bool {
27        // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
28        *bits <= Self::MAX_VALUE
29    }
30}
31
32// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
33// the min and max values.
34unsafe impl Contiguous for GenericFamily {
35    type Int = u8;
36    const MIN_VALUE: u8 = Self::Serif as u8;
37    #[allow(
38        clippy::use_self,
39        reason = "Using `Self::MAX_VALUE` here would refer to `Contiguous::MAX_VALUE` (self-reference)."
40    )]
41    const MAX_VALUE: u8 = GenericFamily::MAX_VALUE;
42}
43
44// Safety: The struct is `repr(transparent)`, wrapping a `u8`.
45//
46// While generally BidiLevels have a maximum of 125, no value is unsound.
47unsafe impl Pod for BidiLevel {}
48
49// Safety: The struct is `repr(transparent)`, wrapping a `u8`.
50unsafe impl Zeroable for BidiLevel {}
51
52// Safety: The struct is `repr(transparent)`, wrapping an `i16`. All bit patterns are valid.
53unsafe impl Pod for NormalizedCoord {}
54
55// Safety: The struct is `repr(transparent)`, wrapping an `i16`.
56unsafe impl Zeroable for NormalizedCoord {}
57
58// Safety: The struct is `repr(transparent)`, wrapping an `i16`, and has no other fields.
59unsafe impl TransparentWrapper<i16> for NormalizedCoord {}
60
61#[cfg(test)]
62mod tests {
63    use bytemuck::{Contiguous, TransparentWrapper, Zeroable, checked::try_from_bytes};
64    use core::ptr;
65
66    use super::{BidiLevel, GenericFamily, NormalizedCoord};
67
68    #[test]
69    fn checked_bit_pattern() {
70        let valid = bytemuck::bytes_of(&2_u8);
71        let invalid = bytemuck::bytes_of(&200_u8);
72
73        assert_eq!(
74            Ok(&GenericFamily::Monospace),
75            try_from_bytes::<GenericFamily>(valid)
76        );
77
78        assert!(try_from_bytes::<GenericFamily>(invalid).is_err());
79    }
80
81    #[test]
82    fn contiguous() {
83        let hd1 = GenericFamily::SansSerif;
84        let hd2 = GenericFamily::from_integer(hd1.into_integer());
85        assert_eq!(Some(hd1), hd2);
86
87        assert_eq!(None, GenericFamily::from_integer(255));
88    }
89
90    #[test]
91    fn zeroable() {
92        let hd = GenericFamily::zeroed();
93        assert_eq!(hd, GenericFamily::Serif);
94    }
95
96    /// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect.
97    const _: () = {
98        let mut value = 0;
99        while value <= GenericFamily::MAX_VALUE {
100            // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
101            let it: GenericFamily = unsafe { ptr::read((&raw const value).cast()) };
102            // Evaluate the enum value to ensure it actually has a valid tag.
103            if it as u8 != value {
104                unreachable!();
105            }
106            value += 1;
107        }
108    };
109
110    #[test]
111    fn normalized_coord_cast_slice() {
112        let coords = [
113            NormalizedCoord::from_bits(-16384),
114            NormalizedCoord::from_bits(0),
115            NormalizedCoord::from_bits(8192),
116        ];
117        let bits: &[i16] = bytemuck::cast_slice(&coords);
118        assert_eq!(bits, [-16384, 0, 8192]);
119        let back: &[NormalizedCoord] = bytemuck::cast_slice(bits);
120        assert_eq!(back, coords);
121    }
122
123    #[test]
124    fn normalized_coord_transparent_wrapper() {
125        assert_eq!(NormalizedCoord::zeroed(), NormalizedCoord::from_bits(0));
126        let coords = [NormalizedCoord::from_bits(1), NormalizedCoord::from_bits(2)];
127        assert_eq!(NormalizedCoord::peel_slice(&coords), [1, 2]);
128        assert_eq!(
129            NormalizedCoord::wrap_slice(&[3, 4]),
130            [NormalizedCoord::from_bits(3), NormalizedCoord::from_bits(4)]
131        );
132    }
133
134    /// Tests that [`NormalizedCoord`] is two bytes.
135    ///
136    /// That may catch its representation changing, in which case the implementations here
137    /// definitely need revisiting.
138    const _: () = {
139        if size_of::<NormalizedCoord>() != 2 {
140            panic!("`NormalizedCoord` is not two bytes");
141        }
142    };
143
144    /// Tests that [`BidiLevel`] is one byte.
145    ///
146    /// That may catch its representation changing, in which case the implementations here
147    /// definitely need revisiting.
148    const _: () = {
149        if size_of::<BidiLevel>() != 1 {
150            panic!("`BidiLevel` is not one byte");
151        }
152    };
153}
154
155#[cfg(doctest)]
156/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead.
157mod doctests {
158    /// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous`
159    /// impl.
160    ///
161    /// ```compile_fail,E0080
162    /// use bytemuck::Contiguous;
163    /// use parlance::GenericFamily;
164    /// const {
165    ///     let value = GenericFamily::MAX_VALUE + 1;
166    ///     // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
167    ///     // (Indeed, we rely upon that)
168    ///     let it: GenericFamily = unsafe { core::ptr::read((&raw const value).cast()) };
169    ///     // Evaluate the enum value to ensure it actually has an invalid tag.
170    ///     if it as u8 != value {
171    ///         unreachable!();
172    ///     }
173    /// }
174    /// ```
175    const _GENERIC_FAMILY: () = {};
176}