oxc_allocator/boxed.rs
1//! Arena Box.
2//!
3//! Originally based on [jsparagus](https://github.com/mozilla-spidermonkey/jsparagus/blob/24004745a8ed4939fc0dc7332bfd1268ac52285f/crates/ast/src/arena.rs)
4
5use std::{
6 self,
7 fmt::{self, Debug, Display, Formatter},
8 hash::{Hash, Hasher},
9 marker::PhantomData,
10 mem,
11 ops::{Deref, DerefMut},
12 ptr::{self, NonNull},
13};
14
15#[cfg(feature = "serialize")]
16use oxc_estree::{ESTree, Serializer as ESTreeSerializer};
17#[cfg(feature = "serialize")]
18use serde::{Serialize, Serializer as SerdeSerializer};
19
20use crate::Allocator;
21
22/// A `Box` without [`Drop`], which stores its data in the arena allocator.
23///
24/// # No `Drop`s
25///
26/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop). Memory is released in bulk
27/// when the allocator is dropped, without dropping the individual objects in the arena.
28///
29/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
30/// which own memory allocations outside the arena.
31///
32/// Static checks make this impossible to do. [`Box::new_in`] will refuse to compile if called
33/// with a [`Drop`] type.
34#[repr(transparent)]
35pub struct Box<'alloc, T: ?Sized>(NonNull<T>, PhantomData<(&'alloc (), T)>);
36
37impl<T: ?Sized> Box<'_, T> {
38 /// Const assertion that `T` is not `Drop`.
39 /// Must be referenced in all methods which create a `Box`.
40 const ASSERT_T_IS_NOT_DROP: () =
41 assert!(!std::mem::needs_drop::<T>(), "Cannot create a Box<T> where T is a Drop type");
42}
43
44impl<T> Box<'_, T> {
45 /// Put a `value` into a memory arena and get back a [`Box`] with ownership
46 /// to the allocation.
47 ///
48 /// # Examples
49 /// ```
50 /// use oxc_allocator::{Allocator, Box};
51 ///
52 /// let arena = Allocator::default();
53 /// let in_arena: Box<i32> = Box::new_in(5, &arena);
54 /// ```
55 //
56 // `#[inline(always)]` because this is a hot path and `Allocator::alloc` is a very small function.
57 // We always want it to be inlined.
58 #[expect(clippy::inline_always)]
59 #[inline(always)]
60 pub fn new_in(value: T, allocator: &Allocator) -> Self {
61 const { Self::ASSERT_T_IS_NOT_DROP };
62
63 Self(NonNull::from(allocator.alloc(value)), PhantomData)
64 }
65
66 /// Create a fake [`Box`] with a dangling pointer.
67 ///
68 /// # SAFETY
69 /// Safe to create, but must never be dereferenced, as does not point to a valid `T`.
70 /// Only purpose is for mocking types without allocating for const assertions.
71 pub const unsafe fn dangling() -> Self {
72 // SAFETY: None of `from_non_null`'s invariants are satisfied, but caller promises
73 // never to dereference the `Box`
74 unsafe { Self::from_non_null(ptr::NonNull::dangling()) }
75 }
76
77 /// Take ownership of the value stored in this [`Box`], consuming the box in
78 /// the process.
79 ///
80 /// # Examples
81 /// ```
82 /// use oxc_allocator::{Allocator, Box};
83 ///
84 /// let arena = Allocator::default();
85 ///
86 /// // Put `5` into the arena and on the heap.
87 /// let boxed: Box<i32> = Box::new_in(5, &arena);
88 /// // Move it back to the stack. `boxed` has been consumed.
89 /// let i = boxed.unbox();
90 ///
91 /// assert_eq!(i, 5);
92 /// ```
93 #[inline]
94 pub fn unbox(self) -> T {
95 // SAFETY:
96 // This pointer read is safe because the reference `self.0` is
97 // guaranteed to be unique - not just now, but we're guaranteed it's not
98 // borrowed from some other reference. This in turn is because we never
99 // construct a `Box` with a borrowed reference, only with a fresh
100 // one just allocated from an `Arena`.
101 unsafe { ptr::read(self.0.as_ptr()) }
102 }
103}
104
105impl<T: ?Sized> Box<'_, T> {
106 /// Get a [`NonNull`] pointer pointing to the [`Box`]'s contents.
107 ///
108 /// The pointer is not valid for writes.
109 ///
110 /// The caller must ensure that the `Box` outlives the pointer this
111 /// function returns, or else it will end up dangling.
112 ///
113 /// # Example
114 ///
115 /// ```
116 /// use oxc_allocator::{Allocator, Box};
117 ///
118 /// let allocator = Allocator::new();
119 /// let boxed = Box::new_in(123_u64, &allocator);
120 /// let ptr = Box::as_non_null(&boxed);
121 /// ```
122 //
123 // `#[inline(always)]` because this is a no-op
124 #[expect(clippy::inline_always)]
125 #[inline(always)]
126 pub fn as_non_null(boxed: &Self) -> NonNull<T> {
127 boxed.0
128 }
129
130 /// Consume a [`Box`] and return a [`NonNull`] pointer to its contents.
131 //
132 // `#[inline(always)]` because this is a no-op
133 #[expect(clippy::inline_always, clippy::needless_pass_by_value)]
134 #[inline(always)]
135 pub fn into_non_null(boxed: Self) -> NonNull<T> {
136 boxed.0
137 }
138
139 /// Create a [`Box`] from a [`NonNull`] pointer.
140 ///
141 /// # SAFETY
142 ///
143 /// * Pointer must point to a valid `T`.
144 /// * Pointer must point to within an `Allocator`.
145 /// * Caller must ensure that the pointer is valid for the lifetime of the `Box`.
146 pub const unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
147 const { Self::ASSERT_T_IS_NOT_DROP };
148
149 Self(ptr, PhantomData)
150 }
151}
152
153impl<T> Box<'static, [T]> {
154 /// Create a new empty `Box<[T]>`.
155 ///
156 /// This method does not allocate. The returned boxed slice is represented by a dangling,
157 /// correctly-aligned pointer with length 0, similar to how `Vec::new_in` produces an empty vector.
158 #[inline]
159 pub fn new_empty_boxed_slice() -> Self {
160 const { Self::ASSERT_T_IS_NOT_DROP };
161
162 // `NonNull::<T>::dangling()` yields a non-null, properly aligned pointer.
163 // We pair it with length 0 to construct a `NonNull<[T]>` representing an empty slice.
164 // Correct alignment is the only requirement for it to be sound to dereference this pointer
165 // to a slice, because the slice is empty.
166 // See: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
167 let ptr = NonNull::dangling();
168 let slice_ptr = NonNull::slice_from_raw_parts(ptr, 0);
169
170 Self(slice_ptr, PhantomData)
171 }
172}
173
174impl<'a, T> Box<'a, [T]> {
175 /// Convert a boxed slice [`Box<[T]>`] into slice [`&'a [T]`].
176 ///
177 /// The returned slice has the same lifetime as the allocator.
178 //
179 // `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&[T]` have the same layout.
180 #[expect(clippy::inline_always)]
181 #[inline(always)]
182 pub fn into_arena_slice(self) -> &'a [T] {
183 let r = self.as_ref();
184 // Extend lifetime of reference to lifetime of the allocator.
185 // SAFETY: `self` is consumed by this method, so there cannot be any mutable references to it.
186 // The reference lives until the allocator is dropped or reset (`'a` lifetime).
187 // Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
188 unsafe { mem::transmute::<&[T], &'a [T]>(r) }
189 }
190
191 /// Convert a boxed slice [`Box<[T]>`] into mutable slice [`&'a mut [T]`].
192 ///
193 /// The returned slice has the same lifetime as the allocator.
194 //
195 // `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&mut [T]` have the same layout.
196 #[expect(clippy::inline_always)]
197 #[inline(always)]
198 pub fn into_arena_slice_mut(mut self) -> &'a mut [T] {
199 let r = self.as_mut();
200 // Extend lifetime of reference to lifetime of the allocator.
201 // SAFETY: `self` is consumed by this method, so there cannot be any other references to it.
202 // The reference lives until the allocator is dropped or reset (`'a` lifetime).
203 // Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
204 unsafe { mem::transmute::<&mut [T], &'a mut [T]>(r) }
205 }
206}
207
208impl<T: ?Sized> Deref for Box<'_, T> {
209 type Target = T;
210
211 #[inline]
212 fn deref(&self) -> &T {
213 // SAFETY: `self.0` is always a unique reference allocated from an `Arena` in `Box::new_in`,
214 // or an empty slice allocated from `Box::new_empty_boxed_slice`
215 unsafe { self.0.as_ref() }
216 }
217}
218
219impl<T: ?Sized> DerefMut for Box<'_, T> {
220 #[inline]
221 fn deref_mut(&mut self) -> &mut T {
222 // SAFETY: `self.0` is always a unique reference allocated from an `Arena` in `Box::new_in`,
223 // or an empty slice allocated from `Box::new_empty_boxed_slice`
224 unsafe { self.0.as_mut() }
225 }
226}
227
228impl<T: ?Sized> AsRef<T> for Box<'_, T> {
229 #[inline]
230 fn as_ref(&self) -> &T {
231 self
232 }
233}
234
235impl<T: ?Sized> AsMut<T> for Box<'_, T> {
236 #[inline]
237 fn as_mut(&mut self) -> &mut T {
238 self
239 }
240}
241
242impl<T: ?Sized + Display> Display for Box<'_, T> {
243 #[inline]
244 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
245 self.deref().fmt(f)
246 }
247}
248
249impl<T: ?Sized + Debug> Debug for Box<'_, T> {
250 #[inline]
251 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
252 self.deref().fmt(f)
253 }
254}
255
256// Unused right now.
257// impl<'alloc, T> PartialEq for Box<'alloc, T>
258// where
259// T: PartialEq<T> + ?Sized,
260// {
261// fn eq(&self, other: &Box<'alloc, T>) -> bool {
262// PartialEq::eq(&**self, &**other)
263// }
264// }
265
266#[cfg(feature = "serialize")]
267impl<T: Serialize> Serialize for Box<'_, T> {
268 fn serialize<S: SerdeSerializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
269 self.deref().serialize(serializer)
270 }
271}
272
273#[cfg(feature = "serialize")]
274impl<T: ESTree> ESTree for Box<'_, T> {
275 fn serialize<S: ESTreeSerializer>(&self, serializer: S) {
276 self.deref().serialize(serializer);
277 }
278}
279
280impl<T: Hash> Hash for Box<'_, T> {
281 #[inline]
282 fn hash<H: Hasher>(&self, state: &mut H) {
283 self.deref().hash(state);
284 }
285}
286
287#[cfg(test)]
288mod test {
289 use std::hash::{DefaultHasher, Hash, Hasher};
290
291 use crate::{Allocator, Vec};
292
293 use super::Box;
294
295 #[test]
296 fn box_deref_mut() {
297 let allocator = Allocator::default();
298 let mut b = Box::new_in("x", &allocator);
299 let b = &mut *b;
300 *b = allocator.alloc("v");
301 assert_eq!(*b, "v");
302 }
303
304 #[test]
305 fn new_empty_boxed_slice() {
306 let b = Box::<[u32]>::new_empty_boxed_slice();
307 assert!(b.is_empty());
308 assert_eq!(b.len(), 0);
309 assert_eq!(&*b, &[] as &[u32]);
310 }
311
312 #[test]
313 fn boxed_slice_into_arena_slice() {
314 let allocator = Allocator::default();
315 let v = Vec::from_iter_in([1, 2, 3], &allocator);
316 let b = v.into_boxed_slice();
317 let slice = b.into_arena_slice();
318 assert_eq!(slice, &[1, 2, 3]);
319 }
320
321 #[test]
322 fn boxed_slice_into_arena_slice_mut() {
323 let allocator = Allocator::default();
324 let v = Vec::from_iter_in([10, 20, 30], &allocator);
325 let b = v.into_boxed_slice();
326 let slice = b.into_arena_slice_mut();
327 slice[1] = 99;
328 assert_eq!(slice, &[10, 99, 30]);
329 }
330
331 #[test]
332 fn box_debug() {
333 let allocator = Allocator::default();
334 let b = Box::new_in("x", &allocator);
335 let b = format!("{b:?}");
336 assert_eq!(b, "\"x\"");
337 }
338
339 #[test]
340 fn box_hash() {
341 fn hash(val: &impl Hash) -> u64 {
342 let mut hasher = DefaultHasher::default();
343 val.hash(&mut hasher);
344 hasher.finish()
345 }
346
347 let allocator = Allocator::default();
348 let a = Box::new_in("x", &allocator);
349 let b = Box::new_in("x", &allocator);
350
351 assert_eq!(hash(&a), hash(&b));
352 }
353
354 #[cfg(feature = "serialize")]
355 #[test]
356 fn box_serialize() {
357 let allocator = Allocator::default();
358 let b = Box::new_in("x", &allocator);
359 let s = serde_json::to_string(&b).unwrap();
360 assert_eq!(s, r#""x""#);
361 }
362
363 #[cfg(feature = "serialize")]
364 #[test]
365 fn box_serialize_estree() {
366 use oxc_estree::{CompactTSSerializer, ESTree};
367
368 let allocator = Allocator::default();
369 let b = Box::new_in("x", &allocator);
370
371 let mut serializer = CompactTSSerializer::default();
372 b.serialize(&mut serializer);
373 let s = serializer.into_string();
374 assert_eq!(s, r#""x""#);
375 }
376
377 #[test]
378 fn lifetime_variance() {
379 fn _assert_box_variant_lifetime<'a: 'b, 'b, T>(program: Box<'a, T>) -> Box<'b, T> {
380 program
381 }
382 }
383}