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 ops::{self, Deref},
11 ptr::{self, NonNull},
12};
13
14#[cfg(any(feature = "serialize", test))]
15use oxc_estree::{ESTree, Serializer as ESTreeSerializer};
16#[cfg(any(feature = "serialize", test))]
17use serde::{Serialize, Serializer as SerdeSerializer};
18
19use crate::Allocator;
20
21/// A `Box` without [`Drop`], which stores its data in the arena allocator.
22///
23/// # No `Drop`s
24///
25/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop). Memory is released in bulk
26/// when the allocator is dropped, without dropping the individual objects in the arena.
27///
28/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
29/// which own memory allocations outside the arena.
30///
31/// Static checks make this impossible to do. [`Box::new_in`] will refuse to compile if called
32/// with a [`Drop`] type.
33#[repr(transparent)]
34pub struct Box<'alloc, T: ?Sized>(NonNull<T>, PhantomData<(&'alloc (), T)>);
35
36impl<T: ?Sized> Box<'_, T> {
37 /// Const assertion that `T` is not `Drop`.
38 /// Must be referenced in all methods which create a `Box`.
39 const ASSERT_T_IS_NOT_DROP: () =
40 assert!(!std::mem::needs_drop::<T>(), "Cannot create a Box<T> where T is a Drop type");
41}
42
43impl<T> Box<'_, T> {
44 /// Put a `value` into a memory arena and get back a [`Box`] with ownership
45 /// to the allocation.
46 ///
47 /// # Examples
48 /// ```
49 /// use oxc_allocator::{Allocator, Box};
50 ///
51 /// let arena = Allocator::default();
52 /// let in_arena: Box<i32> = Box::new_in(5, &arena);
53 /// ```
54 //
55 // `#[inline(always)]` because this is a hot path and `Allocator::alloc` is a very small function.
56 // We always want it to be inlined.
57 #[expect(clippy::inline_always)]
58 #[inline(always)]
59 pub fn new_in(value: T, allocator: &Allocator) -> Self {
60 const { Self::ASSERT_T_IS_NOT_DROP };
61
62 Self(NonNull::from(allocator.alloc(value)), PhantomData)
63 }
64
65 /// Create a fake [`Box`] with a dangling pointer.
66 ///
67 /// # SAFETY
68 /// Safe to create, but must never be dereferenced, as does not point to a valid `T`.
69 /// Only purpose is for mocking types without allocating for const assertions.
70 pub const unsafe fn dangling() -> Self {
71 // SAFETY: None of `from_non_null`'s invariants are satisfied, but caller promises
72 // never to dereference the `Box`
73 unsafe { Self::from_non_null(ptr::NonNull::dangling()) }
74 }
75
76 /// Take ownership of the value stored in this [`Box`], consuming the box in
77 /// the process.
78 ///
79 /// # Examples
80 /// ```
81 /// use oxc_allocator::{Allocator, Box};
82 ///
83 /// let arena = Allocator::default();
84 ///
85 /// // Put `5` into the arena and on the heap.
86 /// let boxed: Box<i32> = Box::new_in(5, &arena);
87 /// // Move it back to the stack. `boxed` has been consumed.
88 /// let i = boxed.unbox();
89 ///
90 /// assert_eq!(i, 5);
91 /// ```
92 #[inline]
93 pub fn unbox(self) -> T {
94 // SAFETY:
95 // This pointer read is safe because the reference `self.0` is
96 // guaranteed to be unique - not just now, but we're guaranteed it's not
97 // borrowed from some other reference. This in turn is because we never
98 // construct a `Box` with a borrowed reference, only with a fresh
99 // one just allocated from a `Bump`.
100 unsafe { ptr::read(self.0.as_ptr()) }
101 }
102}
103
104impl<T: ?Sized> Box<'_, T> {
105 /// Get a [`NonNull`] pointer pointing to the [`Box`]'s contents.
106 ///
107 /// The pointer is not valid for writes.
108 ///
109 /// The caller must ensure that the `Box` outlives the pointer this
110 /// function returns, or else it will end up dangling.
111 ///
112 /// # Example
113 ///
114 /// ```
115 /// use oxc_allocator::{Allocator, Box};
116 ///
117 /// let allocator = Allocator::new();
118 /// let boxed = Box::new_in(123_u64, &allocator);
119 /// let ptr = Box::as_non_null(&boxed);
120 /// ```
121 //
122 // `#[inline(always)]` because this is a no-op
123 #[expect(clippy::inline_always)]
124 #[inline(always)]
125 pub fn as_non_null(boxed: &Self) -> NonNull<T> {
126 boxed.0
127 }
128
129 /// Consume a [`Box`] and return a [`NonNull`] pointer to its contents.
130 //
131 // `#[inline(always)]` because this is a no-op
132 #[expect(clippy::inline_always, clippy::needless_pass_by_value)]
133 #[inline(always)]
134 pub fn into_non_null(boxed: Self) -> NonNull<T> {
135 boxed.0
136 }
137
138 /// Create a [`Box`] from a [`NonNull`] pointer.
139 ///
140 /// # SAFETY
141 ///
142 /// * Pointer must point to a valid `T`.
143 /// * Pointer must point to within an `Allocator`.
144 /// * Caller must ensure that the pointer is valid for the lifetime of the `Box`.
145 pub const unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
146 const { Self::ASSERT_T_IS_NOT_DROP };
147
148 Self(ptr, PhantomData)
149 }
150}
151
152impl<T: ?Sized> ops::Deref for Box<'_, T> {
153 type Target = T;
154
155 #[inline]
156 fn deref(&self) -> &T {
157 // SAFETY: self.0 is always a unique reference allocated from a Bump in Box::new_in
158 unsafe { self.0.as_ref() }
159 }
160}
161
162impl<T: ?Sized> ops::DerefMut for Box<'_, T> {
163 #[inline]
164 fn deref_mut(&mut self) -> &mut T {
165 // SAFETY: self.0 is always a unique reference allocated from a Bump in Box::new_in
166 unsafe { self.0.as_mut() }
167 }
168}
169
170impl<T: ?Sized> AsRef<T> for Box<'_, T> {
171 #[inline]
172 fn as_ref(&self) -> &T {
173 self
174 }
175}
176
177impl<T: ?Sized> AsMut<T> for Box<'_, T> {
178 #[inline]
179 fn as_mut(&mut self) -> &mut T {
180 self
181 }
182}
183
184impl<T: ?Sized + Display> Display for Box<'_, T> {
185 #[inline]
186 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187 self.deref().fmt(f)
188 }
189}
190
191impl<T: ?Sized + Debug> Debug for Box<'_, T> {
192 #[inline]
193 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
194 self.deref().fmt(f)
195 }
196}
197
198// Unused right now.
199// impl<'alloc, T> PartialEq for Box<'alloc, T>
200// where
201// T: PartialEq<T> + ?Sized,
202// {
203// fn eq(&self, other: &Box<'alloc, T>) -> bool {
204// PartialEq::eq(&**self, &**other)
205// }
206// }
207
208#[cfg(any(feature = "serialize", test))]
209impl<T: Serialize> Serialize for Box<'_, T> {
210 fn serialize<S: SerdeSerializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
211 self.deref().serialize(serializer)
212 }
213}
214
215#[cfg(any(feature = "serialize", test))]
216impl<T: ESTree> ESTree for Box<'_, T> {
217 fn serialize<S: ESTreeSerializer>(&self, serializer: S) {
218 self.deref().serialize(serializer);
219 }
220}
221
222impl<T: Hash> Hash for Box<'_, T> {
223 #[inline]
224 fn hash<H: Hasher>(&self, state: &mut H) {
225 self.deref().hash(state);
226 }
227}
228
229#[cfg(test)]
230mod test {
231 use std::hash::{DefaultHasher, Hash, Hasher};
232
233 use super::Box;
234 use crate::Allocator;
235
236 #[test]
237 fn box_deref_mut() {
238 let allocator = Allocator::default();
239 let mut b = Box::new_in("x", &allocator);
240 let b = &mut *b;
241 *b = allocator.alloc("v");
242 assert_eq!(*b, "v");
243 }
244
245 #[test]
246 fn box_debug() {
247 let allocator = Allocator::default();
248 let b = Box::new_in("x", &allocator);
249 let b = format!("{b:?}");
250 assert_eq!(b, "\"x\"");
251 }
252
253 #[test]
254 fn box_hash() {
255 fn hash(val: &impl Hash) -> u64 {
256 let mut hasher = DefaultHasher::default();
257 val.hash(&mut hasher);
258 hasher.finish()
259 }
260
261 let allocator = Allocator::default();
262 let a = Box::new_in("x", &allocator);
263 let b = Box::new_in("x", &allocator);
264
265 assert_eq!(hash(&a), hash(&b));
266 }
267
268 #[test]
269 fn box_serialize() {
270 let allocator = Allocator::default();
271 let b = Box::new_in("x", &allocator);
272 let s = serde_json::to_string(&b).unwrap();
273 assert_eq!(s, r#""x""#);
274 }
275
276 #[test]
277 fn box_serialize_estree() {
278 use oxc_estree::{CompactTSSerializer, ESTree};
279
280 let allocator = Allocator::default();
281 let b = Box::new_in("x", &allocator);
282
283 let mut serializer = CompactTSSerializer::default();
284 b.serialize(&mut serializer);
285 let s = serializer.into_string();
286 assert_eq!(s, r#""x""#);
287 }
288
289 #[test]
290 fn lifetime_variance() {
291 fn _assert_box_variant_lifetime<'a: 'b, 'b, T>(program: Box<'a, T>) -> Box<'b, T> {
292 program
293 }
294 }
295}