Skip to content

Commit b6529c8

Browse files
committed
feat(data_structures): add CodeBuffer::*_unchecked_cap methods
1 parent 61beb43 commit b6529c8

File tree

1 file changed

+170
-1
lines changed

1 file changed

+170
-1
lines changed

crates/oxc_data_structures/src/code_buffer.rs

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! A string builder for constructing source code.
22
3-
use std::iter;
3+
use std::{iter, ptr};
44

55
use crate::assert_unchecked;
66

@@ -287,6 +287,43 @@ impl CodeBuffer {
287287
}
288288
}
289289

290+
/// Push a byte to the buffer, without checking that there's sufficient capacity, and without checking
291+
/// that the buffer still represents a valid UTF-8 string.
292+
///
293+
/// Useful for reducing number of bounds checks when pushing multiple batches of bytes/strings.
294+
///
295+
/// # SAFETY
296+
///
297+
/// All the safety constraints of [`print_byte_unchecked`] apply.
298+
///
299+
/// Additionally, caller must ensure buffer is not full to capacity.
300+
/// i.e. `self.capacity() - self.len() >= 1`.
301+
///
302+
/// # Example
303+
///
304+
/// ```rs
305+
/// # use oxc_data_structures::code_buffer::CodeBuffer;
306+
/// let mut code = CodeBuffer::new();
307+
/// code.reserve(2);
308+
///
309+
/// // SAFETY: 'a' and 'b' are a valid ASCII characters.
310+
/// // Their UTF-8 representation only require a single byte.
311+
/// // We reserved 2 bytes capacity above.
312+
/// unsafe {
313+
/// code.print_byte_unchecked_cap(b'a');
314+
/// code.print_byte_unchecked_cap(b'b');
315+
/// }
316+
/// ```
317+
///
318+
/// [`print_byte_unchecked`]: Self::print_byte_unchecked
319+
#[inline]
320+
pub unsafe fn print_byte_unchecked_cap(&mut self, byte: u8) {
321+
// SAFETY: Caller guarantees buffer is not full to capacity, and they take responsibility
322+
// for ensuring that buffer is not put into a state where it contains an invalid UTF-8 string.
323+
unsafe { assert_unchecked!(self.buf.len() != self.buf.capacity()) }
324+
self.buf.push(byte);
325+
}
326+
290327
/// Push a single Unicode character into the buffer.
291328
///
292329
/// When pushing multiple characters, consider choosing [`print_str`] over this method
@@ -326,6 +363,38 @@ impl CodeBuffer {
326363
self.buf.extend_from_slice(s.as_ref().as_bytes());
327364
}
328365

366+
/// Push a string into the buffer, without checking there is sufficient capacity.
367+
///
368+
/// Useful for reducing number of bounds checks when pushing multiple batches of bytes/strings.
369+
///
370+
/// # SAFETY
371+
///
372+
/// There must be sufficient spare capacity in the buffer for the string `s`.
373+
/// i.e. `self.capacity() - self.len() >= s.len()`.
374+
///
375+
/// # Example
376+
///
377+
/// ```rs
378+
/// # use oxc_data_structures::code_buffer::CodeBuffer;
379+
/// let mut code = CodeBuffer::new();
380+
///
381+
/// let s1 = "abcde";
382+
/// let s2 = "fghij";
383+
///
384+
/// code.reserve(s1.len() + s2.len());
385+
/// // SAFETY: Have reserved sufficient capacity
386+
/// unsafe {
387+
/// code.print_str_unchecked_cap(s1);
388+
/// code.print_str_unchecked_cap(s2);
389+
/// }
390+
/// ```
391+
#[inline]
392+
pub unsafe fn print_str_unchecked_cap<S: AsRef<str>>(&mut self, s: S) {
393+
let bytes = s.as_ref().as_bytes();
394+
// SAFETY: Caller guarantees there is sufficient capacity in buffer for `s`
395+
unsafe { self.print_bytes_unchecked_cap(bytes) };
396+
}
397+
329398
/// Push a sequence of ASCII characters into the buffer.
330399
///
331400
/// # Panics
@@ -381,6 +450,50 @@ impl CodeBuffer {
381450
self.buf.extend_from_slice(bytes);
382451
}
383452

