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}