diff --git a/crates/oxc_formatter/src/formatter/buffer.rs b/crates/oxc_formatter/src/formatter/buffer.rs index 1c992fc5bfa53..30d015ca310a4 100644 --- a/crates/oxc_formatter/src/formatter/buffer.rs +++ b/crates/oxc_formatter/src/formatter/buffer.rs @@ -7,6 +7,8 @@ use std::{ use rustc_hash::FxHashMap; +use oxc_allocator::{Allocator, TakeIn, Vec as ArenaVec}; + use super::{ Arguments, Format, FormatElement, FormatResult, FormatState, format_element::Interned, @@ -175,34 +177,35 @@ impl<'ast, W: Buffer<'ast> + ?Sized> Buffer<'ast> for &mut W { #[derive(Debug)] pub struct VecBuffer<'buf, 'ast> { state: &'buf mut FormatState<'ast>, - elements: Vec>, + elements: ArenaVec<'ast, FormatElement<'ast>>, } impl<'buf, 'ast> VecBuffer<'buf, 'ast> { pub fn new(state: &'buf mut FormatState<'ast>) -> Self { - Self::new_with_vec(state, Vec::new()) + Self::new_with_vec(state, ArenaVec::new_in(state.context().allocator())) } pub fn new_with_vec( state: &'buf mut FormatState<'ast>, - elements: Vec>, + elements: ArenaVec<'ast, FormatElement<'ast>>, ) -> Self { Self { state, elements } } /// Creates a buffer with the specified capacity pub fn with_capacity(capacity: usize, state: &'buf mut FormatState<'ast>) -> Self { - Self { state, elements: Vec::with_capacity(capacity) } + let elements = ArenaVec::with_capacity_in(capacity, state.context().allocator()); + Self { state, elements } } /// Consumes the buffer and returns the written [`FormatElement]`s as a vector. - pub fn into_vec(self) -> Vec> { + pub fn into_vec(self) -> ArenaVec<'ast, FormatElement<'ast>> { self.elements } /// Takes the elements without consuming self - pub fn take_vec(&mut self) -> Vec> { - std::mem::take(&mut self.elements) + pub fn take_vec(&mut self) -> ArenaVec<'ast, FormatElement<'ast>> { + self.elements.take_in(self.state.context().allocator()) } } @@ -493,8 +496,13 @@ impl<'buf, 'ast> RemoveSoftLinesBuffer<'buf, 'ast> { } /// Removes the soft line breaks from an interned element. - fn clean_interned(&mut self, interned: &Interned<'ast>) -> Interned<'ast> { - clean_interned(interned, &mut self.interned_cache, &mut self.conditional_content_stack) + fn clean_interned(&mut self, interned: Interned<'ast>) -> Interned<'ast> { + clean_interned( + interned, + &mut self.interned_cache, + &mut self.conditional_content_stack, + self.inner.state().context().allocator(), + ) } /// Marker for whether a `StartConditionalContent(mode: Expanded)` has been @@ -509,11 +517,12 @@ impl<'buf, 'ast> RemoveSoftLinesBuffer<'buf, 'ast> { // Extracted to function to avoid monomorphization fn clean_interned<'ast>( - interned: &Interned<'ast>, + interned: Interned<'ast>, interned_cache: &mut FxHashMap, Interned<'ast>>, condition_content_stack: &mut Vec, + allocator: &'ast Allocator, ) -> Interned<'ast> { - if let Some(cleaned) = interned_cache.get(interned) { + if let Some(cleaned) = interned_cache.get(&interned) { cleaned.clone() } else { // Find the first soft line break element, interned element, or conditional expanded @@ -522,18 +531,23 @@ fn clean_interned<'ast>( FormatElement::Line(LineMode::Soft | LineMode::SoftOrSpace) | FormatElement::Tag(Tag::StartConditionalContent(_) | Tag::EndConditionalContent) | FormatElement::BestFitting(_) => { - let mut cleaned = Vec::new(); - cleaned.extend_from_slice(&interned[..index]); + let mut cleaned = + ArenaVec::from_iter_in(interned[..index].iter().cloned(), allocator); Some((cleaned, &interned[index..])) } FormatElement::Interned(inner) => { - let cleaned_inner = clean_interned(inner, interned_cache, condition_content_stack); + let cleaned_inner = clean_interned( + inner.clone(), + interned_cache, + condition_content_stack, + allocator, + ); if &cleaned_inner == inner { None } else { - let mut cleaned = Vec::with_capacity(interned.len()); - cleaned.extend_from_slice(&interned[..index]); + let mut cleaned = ArenaVec::with_capacity_in(interned.len(), allocator); + cleaned.extend(interned[..index].iter().cloned()); cleaned.push(FormatElement::Interned(cleaned_inner)); Some((cleaned, &interned[index + 1..])) } @@ -567,9 +581,10 @@ fn clean_interned<'ast>( FormatElement::Interned(interned) => { cleaned.push(FormatElement::Interned(clean_interned( - interned, + interned.clone(), interned_cache, condition_content_stack, + allocator, ))); } // Since this buffer aims to simulate infinite print width, we don't need to retain the best fitting. @@ -588,15 +603,14 @@ fn clean_interned<'ast>( None => interned.clone(), }; - interned_cache.insert(interned.clone(), result.clone()); + interned_cache.insert(interned, result.clone()); result } } impl<'ast> Buffer<'ast> for RemoveSoftLinesBuffer<'_, 'ast> { fn write_element(&mut self, element: FormatElement<'ast>) -> FormatResult<()> { - let mut element_stack = Vec::new(); - element_stack.push(element); + let mut element_stack = Vec::from_iter([element]); while let Some(element) = element_stack.pop() { match element { FormatElement::Tag(Tag::StartConditionalContent(condition)) => { @@ -614,14 +628,14 @@ impl<'ast> Buffer<'ast> for RemoveSoftLinesBuffer<'_, 'ast> { self.inner.write_element(FormatElement::Space)?; } FormatElement::Interned(interned) => { - let cleaned = self.clean_interned(&interned); + let cleaned = self.clean_interned(interned); self.inner.write_element(FormatElement::Interned(cleaned))?; } // Since this buffer aims to simulate infinite print width, we don't need to retain the best fitting. // Just extract the flattest variant and then handle elements within it. FormatElement::BestFitting(best_fitting) => { let most_flat = best_fitting.most_flat(); - most_flat.iter().rev().for_each(|element| element_stack.push(element.clone())); + element_stack.extend(most_flat.iter().rev().cloned()); } element => self.inner.write_element(element)?, } diff --git a/crates/oxc_formatter/src/formatter/builders.rs b/crates/oxc_formatter/src/formatter/builders.rs index 52fbade685f1c..4a2ea5c0308fd 100644 --- a/crates/oxc_formatter/src/formatter/builders.rs +++ b/crates/oxc_formatter/src/formatter/builders.rs @@ -6,6 +6,7 @@ use Tag::{ StartDedent, StartEntry, StartFill, StartGroup, StartIndent, StartIndentIfGroupBreaks, StartLabelled, StartLineSuffix, }; +use oxc_allocator::Vec as ArenaVec; use oxc_span::{GetSpan, Span}; use oxc_syntax::identifier::{is_identifier_name, is_line_terminator, is_white_space_single_line}; @@ -2549,9 +2550,12 @@ impl<'ast> Format<'ast> for BestFitting<'_, 'ast> { buffer.write_fmt(Arguments::from(variant))?; buffer.write_element(FormatElement::Tag(EndEntry))?; - formatted_variants.push(buffer.take_vec().into_boxed_slice()); + formatted_variants.push(buffer.take_vec().into_bump_slice()); } + let formatted_variants = + ArenaVec::from_iter_in(formatted_variants, f.context().allocator()); + // SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore, // safe to call into the unsafe `from_vec_unchecked` function let element = unsafe { diff --git a/crates/oxc_formatter/src/formatter/format_element/document.rs b/crates/oxc_formatter/src/formatter/format_element/document.rs index 44a9c8584f847..fa1604227820f 100644 --- a/crates/oxc_formatter/src/formatter/format_element/document.rs +++ b/crates/oxc_formatter/src/formatter/format_element/document.rs @@ -2,7 +2,7 @@ use cow_utils::CowUtils; use std::ops::Deref; -use oxc_allocator::Allocator; +use oxc_allocator::{Allocator, Vec as ArenaVec}; use oxc_ast::Comment; use oxc_span::SourceType; use rustc_hash::FxHashMap; @@ -21,7 +21,7 @@ use crate::{format, write}; /// A formatted document. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct Document<'a> { - elements: Vec>, + elements: &'a [FormatElement<'a>], } impl Document<'_> { @@ -140,9 +140,9 @@ impl Document<'_> { } } -impl<'a> From>> for Document<'a> { - fn from(elements: Vec>) -> Self { - Self { elements } +impl<'a> From>> for Document<'a> { + fn from(elements: ArenaVec<'a, FormatElement<'a>>) -> Self { + Self { elements: elements.into_bump_slice() } } } @@ -150,7 +150,7 @@ impl<'a> Deref for Document<'a> { type Target = [FormatElement<'a>]; fn deref(&self) -> &Self::Target { - self.elements.as_slice() + self.elements } } @@ -158,8 +158,8 @@ impl std::fmt::Display for Document<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let allocator = Allocator::default(); let context = FormatContext::dummy(&allocator); - let formatted = format!(context, [self.elements.as_slice()]) - .expect("Formatting not to throw any FormatErrors"); + let formatted = + format!(context, [self.elements]).expect("Formatting not to throw any FormatErrors"); f.write_str(formatted.print().expect("Expected a valid document").as_code()) } diff --git a/crates/oxc_formatter/src/formatter/format_element/mod.rs b/crates/oxc_formatter/src/formatter/format_element/mod.rs index bd58c5b49bb46..afec6bcc865df 100644 --- a/crates/oxc_formatter/src/formatter/format_element/mod.rs +++ b/crates/oxc_formatter/src/formatter/format_element/mod.rs @@ -5,10 +5,13 @@ pub mod tag; // use biome_rowan::static_assert; use std::hash::{Hash, Hasher}; use std::num::NonZeroU32; +use std::ptr; use std::{borrow::Cow, ops::Deref, rc::Rc}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use oxc_allocator::{Address, Box as ArenaBox, Vec as ArenaVec}; + use crate::{IndentWidth, TabWidth}; use super::{ @@ -146,28 +149,25 @@ impl PrintMode { } #[derive(Clone)] -pub struct Interned<'a>(Rc<[FormatElement<'a>]>); +pub struct Interned<'a>(&'a [FormatElement<'a>]); impl<'a> Interned<'a> { - pub(super) fn new(content: Vec>) -> Self { - Self(content.into()) + pub(super) fn new(content: ArenaVec<'a, FormatElement<'a>>) -> Self { + Self(content.into_bump_slice()) } } impl PartialEq for Interned<'_> { fn eq(&self, other: &Interned<'_>) -> bool { - Rc::ptr_eq(&self.0, &other.0) + ptr::eq(self.0, other.0) } } impl Eq for Interned<'_> {} impl Hash for Interned<'_> { - fn hash(&self, hasher: &mut H) - where - H: Hasher, - { - Rc::as_ptr(&self.0).hash(hasher); + fn hash(&self, state: &mut H) { + self.0.as_ptr().addr().hash(state); } } @@ -181,7 +181,7 @@ impl<'a> Deref for Interned<'a> { type Target = [FormatElement<'a>]; fn deref(&self) -> &Self::Target { - &self.0 + self.0 } } @@ -305,7 +305,7 @@ pub struct BestFittingElement<'a> { /// The different variants for this element. /// The first element is the one that takes up the most space horizontally (the most flat), /// The last element takes up the least space horizontally (but most horizontal space). - variants: Box<[Box<[FormatElement<'a>]>]>, + variants: &'a [&'a [FormatElement<'a>]], } impl<'a> BestFittingElement<'a> { @@ -318,13 +318,13 @@ impl<'a> BestFittingElement<'a> { /// ## Safety /// The slice must contain at least two variants. #[doc(hidden)] - pub unsafe fn from_vec_unchecked(variants: Vec]>>) -> Self { + pub unsafe fn from_vec_unchecked(variants: ArenaVec<'a, &'a [FormatElement<'a>]>) -> Self { debug_assert!( variants.len() >= 2, "Requires at least the least expanded and most expanded variants" ); - Self { variants: variants.into_boxed_slice() } + Self { variants: variants.into_bump_slice() } } /// Returns the most expanded variant @@ -334,8 +334,8 @@ impl<'a> BestFittingElement<'a> { ) } - pub fn variants(&self) -> &[Box<[FormatElement<'a>]>] { - &self.variants + pub fn variants(&self) -> &[&'a [FormatElement<'a>]] { + self.variants } /// Returns the least expanded variant @@ -348,7 +348,7 @@ impl<'a> BestFittingElement<'a> { impl std::fmt::Debug for BestFittingElement<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_list().entries(&*self.variants).finish() + f.debug_list().entries(self.variants).finish() } } diff --git a/crates/oxc_formatter/src/formatter/formatter.rs b/crates/oxc_formatter/src/formatter/formatter.rs index cb5b944e690d4..6c13b5fa11ecc 100644 --- a/crates/oxc_formatter/src/formatter/formatter.rs +++ b/crates/oxc_formatter/src/formatter/formatter.rs @@ -1,6 +1,6 @@ #![allow(clippy::module_inception)] -use oxc_allocator::{Address, Allocator}; +use oxc_allocator::{Address, Allocator, Vec as ArenaVec}; use oxc_ast::AstKind; use crate::options::FormatOptions; @@ -27,7 +27,7 @@ impl<'buf, 'ast> Formatter<'buf, 'ast> { Self { buffer } } - pub fn allocator(&self) -> &Allocator { + pub fn allocator(&self) -> &'ast Allocator { self.context().allocator() } @@ -244,7 +244,7 @@ impl<'buf, 'ast> Formatter<'buf, 'ast> { pub fn intern_vec( &mut self, - mut elements: Vec>, + mut elements: ArenaVec<'ast, FormatElement<'ast>>, ) -> Option> { match elements.len() { 0 => None, diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs index 342914a9ff898..daef4a6dd0759 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs @@ -2,6 +2,10 @@ mod import_unit; mod partitioned_chunk; mod source_line; +use std::mem; + +use oxc_allocator::{Allocator, Vec as ArenaVec}; + use crate::{ formatter::format_element::{FormatElement, LineMode, document::Document}, options, @@ -26,7 +30,7 @@ impl SortImportsTransform { // It means that: // - There is no redundant spaces, no consecutive line breaks, etc... // - Last element is always `FormatElement::Line(Hard)`. - pub fn transform<'a>(&self, document: &Document<'a>) -> Document<'a> { + pub fn transform<'a>(&self, document: &Document<'a>, allocator: &'a Allocator) -> Document<'a> { // Early return for empty files if document.len() == 1 && matches!(document[0], FormatElement::Line(LineMode::Hard)) { return document.clone(); @@ -136,7 +140,7 @@ impl SortImportsTransform { // Finally, sort import lines within each chunk. // After sorting, flatten everything back to `FormatElement`s. - let mut next_elements = Vec::with_capacity(prev_elements.len()); + let mut next_elements = ArenaVec::with_capacity_in(prev_elements.len(), allocator); let mut chunks_iter = chunks.into_iter().enumerate().peekable(); while let Some((idx, chunk)) = chunks_iter.next() { diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs index 02d5fcd1566fa..8c08d64d433de 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/source_line.rs @@ -1,5 +1,7 @@ use std::ops::Range; +use oxc_allocator::Vec as ArenaVec; + use crate::{ JsLabels, formatter::format_element::{ @@ -153,7 +155,7 @@ impl SourceLine { pub fn write<'a>( &self, prev_elements: &[FormatElement<'a>], - next_elements: &mut Vec>, + next_elements: &mut ArenaVec<'a, FormatElement<'a>>, preserve_empty_line: bool, ) { match self { diff --git a/crates/oxc_formatter/src/lib.rs b/crates/oxc_formatter/src/lib.rs index 4667291bfec8c..dc9a28eaa60cf 100644 --- a/crates/oxc_formatter/src/lib.rs +++ b/crates/oxc_formatter/src/lib.rs @@ -103,7 +103,7 @@ impl<'a> Formatter<'a> { // Now apply additional transforms if enabled. if let Some(sort_imports_options) = experimental_sort_imports { let sort_imports = SortImportsTransform::new(sort_imports_options); - formatted.apply_transform(|doc| sort_imports.transform(doc)); + formatted.apply_transform(|doc| sort_imports.transform(doc, self.allocator)); } formatted diff --git a/crates/oxc_formatter/src/write/call_arguments.rs b/crates/oxc_formatter/src/write/call_arguments.rs index 80c6aabfeb75d..cf56828937d15 100644 --- a/crates/oxc_formatter/src/write/call_arguments.rs +++ b/crates/oxc_formatter/src/write/call_arguments.rs @@ -708,7 +708,7 @@ fn write_grouped_arguments<'a>( buffer.write_element(FormatElement::Tag(Tag::EndEntry))?; - buffer.into_vec() + buffer.into_vec().into_bump_slice() }; // Now reformat the first or last argument if they happen to be a function or arrow function expression. @@ -808,14 +808,14 @@ fn write_grouped_arguments<'a>( buffer.write_element(FormatElement::Tag(Tag::EndEntry))?; - buffer.into_vec().into_boxed_slice() + buffer.into_vec().into_bump_slice() }; // If the grouped content breaks, then we can skip the most_flat variant, // since we already know that it won't be fitting on a single line. let variants = if grouped_breaks { write!(f, [expand_parent()])?; - vec![middle_variant, most_expanded.into_boxed_slice()] + ArenaVec::from_array_in([middle_variant, most_expanded], f.context().allocator()) } else { // Write the most flat variant with the first or last argument grouped. let most_flat = { @@ -845,10 +845,10 @@ fn write_grouped_arguments<'a>( buffer.write_element(FormatElement::Tag(Tag::EndEntry))?; - buffer.into_vec().into_boxed_slice() + buffer.into_vec().into_bump_slice() }; - vec![most_flat, middle_variant, most_expanded.into_boxed_slice()] + ArenaVec::from_array_in([most_flat, middle_variant, most_expanded], f.context().allocator()) }; // SAFETY: Safe because variants is guaranteed to contain exactly 3 entries: diff --git a/crates/oxc_formatter/src/write/jsx/child_list.rs b/crates/oxc_formatter/src/write/jsx/child_list.rs index 204d7342431e1..c38bbb4c64de7 100644 --- a/crates/oxc_formatter/src/write/jsx/child_list.rs +++ b/crates/oxc_formatter/src/write/jsx/child_list.rs @@ -1,4 +1,4 @@ -use oxc_allocator::Vec as ArenaVec; +use oxc_allocator::{Allocator, TakeIn, Vec as ArenaVec}; use oxc_ast::ast::*; use oxc_span::GetSpan; @@ -47,8 +47,8 @@ impl FormatJsxChildList { MultilineLayout::NoFill }; - let mut flat = FlatBuilder::new(); - let mut multiline = MultilineBuilder::new(multiline_layout); + let mut flat = FlatBuilder::new(f.context().allocator()); + let mut multiline = MultilineBuilder::new(multiline_layout, f.context().allocator()); let mut force_multiline = layout.is_multiline(); @@ -549,15 +549,15 @@ enum MultilineLayout { /// /// This builder takes care of doing the least amount of work necessary for the chosen layout while also guaranteeing /// that the written element is valid -#[derive(Debug, Clone)] +#[derive(Debug)] struct MultilineBuilder<'a> { layout: MultilineLayout, - result: FormatResult>>, + result: FormatResult>>, } impl<'a> MultilineBuilder<'a> { - fn new(layout: MultilineLayout) -> Self { - Self { layout, result: Ok(Vec::new()) } + fn new(layout: MultilineLayout, allocator: &'a Allocator) -> Self { + Self { layout, result: Ok(ArenaVec::new_in(allocator)) } } /// Formats an element that does not require a separator @@ -587,7 +587,8 @@ impl<'a> MultilineBuilder<'a> { separator: Option<&dyn Format<'a>>, f: &mut Formatter<'_, 'a>, ) { - let result = std::mem::replace(&mut self.result, Ok(Vec::new())); + let result = + std::mem::replace(&mut self.result, Ok(ArenaVec::new_in(f.context().allocator()))); self.result = result.and_then(|elements| { let elements = { @@ -628,13 +629,15 @@ impl<'a> MultilineBuilder<'a> { #[derive(Debug)] pub struct FormatMultilineChildren<'a> { layout: MultilineLayout, - elements: RefCell>>, + elements: RefCell>>, } impl<'a> Format<'a> for FormatMultilineChildren<'a> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let format_inner = format_once(|f| { - if let Some(elements) = f.intern_vec(self.elements.take()) { + if let Some(elements) = + f.intern_vec(self.elements.borrow_mut().take_in(f.context().allocator())) + { match self.layout { MultilineLayout::Fill => f.write_elements([ FormatElement::Tag(Tag::StartFill), @@ -682,13 +685,13 @@ impl<'a> Format<'a> for FormatMultilineChildren<'a> { #[derive(Debug)] struct FlatBuilder<'a> { - result: FormatResult>>, + result: FormatResult>>, disabled: bool, } impl<'a> FlatBuilder<'a> { - fn new() -> Self { - Self { result: Ok(Vec::new()), disabled: false } + fn new(allocator: &'a Allocator) -> Self { + Self { result: Ok(ArenaVec::new_in(allocator)), disabled: false } } fn write(&mut self, content: &dyn Format<'a>, f: &mut Formatter<'_, 'a>) { @@ -696,7 +699,8 @@ impl<'a> FlatBuilder<'a> { return; } - let result = std::mem::replace(&mut self.result, Ok(Vec::new())); + let result = + std::mem::replace(&mut self.result, Ok(ArenaVec::new_in(f.context().allocator()))); self.result = result.and_then(|elements| { let mut buffer = VecBuffer::new_with_vec(f.state_mut(), elements); @@ -723,12 +727,14 @@ impl<'a> FlatBuilder<'a> { #[derive(Debug)] pub struct FormatFlatChildren<'a> { - elements: RefCell>>, + elements: RefCell>>, } impl<'a> Format<'a> for FormatFlatChildren<'a> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if let Some(elements) = f.intern_vec(self.elements.take()) { + if let Some(elements) = + f.intern_vec(self.elements.borrow_mut().take_in(f.context().allocator())) + { f.write_element(elements)?; } Ok(())