dungeon_cell/
compile_time.rs

1//! Compile time utilities.
2//!
3//! # Compile Time Assertions
4//!
5//! By default, [`dungeon_cell`][crate] will generate compile time panics if
6//! an assertion made with this module fails. These panics will happen when rustc
7//! attempts to instantiate associated const values. This **does not** happen when
8//! running `cargo check`. They will only happen when actually fully building a project.
9//! This limitation is because generics can't be used in normal const expressions
10//! (see the [tracking issue](https://github.com/rust-lang/rust/issues/76560) for `generic_const_exprs`).
11//!
12//! The resulting compiler errors don't have any information on where the
13//! failing assert is located. The environment variable
14//! `DUNGEON_CELL_RUNTIME_CHECKS` can be enabled (it can have any value) while
15//! building [`dungeon_cell`][crate] to have asserts made with this module generate runtime
16//! panics with a backtrace instead.
17//!
18//! Example of enabling the environment variable using a `.config/cargo.toml`.
19//! ```toml
20//! [env]
21//! DUNGEON_CELL_RUNTIME_CHECKS = "true"
22//! ```
23//!
24//! ### Example Assertion Failure
25//! ```compile_fail
26//! use dungeon_cell::compile_time::{SmallerOrEqualTo, UsizeAssert};
27//! use dungeon_cell::Size;
28//!
29//! SmallerOrEqualTo::<u16, Size<1>>::assert();
30//! ```
31//! Output:
32//! ```text
33//! error[E0080]: evaluation of `<compile_time::SmallerOrEqualTo<u16, layout::Size<1>> as compile_time::UsizeAssert>::FAILED` failed
34//!   --> src/compile_time.rs:40:26
35//!    |
36//! 40 |     const FAILED: bool = do_usize_const_assert::<Self>(PanicMode::Monomorphized);
37//!    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at '
38//!
39//! assertion failed: size of type T <= 1, type T has size = 2.
40//!
41//! This error happens after monomorphization so the location of the code that caused
42//! this to happen is not known. Add the `DUNGEON_CELL_RUNTIME_CHECKS` environment
43//! variable when compiling to enable runtime panics with a backtrace.
44//!
45//! ', src/compile_time.rs:40:26
46//! ```
47
48mod transmute;
49
50pub use transmute::const_transmute;
51
52use core::marker::PhantomData;
53
54use crate::marker_traits::{IsAlignment, IsSize};
55
56/// Compile time evaluable assert for [`usize`] values.
57///
58/// See [module docs](self#compile-time-assertions) for what happens when an assertion fails.
59///
60/// # Safety
61/// [`Self::assert()`] and [`Self::FAILED`] must keep the default implementation.
62pub unsafe trait UsizeAssert: Sized {
63    /// Type with the property being checked.
64    type Type;
65
66    /// Associated constant used to trigger compile time errors.
67    ///
68    /// By making rustc evaluate this constant, you can trigger a compile time error.
69    ///
70    /// See [module docs](self#compile-time-assertions) for details.
71    const FAILED: bool =
72        do_usize_const_assert::<Self>(PanicMode::Monomorphized);
73
74    /// Left hand side of the comparison.
75    ///
76    /// This is the value the type has.
77    const LHS: usize;
78
79    /// Right hand side of the comparison.
80    ///
81    /// This is the value to check against.
82    const RHS: usize;
83
84    /// Type of comparison to make between [`Self::LHS`] and [`Self::RHS`].
85    const COMPARE: Comparison;
86
87    /// Same of the property being checked.
88    ///
89    /// This is printed in the failure message.
90    const PROPERTY: &'static str;
91
92    /// Assert the defined comparison is true.
93    ///
94    /// # Panics
95    /// This function will panic if the `DUNGEON_CELL_RUNTIME_CHECKS` environment variable is
96    /// set and the assertion fails.
97    /// Otherwise, this function is a no-op for the program.
98    ///
99    /// See [module docs](self#compile-time-assertions) for details.
100    #[track_caller]
101    fn assert() {
102        #[cfg(use_const_assert)]
103        let failed = Self::FAILED;
104
105        #[cfg(not(use_const_assert))]
106        let failed = do_assert::<Self>();
107
108        if failed {
109            panic!("assert failed");
110        }
111    }
112}
113
114/// Type of comparison.
115#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Hash, Eq, Ord)]
116pub enum Comparison {
117    /// `a < b`
118    LessThan,
119
120    /// `a <= b`
121    LessThanEqual,
122
123    /// `a == b`
124    Equal,
125
126    /// `a >= b`
127    GreaterThanEqual,
128
129    /// `a > b`
130    GreaterThan,
131
132    /// `a != b`
133    NotEqual,
134}
135
136/// Const form of [`UsizeAssert::assert()`].
137///
138/// Prefer using [`UsizeAssert::assert()`] when possible because it can give better error
139/// messages.
140///
141/// # Panics
142/// This function will panic if the `DUNGEON_CELL_RUNTIME_CHECKS` environment variable is
143/// set and the assertion fails.
144/// Otherwise, this function is a no-op for the program.
145///
146/// See [module docs](self#compile-time-assertions) for details.
147#[track_caller]
148pub const fn const_usize_assert<T: UsizeAssert>() {
149    #[cfg(use_const_assert)]
150    let failed = T::FAILED;
151
152    #[cfg(not(use_const_assert))]
153    let failed = do_usize_const_assert::<T>(PanicMode::Normal);
154
155    if failed {
156        panic!("assert failed");
157    }
158}
159
160/// Assert size of `T` is smaller or equal to given size.
161///
162/// # Examples
163/// ```
164/// use dungeon_cell::compile_time::{SizeSmallerOrEqualTo, UsizeAssert};
165/// use dungeon_cell::layout::Size;
166///
167/// // this will pass
168/// SizeSmallerOrEqualTo::<u16, Size<4>>::assert();
169/// ```
170/// ```compile_fail
171/// use dungeon_cell::compile_time::{SizeSmallerOrEqualTo, UsizeAssert};
172/// use dungeon_cell::Size;
173///
174/// // this won't pass
175/// SizeSmallerOrEqualTo::<u16, Size<1>>::assert();
176/// ```
177pub struct SizeSmallerOrEqualTo<T, Size>(PhantomData<T>, PhantomData<Size>);
178
179// SAFETY: We don't override the default impls.
180unsafe impl<T, Size: IsSize> UsizeAssert for SizeSmallerOrEqualTo<T, Size> {
181    const LHS: usize = core::mem::size_of::<T>();
182
183    const RHS: usize = Size::VALUE;
184
185    const COMPARE: Comparison = Comparison::LessThanEqual;
186
187    type Type = T;
188
189    const PROPERTY: &'static str = "size";
190}
191
192pub struct AlignmentSmallerOrEqualTo<T, Alignment>(
193    PhantomData<T>,
194    PhantomData<Alignment>,
195);
196
197// SAFETY: We don't override the default impls.
198unsafe impl<T, Alignment: IsAlignment> UsizeAssert
199    for AlignmentSmallerOrEqualTo<T, Alignment>
200{
201    const LHS: usize = core::mem::align_of::<T>();
202
203    const RHS: usize = Alignment::VALUE;
204
205    const COMPARE: Comparison = Comparison::LessThanEqual;
206
207    type Type = T;
208
209    const PROPERTY: &'static str = "alignment";
210}
211
212#[track_caller]
213const fn do_usize_const_assert<T: UsizeAssert>(mode: PanicMode) -> bool {
214    let (passed, symbol) = {
215        let a = T::LHS;
216        let b = T::RHS;
217
218        match T::COMPARE {
219            Comparison::LessThan => (a < b, " < "),
220            Comparison::LessThanEqual => (a <= b, " <= "),
221            Comparison::Equal => (a == b, " == "),
222            Comparison::GreaterThanEqual => (a >= b, " >= "),
223            Comparison::GreaterThan => (a > b, " > "),
224            Comparison::NotEqual => (a != b, " != "),
225        }
226    };
227
228    if passed {
229        false
230    } else {
231        #[cfg(has_const_type_name)]
232        let type_name = core::any::type_name::<T::Type>();
233
234        #[cfg(not(has_const_type_name))]
235        let type_name = "type T";
236
237        let property_name = T::PROPERTY;
238        let value = T::RHS;
239        let type_value = T::LHS;
240
241        // assertion failed: size of T <= 1. T has size 4.
242        match mode {
243            PanicMode::Normal => {
244                const_panic::concat_panic!(
245                    "assertion failed: ",
246                    display: property_name,
247                    " of ",
248                    display: type_name,
249                    display: symbol,
250                    value,
251                    ", ",
252                    display: type_name,
253                    " has ",
254                    display: property_name,
255                    " = ",
256                    type_value,
257                    "."
258                );
259            }
260            PanicMode::Monomorphized => {
261                const_panic::concat_panic!(
262                    "\n\nassertion failed: ",
263                    display: property_name,
264                    " of ",
265                    display: type_name,
266                    display: symbol,
267                    value,
268                    ", ",
269                    display: type_name,
270                    " has ",
271                    display: property_name,
272                    " = ",
273                    type_value,
274                    ".\n\nThis error happens after monomorphization so \
275                    the location of the code that caused this to happen is not known. \
276                    Add the `DUNGEON_CELL_RUNTIME_CHECKS` environment variable \
277                    when compiling to enable runtime panics with a backtrace.\n\n"
278                );
279            }
280        }
281    }
282}
283
284#[track_caller]
285#[cfg(not(use_const_assert))]
286fn do_assert<T: UsizeAssert>() -> bool {
287    let (passed, symbol) = {
288        let a = T::LHS;
289        let b = T::RHS;
290
291        match T::COMPARE {
292            Comparison::LessThan => (a < b, " < "),
293            Comparison::LessThanEqual => (a <= b, " <= "),
294            Comparison::Equal => (a == b, " == "),
295            Comparison::GreaterThanEqual => (a >= b, " >= "),
296            Comparison::GreaterThan => (a > b, " > "),
297            Comparison::NotEqual => (a != b, " != "),
298        }
299    };
300
301    if passed {
302        false
303    } else {
304        let type_name = core::any::type_name::<T::Type>();
305        let property_name = T::PROPERTY;
306        let value = T::RHS;
307        let type_value = T::LHS;
308
309        panic!(
310            "assertion failed: {property_name} of \
311            {type_name}{symbol}{value}. {type_name} \
312            has {property_name} = {type_value}.",
313        );
314    }
315}
316
317#[allow(dead_code)]
318enum PanicMode {
319    Normal,
320    Monomorphized,
321}