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}