Skip to main content

oxc_allocator/
clone_in.rs

1use std::{
2    alloc::Layout,
3    cell::Cell,
4    hash::{BuildHasher, Hash},
5    mem::MaybeUninit,
6    ptr::NonNull,
7    slice,
8};
9
10use crate::{Allocator, Box, HashMap, Vec};
11
12/// Option to pass to [`CloneIn::clone_in_impl`] to determine how to clone semantic IDs.
13///
14/// * [`CloneInSemanticIds::With`] to clone by copying current value.
15/// * [`CloneInSemanticIds::Without`] to clone by substituting a dummy value -
16///   `NonMaxU32(0)` for a plain ID, `None` for an optional ID.
17///
18/// This is designed so that cloning semantic IDs is branchless and cheap.
19/// See `SemanticId` trait in `oxc_syntax` crate.
20#[derive(Clone, Copy, PartialEq, Eq)]
21#[repr(u32)]
22pub enum CloneInSemanticIds {
23    With = 0,
24    Without = u32::MAX,
25}
26
27/// A trait to explicitly clone an object into an arena allocator.
28///
29/// As a convention `Cloned` associated type should always be the same as `Self`,
30/// It'd only differ in the lifetime, Here's an example:
31///
32/// ```
33/// # use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Vec};
34/// # struct Struct<'a> {a: Vec<'a, u8>, b: u8}
35///
36/// impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for Struct<'old_alloc> {
37///     type Cloned = Struct<'new_alloc>;
38///
39///     fn clone_in_impl(
40///         &self,
41///         with_semantic_ids: CloneInSemanticIds,
42///         allocator: &'new_alloc Allocator,
43///     ) -> Self::Cloned {
44///         Struct {
45///             a: self.a.clone_in_impl(with_semantic_ids, allocator),
46///             b: self.b.clone_in_impl(with_semantic_ids, allocator),
47///         }
48///     }
49/// }
50/// ```
51///
52/// Implementations of this trait on non-allocated items usually delegate to `Clone::clone`.
53/// However, it **isn't** guaranteed.
54pub trait CloneIn<'new_alloc>: Sized {
55    /// The type of the cloned object.
56    ///
57    /// This should always be `Self` with a different lifetime.
58    type Cloned;
59
60    /// Clone `self` into the given `allocator`. `allocator` may be the same one
61    /// that `self` is already in.
62    // `#[inline(always)]` because it just delegates to `clone_in_impl`.
63    #[expect(clippy::inline_always)]
64    #[inline(always)]
65    fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
66        self.clone_in_impl(CloneInSemanticIds::Without, allocator)
67    }
68
69    /// Almost identical as `clone_in`, but for some special type, it will also clone the semantic ids.
70    /// Please use this method only if you make sure semantic info is synced with the ast node.
71    // `#[inline(always)]` because it just delegates to `clone_in_impl`.
72    #[expect(clippy::inline_always)]
73    #[inline(always)]
74    fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
75        self.clone_in_impl(CloneInSemanticIds::With, allocator)
76    }
77
78    /// Clone `self` into `allocator`, threading whether semantic ids should be preserved as a
79    /// runtime `with_semantic_ids` flag rather than as two separate methods.
80    ///
81    /// This is the method that implementors provide.
82    /// `clone_in` and `clone_in_with_semantic_ids` are thin wrappers around it.
83    ///
84    /// It exists so the `CloneIn` derive can emit a *single* recursive traversal that serves both:
85    /// * `clone_in`: `with_semantic_ids == CloneInSemanticIds::Without`
86    /// * `clone_in_with_semantic_ids`: `with_semantic_ids == CloneInSemanticIds::With`
87    ///
88    /// This is instead of monomorphizing two near-identical traversals over the whole AST.
89    /// ID fields copy existing values when `with_semantic_ids == CloneInSemanticIds::With`,
90    /// and reset to their default when `CloneInSemanticIds::Without`.
91    ///
92    /// Prefer calling `clone_in` or `clone_in_with_semantic_ids` at call sites —
93    /// they name the intent and delegate here.
94    fn clone_in_impl(
95        &self,
96        with_semantic_ids: CloneInSemanticIds,
97        allocator: &'new_alloc Allocator,
98    ) -> Self::Cloned;
99}
100
101impl<'alloc, T, C> CloneIn<'alloc> for Option<T>
102where
103    T: CloneIn<'alloc, Cloned = C>,
104{
105    type Cloned = Option<C>;
106
107    #[inline]
108    fn clone_in_impl(
109        &self,
110        with_semantic_ids: CloneInSemanticIds,
111        allocator: &'alloc Allocator,
112    ) -> Self::Cloned {
113        self.as_ref().map(|it| it.clone_in_impl(with_semantic_ids, allocator))
114    }
115}
116
117impl<'new_alloc, T, C> CloneIn<'new_alloc> for Box<'_, T>
118where
119    T: CloneIn<'new_alloc, Cloned = C>,
120{
121    type Cloned = Box<'new_alloc, C>;
122
123    #[inline]
124    fn clone_in_impl(
125        &self,
126        with_semantic_ids: CloneInSemanticIds,
127        allocator: &'new_alloc Allocator,
128    ) -> Self::Cloned {
129        Box::new_in(self.as_ref().clone_in_impl(with_semantic_ids, allocator), &allocator)
130    }
131}
132
133impl<'new_alloc, T, C> CloneIn<'new_alloc> for Box<'_, [T]>
134where
135    T: CloneIn<'new_alloc, Cloned = C>,
136{
137    type Cloned = Box<'new_alloc, [C]>;
138
139    fn clone_in_impl(
140        &self,
141        with_semantic_ids: CloneInSemanticIds,
142        allocator: &'new_alloc Allocator,
143    ) -> Self::Cloned {
144        let ptr = clone_slice_in(self.as_ref(), with_semantic_ids, allocator);
145
146        // SAFETY: `ptr` points to the cloned `[C]`, allocated in `allocator`'s arena.
147        // The returned `Box`'s lifetime matches the `Allocator` the data was allocated in.
148        unsafe { Box::from_non_null(ptr) }
149    }
150}
151
152impl<'new_alloc, T, C> CloneIn<'new_alloc> for Vec<'_, T>
153where
154    T: CloneIn<'new_alloc, Cloned = C>,
155    // TODO: This lifetime bound possibly shouldn't be required.
156    // https://github.com/oxc-project/oxc/pull/9656#issuecomment-2719762898
157    C: 'new_alloc,
158{
159    type Cloned = Vec<'new_alloc, C>;
160
161    fn clone_in_impl(
162        &self,
163        with_semantic_ids: CloneInSemanticIds,
164        allocator: &'new_alloc Allocator,
165    ) -> Self::Cloned {
166        let slice = self.as_slice();
167
168        // Empty `Vec`s are common in ASTs. Short-circuit to skip making a zero-sized allocation.
169        if slice.is_empty() {
170            return Vec::new_in(&allocator);
171        }
172
173        let ptr = clone_slice_in(slice, with_semantic_ids, allocator);
174        let len = slice.len();
175
176        // Reconstruct a `Vec` owning the cloned `[C]`. Length and capacity are both `slice.len()`:
177        // the allocation holds exactly the cloned elements, with no spare capacity.
178        // SAFETY: `ptr` points to `len` initialized `C`s allocated in `allocator`'s arena,
179        // valid for the returned `Vec`'s lifetime (tied to `allocator`).
180        unsafe { Vec::from_raw_parts_in(ptr.cast::<C>(), len, len, &allocator) }
181    }
182}
183
184/// Allocate space for a clone of `slice` in `allocator`'s arena, clone each item of `slice` into
185/// it (via [`CloneIn::clone_in_impl`]), and return a pointer to the resulting initialized `[C]`.
186///
187/// Shared by the `Box<[T]>` and `Vec<T>` [`CloneIn`] impls - the only part not shared between them
188/// is wrapping the returned pointer back up as a `Box` or `Vec`.
189///
190/// `#[inline]` so the compile-time layout check and the allocation const-fold into each caller,
191/// and callers optimize around the returned pointer (e.g. the `Vec` impl's raw-parts reconstruction).
192#[inline]
193fn clone_slice_in<'new_alloc, T, C>(
194    slice: &[T],
195    with_semantic_ids: CloneInSemanticIds,
196    allocator: &'new_alloc Allocator,
197) -> NonNull<[C]>
198where
199    T: CloneIn<'new_alloc, Cloned = C>,
200{
201    // Compile-time check that `T` and `C` have identical size and alignment - which they always will
202    // with intended usage that `T` and `C` are same types, just with different lifetimes.
203    // This guarantees that layout of clone is same as layout of `slice`,
204    // so we can create `Layout` with `for_value`, which has no runtime checks.
205    const {
206        assert!(
207            size_of::<C>() == size_of::<T>() && align_of::<C>() == align_of::<T>(),
208            "Size and alignment of `T` and `<T as CloneIn>::Cloned` must be the same"
209        );
210    }
211
212    let layout = Layout::for_value(slice);
213
214    let dst_ptr = allocator.alloc_layout(layout).cast::<MaybeUninit<C>>().as_ptr();
215
216    // SAFETY: We allocated space for `slice.len()` items of type `C`, starting at `dst_ptr`.
217    // `MaybeUninit<C>` has the same layout as `C`, so this is a valid view of that
218    // (still uninitialized) memory region as a slice of `slice.len()` elements.
219    let dst = unsafe { slice::from_raw_parts_mut(dst_ptr, slice.len()) };
220
221    // Clone each item of `slice` into `dst`.
222    // `C` isn't `Drop`, and allocation is in the arena, so we don't need to worry about a panic
223    // in the loop - can't lead to a memory leak.
224    clone_between_slices(slice, dst, with_semantic_ids, allocator);
225
226    // `clone_between_slices` initialized every element of `dst`, so we can view it as `&mut [C]`,
227    // reusing `dst`'s provenance rather than re-deriving a fresh slice from `dst_ptr`.
228    // SAFETY: All `slice.len()` elements of `dst` were just initialized.
229    let new_slice = unsafe { dst.assume_init_mut() };
230
231    NonNull::from(new_slice)
232}
233
234/// Clone each item of `src` into `dst` (via [`CloneIn::clone_in_impl`]).
235///
236/// `src` and `dst` are expected to be the same length - only `src.len().min(dst.len())` items are cloned.
237/// Callers pass equal-length slices, so on return every element of `dst` is initialized.
238///
239/// # Why an out-of-line function taking slices
240///
241/// The clone loop lives in this separate function, taking source and destination as slice *parameters*,
242/// rather than being written inline in the callers. LLVM IR `noalias` is only emitted for reference-typed
243/// function parameters (references created mid-function carry no aliasing information), and it survives
244/// inlining as scoped-alias metadata - so this shape lets LLVM prove `src` and `dst` are disjoint.
245///
246/// For trivially-cloneable types, that collapses the loop to a single `memcpy`.
247/// For types with real `CloneIn` impls, it enables vectorization without a runtime overlap check.
248///
249/// No drop guard is needed to guard against a panic mid-loop. `C` is never `Drop` (with intended
250/// usage `C` is `T` with a different lifetime), and the destination is arena-allocated, so a panic
251/// part-way through leaks nothing - see the callers' comments.
252#[expect(clippy::inline_always)]
253#[inline(always)] // To ensure compiler sees that `src.len()` and `dst.len()` are the same
254fn clone_between_slices<'new_alloc, T, C>(
255    src: &[T],
256    dst: &mut [MaybeUninit<C>],
257    with_semantic_ids: CloneInSemanticIds,
258    allocator: &'new_alloc Allocator,
259) where
260    T: CloneIn<'new_alloc, Cloned = C>,
261{
262    for (src_item, dst_item) in src.iter().zip(dst.iter_mut()) {
263        dst_item.write(src_item.clone_in_impl(with_semantic_ids, allocator));
264    }
265}
266
267impl<'new_alloc, K, V, CK, CV, S> CloneIn<'new_alloc> for HashMap<'_, K, V, S>
268where
269    K: CloneIn<'new_alloc, Cloned = CK>,
270    V: CloneIn<'new_alloc, Cloned = CV>,
271    CK: Hash + Eq,
272    S: Default + BuildHasher,
273{
274    type Cloned = HashMap<'new_alloc, CK, CV, S>;
275
276    fn clone_in_impl(
277        &self,
278        with_semantic_ids: CloneInSemanticIds,
279        allocator: &'new_alloc Allocator,
280    ) -> Self::Cloned {
281        // Keys in original hash map are guaranteed to be unique.
282        // Unfortunately, we have no static guarantee that `CloneIn` maintains that uniqueness
283        // - original keys (`K`) are guaranteed unique, but cloned keys (`CK`) might not be.
284        // If we did have that guarantee, we could use the faster `insert_unique_unchecked` here.
285        // `hashbrown::HashMap` also has a faster cloning method in its `Clone` implementation,
286        // but those APIs are not exposed, and `Clone` doesn't support custom allocators.
287        // So sadly this is a lot slower than it could be, especially for `Copy` types.
288        let mut cloned = HashMap::with_capacity_in(self.len(), allocator);
289        for (key, value) in self {
290            cloned.insert(
291                key.clone_in_impl(with_semantic_ids, allocator),
292                value.clone_in_impl(with_semantic_ids, allocator),
293            );
294        }
295        cloned
296    }
297}
298
299impl<'alloc, T, C> CloneIn<'alloc> for Cell<T>
300where
301    T: Copy + CloneIn<'alloc, Cloned = C>,
302{
303    type Cloned = Cell<C>;
304
305    #[inline]
306    fn clone_in_impl(
307        &self,
308        with_semantic_ids: CloneInSemanticIds,
309        allocator: &'alloc Allocator,
310    ) -> Self::Cloned {
311        Cell::new(self.get().clone_in_impl(with_semantic_ids, allocator))
312    }
313}
314
315impl<'new_alloc> CloneIn<'new_alloc> for &str {
316    type Cloned = &'new_alloc str;
317
318    fn clone_in_impl(
319        &self,
320        _with_semantic_ids: CloneInSemanticIds,
321        allocator: &'new_alloc Allocator,
322    ) -> Self::Cloned {
323        allocator.alloc_str(self)
324    }
325}
326
327macro_rules! impl_clone_in {
328    ($($t:ty)*) => {
329        $(
330            impl<'alloc> CloneIn<'alloc> for $t {
331                type Cloned = Self;
332                #[inline(always)]
333                fn clone_in_impl(&self, _with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
334                    *self
335                }
336            }
337        )*
338    }
339}
340
341impl_clone_in! {
342    usize u8 u16 u32 u64 u128
343    isize i8 i16 i32 i64 i128
344    f32 f64
345    bool char
346}
347
348#[cfg(test)]
349mod test {
350    use super::{Allocator, CloneIn, HashMap, Vec};
351
352    #[test]
353    fn clone_in_boxed_slice() {
354        let allocator = Allocator::default();
355        let allocator = &allocator;
356
357        let mut original = Vec::from_iter_in([1, 2, 3], &allocator).into_boxed_slice();
358
359        let cloned = original.clone_in(allocator);
360        let cloned2 = original.clone_in_with_semantic_ids(allocator);
361        original[1] = 4;
362
363        assert_eq!(original.as_ref(), &[1, 4, 3]);
364        assert_eq!(cloned.as_ref(), &[1, 2, 3]);
365        assert_eq!(cloned2.as_ref(), &[1, 2, 3]);
366    }
367
368    #[test]
369    fn clone_in_empty_boxed_slice() {
370        let allocator = Allocator::default();
371        let allocator = &allocator;
372
373        // Exercises the zero-sized `alloc_layout` path in `clone_slice_in`
374        let original = Vec::<u32>::new_in(&allocator).into_boxed_slice();
375
376        let cloned = original.clone_in(allocator);
377        let cloned2 = original.clone_in_with_semantic_ids(allocator);
378
379        assert_eq!(cloned.as_ref(), &[] as &[u32]);
380        assert_eq!(cloned2.as_ref(), &[] as &[u32]);
381    }
382
383    #[test]
384    fn clone_in_vec() {
385        let allocator = Allocator::default();
386        let allocator = &allocator;
387
388        let mut original = Vec::with_capacity_in(8, &allocator);
389        original.extend_from_slice(&[1, 2, 3]);
390
391        let cloned = original.clone_in(allocator);
392        let cloned2 = original.clone_in_with_semantic_ids(allocator);
393        original[1] = 4;
394
395        assert_eq!(original.as_slice(), &[1, 4, 3]);
396        assert_eq!(cloned.as_slice(), &[1, 2, 3]);
397        assert_eq!(cloned.capacity(), 3);
398        assert_eq!(cloned2.as_slice(), &[1, 2, 3]);
399        assert_eq!(cloned2.capacity(), 3);
400    }
401
402    #[test]
403    fn clone_in_empty_vec() {
404        let allocator = Allocator::default();
405        let allocator = &allocator;
406
407        // Exercises the `slice.is_empty()` short-circuit to `Vec::new_in`
408        let original = Vec::<u32>::new_in(&allocator);
409
410        let cloned = original.clone_in(allocator);
411        let cloned2 = original.clone_in_with_semantic_ids(allocator);
412
413        assert_eq!(cloned.as_slice(), &[] as &[u32]);
414        assert_eq!(cloned.capacity(), 0);
415        assert_eq!(cloned2.as_slice(), &[] as &[u32]);
416        assert_eq!(cloned2.capacity(), 0);
417    }
418
419    #[test]
420    fn clone_in_hash_map() {
421        let allocator = Allocator::default();
422
423        let mut original: HashMap<'_, &str, &str> = HashMap::with_capacity_in(8, &allocator);
424        original.extend(&[("x", "xx"), ("y", "yy"), ("z", "zz")]);
425
426        let cloned = original.clone_in(&allocator);
427        let cloned2 = original.clone_in_with_semantic_ids(&allocator);
428        *original.get_mut("y").unwrap() = "changed";
429
430        let mut original_as_vec = original.iter().collect::<std::vec::Vec<_>>();
431        original_as_vec.sort_unstable();
432        assert_eq!(original_as_vec, &[(&"x", &"xx"), (&"y", &"changed"), (&"z", &"zz")]);
433
434        assert_eq!(cloned.capacity(), 3);
435        let mut cloned_as_vec = cloned.iter().collect::<std::vec::Vec<_>>();
436        cloned_as_vec.sort_unstable();
437        assert_eq!(cloned_as_vec, &[(&"x", &"xx"), (&"y", &"yy"), (&"z", &"zz")]);
438
439        assert_eq!(cloned2.capacity(), 3);
440        let mut cloned2_as_vec = cloned2.iter().collect::<std::vec::Vec<_>>();
441        cloned2_as_vec.sort_unstable();
442        assert_eq!(cloned2_as_vec, &[(&"x", &"xx"), (&"y", &"yy"), (&"z", &"zz")]);
443    }
444}