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}