This PR is inspired by #26333, addresses the repeated `with_hasher_in(IdentBuildHasher, allocator)` call, and removes the extra `'ident` lifetime.
`ArenaHashMap::new_in` works with any `Default` hasher, so `ArenaIdentHashMap::new_in(allocator)` picks `IdentBuildHasher` automatically. `ArenaHashSet` only has `new_in`, `with_capacity_in` and `from_iter_in` for `FxBuildHasher`, so an `Ident`-keyed arena set has to call `with_hasher_in(IdentBuildHasher, allocator)` every time (see #26333).
Mirror the map's `S: Default` impl on the set and add an `ArenaIdentHashSet<'alloc>` alias next to `ArenaIdentHashMap`. Nothing calls the `FxBuildHasher`-only constructors today, so no other code changes.
The alias takes a single lifetime, same as `ArenaIdentHashMap`. A separate lifetime for the `Ident` keys is not needed, and dropping it is sound: both `ArenaHashSet` and `Ident` are covariant, so when the idents are borrowed from somewhere other than the arena (for example `Scoping` in the mangler), the borrow checker shrinks `'alloc` to the shorter of the two borrows. The set can never outlive the strings its idents point to.
## Summary
- compile Allocator::data_end_ptr only in supported fixed-size allocator builds
- remove the dead-code warning from coverage builds while preserving fixed-size pool usage
`Allocator::cursor_ptr` and `Allocator::data_end_ptr` were only exposed for `oxc_linter` to use for some dodgy pointer laundering. #26077 removed that code, so these methods no longer need to be exposed.
Remove them from public API - it's not ideal to be exposing the internals of the allocator to user code. Both are still used within `oxc_allocator` crate, so they're not removed entirely, but become `pub(crate)`.
Addresses deprecations reported by the latest Rust nightly while preserving Rust 1.98 compatibility.
- Rename `unit-bindings` to `unit_bindings`: hyphenated lint names are deprecated in favor of their canonical underscore spelling.
- Replace `isize::max_value()` and `usize::max_value()` with `isize::MAX` and `usize::MAX`: the legacy numeric methods are deprecated in favor of associated constants.
- Set explicit `MAX_INDEX` values on `define_index_type!`: `oxc_index` otherwise generates `<raw>::max_value() as usize`; the explicit modern constants avoid that deprecated expansion without changing the index limits.
- Remove obsolete `clippy::legacy_numeric_constants` expectations: the corrected code no longer triggers that lint, so the expectations would be unfulfilled.
- Allow the AST scanner to consume optional `define_index_type!` configuration: it previously parsed only the struct declaration and rejected the added `MAX_INDEX` option.
AI-assisted.
Add more tests for bump pointer calculation in `Arena::try_alloc_layout_fast_impl`.
These tests close a gap in test coverage - test that allocation is rejected when subtracting the requested allocation size from current cursor pointer produces 0 exactly (null pointer). All pass.
Alter `Arena::alloc_with` and `Arena::try_alloc_with` to write through `&mut MaybeUninit<T>` instead of raw `NonNull<T>` pointers.
This is pure refactor at present. It reduces the unsafe code in these methods to a single line, and I feel makes it clearer.
Additional motivation is to give LLVM `noalias` information (`&mut MaybeUninit<T>` has the exclusivity guarantee of `&mut` which `NonNull` doesn't). At present compiler produces same assembly before/after this change, so it doesn't seem to capitalize on this `noalias` info, however it might in the future. The change I don't think has any possibility of making codegen worse, and it has at least _potential_ to make it better.
It may also be contributing to the gain in #25337.
Rust's stdlib recently added a new method `push_mut` to `Vec`.
https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push_mut
Add the same method to `ArenaVec`, and also a `push_fast_mut` method, which is the counterpart to `push_fast`.
Before:
```rust
fn push_and_do<'a>(vec: &mut ArenaVec<'a, Expression<'a>>, expr: Expression<'a>) {
vec.push(expr);
let expr_mut_ref: &mut Expression<'a> = vec.last_mut().unwrap();
do_something_with_expr(expr_mut_ref);
}
```
After:
```rust
fn push_and_do<'a>(vec: &mut ArenaVec<'a, Expression<'a>>, expr: Expression<'a>) {
let expr_mut_ref: &mut Expression<'a> = vec.push_mut(expr);
do_something_with_expr(expr_mut_ref);
}
```
`push_mut` does the same thing as `push` followed by `last`, but removes the `unwrap`, which compiler is unable to optimize out.
`ArenaBox` was previously `!Send` and `!Sync`, because it contains a `NonNull` pointer.
`ArenaBox<T>` has the same semantics as a `&mut T`, so can be `Send` / `Sync` if `T` is. The also matches stdlib's `Box`.
Note: `ArenaVec` is _not_ `Send` because it contains an `&Allocator` reference and `Allocator` is not `Sync`. But `ArenaBox` contains nothing but the pointer - it does not contain an `&Allocator` - so it doesn't share `ArenaVec`'s restriction.
This is a further step towards making the whole AST `Sync`, so ASTs can be shared across threads. After this PR, the only remaining blocker is the `Cell`s in AST for semantic IDs (e.g. `Cell<NodeId>`).
Remove the `ArenaBox::dangling` method.
This was a hack only required for checking enum discriminants in code generated by `inherit_variants!` macro. That code was removed in #23960, so `ArenaBox::dangling` can now be removed too.
Any code which for some reason needs to create an invalid `ArenaBox` with a dangling pointer can do so with `ArenaBox::from_non_null(NonNull::dangling())`.
Pure refactor. Along the same lines as #25174 and other PRs in this stack.
Replace generic params with `impl` for `GetAllocator` in methods of various types and traits across `oxc_allocator` and `oxc_str`, to make the code easier to read.
Speed up `CloneIn` for `Vec<T>` and `Box<[T]>` by constructing source and destination slices and passing them to a separate function to clone between.
This formulation tells LLVM that source and destination cannot overlap, which causes it to generate more efficient code. When `T` is a `Copy` or trivially-clonable type with no padding bytes (e.g. `Vec<'a, NodeId>`), the entire clone operation is squashed down to a single `memcpy` call - which is much faster than iterating through the source item by item.
`RawVec::from_raw_parts_in` and `InnerVec::from_raw_parts_in` take a `NonNull<T>` pointer, instead of a `*mut T` pointer.
`RawVec` ultimately uses `NonNull` internally, and both callers `Vec::from_value_in` and `Vec::from_array_in` have a `NonNull` pointer to start with. So this change avoid converting `NonNull<T>` to `*mut T` only to convert it back to `NonNull<T>` with an unsafe call.
Use `implements!` macro (introduced in #24721) in various tests, including adding some new ones.
The most valuable change is replacing a `compile_fail` doctest which checks that `Arena` is not `Sync`. Using `assert!(implements!(Arena: !Sync));` is more robust.
Move all `Ident`\-hashing code from `oxc_allocator` into `oxc_str`, to sit alongside `Ident`'s code.
This feels like the natural place for this code, and reduces the public API surface - we no longer need to expose `Ident`'s internals (`ident_hash`). Now only `IdentHasher` and `IdentBuildHasher` are exposed.
Only change to code is removing `pack_len_hash`, as it was only used in tests.
`Vec::from_array_in` doesn't need to allocate when the array passed to it is zero-length.
This wasn't unsound as `Allocator` accepts zero-sized allocations, but it was pointless. Allocating a zero-sized type still aligns the bump cursor to the type's alignment, which is an allocation of sorts - it's not zero-cost.
Just delegate to `Vec::new_in` instead.
Adds three heap-side metrics to the allocation snapshots, using the metric machinery from #24587:
- `sys deallocs` (exact count) — system deallocations per stage; read against `sys allocs` it shows whether a stage churns or retains heap.
- `sys alloc bytes` — cumulative bytes requested from the system allocator per stage (allocation sizes plus reallocation growth).
- `sys peak growth` — high-water mark of live heap during a stage relative to the live bytes at stage start, via live-bytes accounting in the `GlobalAlloc` wrapper. Captures transient heap a stage needs even when it frees it before finishing.
Arena chunk operations are excluded from all `sys` metrics at the source: `oxc_allocator` marks each chunk allocation/deallocation immediately before calling the system allocator (task-only `track_allocations` feature, compiled out everywhere else), and the task's tracking allocator consumes the marker and skips the call. This replaces the previous global chunk counter + per-stage subtraction: a subtraction can correct counts but not a high-water mark, and chunk sizes are quantized and platform-dependent (whether an arena needs one more chunk depends on byte totals that vary across architectures with type layout — the #22621 mechanism). The first CI run demonstrated this concretely: kitchen-sink's minifier peak differed deterministically by 720 kB between aarch64 and x86_64 because the mangler's temporary arena crossed a chunk-growth boundary only on x86_64; and antd's formatter "peak" was 762 MB of main-arena chunk-doubling history rather than formatter behavior (now 13.8 MB of actual heap use).
Cross-platform tolerances: the cumulative byte metrics still drift with the number of heap hashbrown tables allocated (their control-group width is architecture-dependent), so they use a relative tolerance (1%, 4 kB floor) via the committed-value comparison from #24565; `arena size` keeps its tight 1024-byte tolerance.
Sample values for checker.ts (2.9 MB source): parser needs only 2.4 kB of transient heap (fully arena-based), semantic peaks at 3.9 MB, the minifier is the pipeline's largest heap consumer at 10.2 MB, and the formatter requests 5.9 MB total while peaking at 3 MB (heavy alloc/free churn).
🤖 Implemented with [Claude Code](https://claude.com/claude-code).
## Summary
Mark ReplaceWith's write_dummy helper as cold.
The helper is only reached while unwinding from a panic in a replacer closure. Telling the optimizer that this path is cold helps keep its allocator and dummy-construction code out of hot code layout and improves inlining decisions in unwind builds.
Follow-on after #24422.
Make cloning semantic IDs (`NodeId`, `ScopeId` etc) use branchless arithmetic.
* Cloning `NodeId` now just uses a single OR operation to take into account whether to preserve the existing ID (`clone_in_with_semantic_ids`) or substitute a dummy ID (`clone_in`).
* Cloning `Cell<Option<ScopeId>>` is 2 instructions (NOT + AND) on x86_64, or just 1 on aarch64 (NOT-AND).
https://godbolt.org/z/1KWdc5hnb
The mechanism is a type `CloneInSemanticIds` which is used as `with_semantic_ids` instead of a `bool`:
```rs
#[repr(u32)]
pub enum CloneInSemanticIds {
With = 0,
Without = u32::MAX,
}
```
This is 4 bytes, rather than `bool`'s 1 byte, but that makes no difference - either way it takes 1 register to pass between functions. The choice of discriminants is what enables the cheap arithmetic.
This is even smaller in terms of binary size than the previous solution of marking `Cell::clone_in_impl` as `#[inline(never)]` (OR instruction is smaller than function call), and it's more performant as it loses the function call overhead. It also keeps `Cell`'s blanket implementation of `CloneIn` generalized.
Between #24422 and this PR, merging `CloneIn` with/without semantic IDs into a single path has gained us a large binary size reduction, and lost us nothing - or may even have gained us a marginal perf improvement.
No visible change on our benchmarks, as we don't use `CloneIn` much, but it may well be a measurable improvement for Rolldown, which clones whole ASTs.
## Summary
The `CloneIn` derive generated **two near-identical traversals over the whole AST** — `clone_in` (resets semantic ids to their default) and `clone_in_with_semantic_ids` (preserves them). Almost every node carries an id field, so the two bodies diverge nearly everywhere and the pair roughly doubled the clone codegen.
This threads *"preserve semantic ids?"* as a **runtime `bool`** through a **single** traversal:
- **`CloneIn::clone_in_impl(&self, with_semantic_ids: bool, allocator)` is the one required method.** `clone_in` / `clone_in_with_semantic_ids` are `#[inline(always)]` default wrappers that call it with `false` / `true`. Hand-written impls provide only `clone_in_impl`.
- The **derive emits a single `clone_in_impl` body per type** — no per-type `clone_in` / `clone_in_with_semantic_ids` wrappers.
- Semantic ids live in `Cell<NodeId>` / `Cell<Option<{Scope,Symbol,Reference}Id>>`. **`Cell<T: Copy + Default>::clone_in_impl` threads the flag**: preserve (`Cell::new(self.get())`) when `with_semantic_ids`, otherwise reset to the default (`Cell::new(T::default())` = `NodeId::DUMMY` / `None`). So the derive emits a plain `clone_in_impl(&self.node_id, …)` per id field — no per-field `if/else`.
- **`Cell::clone_in_impl` is `#[inline(never)]`** so the (for some consumers dead) preserve branch isn't inlined at every id-field site — see Results.
## Results
Measured `f736d87e77` → `db9f2ab41a` on this branch (release, stripped, fat-LTO):
| | before | after | Δ |
|---|--:|--:|--:|
| `oxc_ast` generated `CloneIn` (source lines) | 7,180 | 5,581 | **−1,599 (−22%)** |
| oxlint binary | 14,192,816 B | 14,126,656 B | **−66,160 B (−64.6 KiB)** |
| napi transform cdylib (`--features allocator`) | 3,633,312 B | 3,633,312 B | **0 (neutral)** |
The two binaries move differently because they clone differently:
- **oxlint / rolldown clone *with* semantic ids** (react compiler on `TSType`; whole-`Program` clones). They already carried chunks of both traversals; merging dedups them → **−64.6 KiB**.
- **napi transform / parse clone *without* ids** — `oxc_transformer` never calls `clone_in_with_semantic_ids`, so before this PR `--gc-sections` dropped that whole half. The merged traversal would force the preserve branch live at every id field (~+16 KiB); `#[inline(never)]` on the `Cell` leaf outlines it to one function per `Cell<T>`, keeping these binaries **flat**.
## Behavior & perf
Behavior is identical by construction — `clone_in` resets ids (`Cell::new(None)` / `Cell::new(DUMMY)`), `clone_in_with_semantic_ids` preserves them. The runtime flag is loop-invariant and well predicted; the `#[inline(never)]` leaf adds one outlined call per id-cell on the clone path (expected negligible — worth a benchmark spot-check). Generated files were produced by `just ast`.
---
🤖 AI-assisted (Claude Code), reviewed and measured by the author.
Rust 1.95 stabilized [`Layout::repeat`](https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.repeat) (MSRV bumped in #24359), which the `UnstableLayoutMethods` polyfill trait in `bumpalo_alloc.rs` was waiting on (per its TODO comment).
It turns out the whole trait is deletable rather than just replaceable:
- The only `.repeat()` call outside the trait itself (`vec2/raw_vec.rs`) is inside a commented-out block.
- The live `Layout::array::<T>(n)` calls already resolve to the inherent std method (stable since 1.44), not the trait method.
- `padding_needed_for` was only used by the trait's own `repeat` impl.
Also drops the two `#![allow(unstable_name_collisions)]` attributes, which only existed because the trait methods collided with the then-unstable std inherent methods.
Net: -59 lines, no behavior change. `cargo check/clippy/test -p oxc_allocator` all clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Rust 1.95 stabilized [`Atomic*::update`](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#method.update) (MSRV bumped in #24359), the infallible sibling of `fetch_update` for closures that always produce a new value.
Both `fetch_update` call sites (`oxc_allocator/src/tracking.rs` and `tasks/track_memory_allocations`) used an always-`Some` closure, so they gain nothing from the fallible API — switching to `update` drops the ignored `Result` (`let _ =`) and the "cannot be `Err`" comments. Same CAS-loop semantics; `saturating_add` behavior unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
#24359 bumped our MSRV to 1.95.0. We can now use `Vec::push_mut`.
Use it in `ReplaceWith` to remove an infallible `unwrap` which compiler previously couldn't prove was infallible. Previously it emitted a panicking branch, now it doesn't.
This code is on a cold path, so perf impact is miniscule, but why not use the best API for the job?
## Issue
The `Allocations` CI job can fail on PRs with a 1-allocation `Sys allocs` difference that local `cargo allocs` cannot reproduce. #22621 fixed one instance of this in the transformer stage. #24112 and #24017 hit the same failure again in the minifier stage: `kitchen-sink.tsx` measures 2309 Sys allocs on macOS/arm64 but 2310 on Linux/x64, so whichever platform regenerates the snapshot last breaks CI for the other.
## Why
The tracker counts every call to the system allocator. That includes the chunks bump arenas request. Whether an arena needs one more chunk depends on the total number of bytes in the arena, and byte totals are not the same on every target. For example, hashbrown tables use 16-byte control groups on x86_64 but 8-byte groups on aarch64. When an arena's content size sits close to a chunk boundary, one target crosses it and the other does not.
In the observed failure, the minifier's mangler-phase semantic build creates a fresh `Scoping`. Its arena holds symbol names, resolved-reference vectors, and the per-scope `bindings` hash tables, which are slightly larger on x86_64. The traced Linux-only allocation happened under `Scoping::add_resolved_reference -> ArenaVec::push -> Arena::new_chunk`, when the push overflowed the current chunk and requested a new 720880-byte chunk. On aarch64 the same content fit in the existing chunk, so the count stayed one lower.
Every other allocation class in the measurement grows on element counts, which are identical across platforms. Logging every allocation size during the minifier stage on macOS/arm64, Linux/arm64, and Linux/x64 showed the three sequences match one-to-one except for arena chunk requests.
Any PR that shifts arena content near a chunk boundary can trip this again in any stage, so fixing one stage at a time does not close the class.
## Fix
Count chunk allocations in `oxc_allocator` under the tracker-only `track_allocations` feature, and subtract them from the reported Sys allocs in the tracker. The counter is global rather than per-arena because short-lived arenas (like the one backing `Scoping`) are created and dropped inside the measured operation.
Arena usage is still measured by the `Arena allocs` / `Arena reallocs` columns; only the platform-sensitive chunk-request count is excluded from `Sys allocs`.
Parser and transformer rows are unchanged (the main arena is pre-warmed, and #22621 already removed transformer scoping growth). Semantic, minifier, and formatter rows drop by their per-stage chunk counts.
## Validation
`cargo allocs` produces byte-identical snapshots on macOS/arm64, Linux/arm64 (Docker), and Linux/x64 (Docker), and is idempotent across reruns. `cargo test -p oxc_allocator` passes; clippy is clean with `track_allocations` both on and off, and the counter compiles out entirely without the feature.
AI usage: AI assistance was used to investigate, implement, and validate this change.
## The problem
A common pattern we have when mutating an AST (e.g. in transformer) is:
- Take a node from the AST.
- Wrap it in some other node, or extract a field from that node.
- Write the result back into the same place in AST the original came from.
Rust does not make this easy, due to aliasing rules. Currently we do something like this (example from arrow function transform):
```rs
let Statement::ExpressionStatement(expr_stmt) = match stmt else { unreachable!() };
let expr = expr_stmt.expression.take_in(ctx);
*stmt = Statement::new_return_statement(expr.span(), Some(expr), ctx);
```
The problem with this is that `take_in` writes a dummy `Expression` into the AST, in order to get the owned `Expression`. This is pointless, as the whole `Statement` that contains that dummy `Expression` is overwritten in the next line - the dummy is immediately disconnected from the AST. But that dummy node lives on forever in the arena, consuming memory.
## Solution in this PR
Introduce `ReplaceWith` trait which replaces the node in place, without writing a dummy:
```rs
stmt.replace_with(|stmt| {
let Statement::ExpressionStatement(expr_stmt) = stmt else { unreachable!() };
let expr = expr_stmt.unbox().expression;
Statement::new_return_statement(expr.span(), Some(expr), ctx)
});
```
The closure receives an owned copy of the `Statement`, and can do whatever it needs to with it. The closure has to return a new `Statement` which is written to the "hole" the original came from.
This PR only adds the trait. Later PRs in this stack implement the trait on AST types, and then use it in various crates.
## Implementation
The difficulty is in making this sound even if the closure panics, and `std::panic::catch_unwind` is in use, allowing the old value to be observed.
A naive implementation would allow UB as follows:
```rs
use std::panic::{AssertUnwindSafe, catch_unwind};
use oxc_allocator::{Allocator, ArenaBox, ReplaceWith};
let allocator = Allocator::new();
let allocator = &allocator;
let mut boxed: ArenaBox<u32> = ArenaBox::new_in(1, &allocator);
let mut copy: Option<ArenaBox<u32>> = None;
let _ = catch_unwind(AssertUnwindSafe(|| {
boxed.replace_with(|old| {
// Move the copy out of the closure
copy = Some(old);
panic!("Unwind before write-back");
});
}));
let mut copy: ArenaBox<u32> = copy.unwrap();
let boxed_mut: &mut u32 = boxed.as_mut();
let copy_mut: &mut u32 = copy.as_mut();
// `boxed_mut` and `copy_mut` both point to same `u32`.
// We have 2 `&mut u32`s pointing to same place. Aliasing violation!
*boxed_mut = 2;
*copy_mut = 3;
```
The solution is to make it possible to implement `ReplaceWith` only on types that also implement `Dummy`.
If the closure panics, a guard is triggered which writes a dummy into the original slot which the type was moved out of. This happens before any other code can observe the value in the slot.
To keep the API ergonomic, we don't want to have to pass in an `&Allocator` to `replace_with`. But the dummy has to be allocated *somewhere*. So we use a global store of `Allocator`s, stored in a `static`, which live for the entire life of the process. When a panic occurs, a new `Allocator` is created, and the dummy is allocated in it.
This is a memory leak - the `Allocator` containing the dummy will never be freed until the process exits.
However, in practice this is fine, because:
#### 1. It shouldn't happen
The guard only triggers, and these leaked `Allocator`s are only created, if the closure panics - which it never should. If the closure *does* panic, that's a bug, and we'll aim to fix it swiftly.
#### 2. Panic usually exits process
It's unusual to use `catch_unwind`. Generally a panic is not caught and it causes the process to exit. So an `Allocator` can be leaked, but only for the few milliseconds before the process exits, and it's freed again.
#### 3. `panic = "abort"`
Oxc's apps (Oxlint, Oxfmt) and NAPI packages (`oxc-parser` etc) are compiled with `panic = "abort"`.
With `panic = "abort"`, panics are converted to process abort. In that case, the compiler sees that the guard can never fire. It removes the guard and all the code which creates `Allocator`s and allocates dummies into them. So all these mechanics are completely zero cost, and no leak ever occurs.
Remove this method, which was added in #23717. The method is sound, and could be useful in some cases, but I realised it's not forward-compatible with planned future changes to allocator. So I think best not to introduce it.
This would be a breaking change, but it was only added a few days ago, so hadn't got into a release yet.
Part of #23043.
Same as #23755, but for `Box`.
`Box::new_in` previously took an `&'a Allocator`. Instead make it take an `&A where A: GetAllocator<'a>`.
This makes it more flexible - you can pass a reference to any type which implements `GetAllocator` e.g. `ParserImpl`, `TraverseCtx` etc.
```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let boxed = Box::new_in(value, ctx.ast.allocator);
// After
let boxed = Box::new_in(value, ctx);
```
The main motivation is to make it a workable replacement for `AstBuilder::alloc`, which will be removed in the new `AstBuilder`:
```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let boxed = ctx.ast.alloc(value);
// After
let boxed = ArenaBox::new_in(value, ctx);
// Instead of
let boxed = ArenaBox::new_in(value, ctx.ast.allocator);
```
This has the same advantages and disadvantages as `Vec` methods - see #23755.
Part of #23043.
`Vec::new_in`, `Vec::with_capacity_in`, `Vec::from_array_in` etc previously took an `&'a Allocator`. Instead make them take an `&A where A: GetAllocator<'a>`.
This makes them more flexible - you can pass a reference to any type which implements `GetAllocator` e.g. `ParserImpl`, `TraverseCtx` etc.
```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let vec = ArenaVec::new_in(ctx.ast.allocator);
// After
let vec = ArenaVec::new_in(ctx);
```
The main motivation is to make them workable replacements for what are currently methods on `AstBuilder`, but will be removed in the new `AstBuilder`:
```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let vec = ctx.ast.vec();
// After
let vec = ArenaVec::new_in(ctx);
// Instead of
let vec = ArenaVec::new_in(ctx.ast.allocator);
```
This is still longer code than the original, which is not ideal, but it's as short as can make them.
The main downside of this change is that when calling these methods with an `&Allocator`, you have to double reference:
```rust
let allocator: &'a Allocator = get_allocator_somehow();
// Passing `&&Allocator`
let vec = ArenaVec::new_in(&allocator);
```
This has no perf impact - these methods are inlined, and compiler collapses the double-reference like it wasn't there, but it's just ugly. Unfortunately, there's no way to implement `GetAllocator` so it'll accept both `&Allocator` and `&mut TraverseCtx`, and the latter is the much more common use case.
Pure refactor.
It's a common pattern in repo to import `Vec` and `Box` etc from `oxc_allocator` and alias them to `ArenaBox` / `ArenaVec`.
```rust
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
```
Make this pattern less verbose by re-exporting `Box` and `Vec` as these aliases from `oxc_allocator`.
Ditto other arena types: `ArenaHashMap`, `ArenaHashSet`, `ArenaStringBuilder`.
Use those aliases in all other crates to shorten code.
```rust
use oxc_allocator::{ArenaBox, ArenaVec};
```
Add `Vec::from_value_in` which creates a `Vec` containing only a single value.
This performs the same role as `AstBuilder::vec1`.
```rust
// These 2 are equivalent
let v = self.ast.vec1(value);
let v = Vec::from_value_in(value, self.ast.allocator);
```
Internally uses `Vec::from_box_in`, introduced in #23717.
Improve `PartialEq` implementations on `oxc_allocator::Vec`, bringing it up to parity with `std::vec::Vec`.
Previously code like this would fail to compile due to missing implementations for comparing `Vec`s to slices:
```rust
let v = Vec::from_array_in([1, 2, 3], &allocator);
assert_eq!(v, [1, 2, 3]);
```