453+
/// Print a slice of bytes, without checking there is sufficient capacity,
454+
/// and without checking that the buffer still contains a valid UTF-8 string.
455+
///
456+
/// Useful for reducing number of bounds checks when pushing multiple batches of bytes/strings.
457+
///
458+
/// # SAFETY
459+
///
460+
/// All the safety requirements of [`print_bytes_unchecked`] apply.
461+
///
462+
/// Additionally, there must be sufficient spare capacity in the buffer for `bytes`.
463+
/// i.e. `self.capacity() - self.len() >= bytes.len()`.
464+
///
465+
/// # Example
466+
///
467+
/// ```rs
468+
/// # use oxc_data_structures::code_buffer::CodeBuffer;
469+
/// let mut code = CodeBuffer::new();
470+
///
471+
/// let b1 = b"abcde";
472+
/// let b2 = b"fghij";
473+
///
474+
/// code.reserve(b1.len() + b2.len());
475+
/// // SAFETY: Have reserved sufficient capacity.
476+
/// // `b1` and `b2` only contain ASCII bytes.
477+
/// unsafe {
478+
/// code.print_bytes_unchecked_cap(b1);
479+
/// code.print_bytes_unchecked_cap(b2);
480+
/// }
481+
/// ```
482+
///
483+
/// [`print_bytes_unchecked`]: Self::print_bytes_unchecked
484+
#[inline]
485+
pub unsafe fn print_bytes_unchecked_cap(&mut self, bytes: &[u8]) {
486+
let src = bytes.as_ptr();
487+
let buf_len = self.len();
488+
let bytes_len = bytes.len();
489+
// SAFETY: Caller guarantees there is sufficient capacity in buffer to contain `bytes`
490+
unsafe {
491+
let dst = self.buf.as_mut_ptr().add(buf_len);
492+
ptr::copy_nonoverlapping(src, dst, bytes_len);
493+
self.buf.set_len(buf_len + bytes_len);
494+
}
495+
}
496+
384497
/// Print a sequence of bytes, without checking that the buffer still contains a valid UTF-8 string.
385498
///
386499
/// # SAFETY
@@ -541,6 +654,26 @@ mod test {
541654
assert_eq!(source, s);
542655
}
543656

657+
#[test]
658+
fn print_str_unchecked_cap() {
659+
let mut code = CodeBuffer::new();
660+
661+
let s1 = "Hello, ";
662+
let s2 = "world";
663+
let s3 = "!";
664+
665+
code.reserve(s1.len() + s2.len() + s3.len());
666+
// SAFETY: Have reserved sufficient capacity
667+
unsafe {
668+
code.print_str_unchecked_cap(s1);
669+
code.print_str_unchecked_cap(s2);
670+
code.print_str_unchecked_cap(s3);
671+
}
672+
673+
assert_eq!(code.len(), s1.len() + s2.len() + s3.len());
674+
assert_eq!(String::from(code), "Hello, world!");
675+
}
676+
544677
#[test]
545678
#[expect(clippy::byte_char_slices)]
546679
fn print_ascii_byte() {
@@ -570,6 +703,22 @@ mod test {
570703
assert_eq!(String::from(code), "foo");
571704
}
572705

706+
#[test]
707+
#[expect(clippy::byte_char_slices)]
708+
fn print_byte_unchecked_cap() {
709+
let mut code = CodeBuffer::with_capacity(3);
710+
// SAFETY: These bytes are all ASCII
711+
unsafe {
712+
code.print_byte_unchecked_cap(b'f');
713+
code.print_byte_unchecked_cap(b'o');
714+
code.print_byte_unchecked_cap(b'o');
715+
}
716+
717+
assert_eq!(code.len(), 3);
718+
assert_eq!(code.as_bytes(), &[b'f', b'o', b'o']);
719+
assert_eq!(String::from(code), "foo");
720+
}
721+
573722
#[test]
574723
#[expect(clippy::byte_char_slices)]
575724
fn print_bytes_unchecked() {
@@ -582,6 +731,26 @@ mod test {
582731
assert_eq!(String::from(code), "foo");
583732
}
584733

734+
#[test]
735+
fn print_bytes_unchecked_cap() {
736+
let mut code = CodeBuffer::new();
737+
738+
let s1 = b"Hello, ";
739+
let s2 = b"world";
740+
let s3 = b"!";
741+
742+
code.reserve(s1.len() + s2.len() + s3.len());
743+
// SAFETY: Have reserved sufficient capacity. All bytes are ASCII.
744+
unsafe {
745+
code.print_bytes_unchecked_cap(s1);
746+
code.print_bytes_unchecked_cap(s2);
747+
code.print_bytes_unchecked_cap(s3);
748+
}
749+
750+
assert_eq!(code.len(), s1.len() + s2.len() + s3.len());
751+
assert_eq!(String::from(code), "Hello, world!");
752+
}
753+
585754
#[test]
586755
#[expect(clippy::byte_char_slices)]
587756
fn print_bytes_iter_unchecked() {

0 commit comments

Comments
 (0)