docs(str): reformat and simplify comments (#27011)

Follow-on after #26435. Comments-only change, and mostly just stylistic.

Reformat comments (I hate how LLMs put line-breaks in weird places, making comments hard to read). Also clarify wording in a few places.
This commit is contained in:
overlookmotel committed 2026-09-27 08:10:02 +00:00
1 parent 02331c8f70
commit dfa6cf9793
4 files changed
+128 -85

No files matched your search

+2 -2
View File
@@ -26,14 +26,14 @@ impl JSChar {
if self.is_surrogate() {
None
} else {
// SAFETY: `JSChar` is at most 0x10_FFFF, and we excluded surrogates.
// SAFETY: `JSChar` is at most 0x10_FFFF, and we excluded surrogates
Some(unsafe { char::from_u32_unchecked(self.0) })
}
}
/// Construct a code point without checking its range.
///
/// # Safety
/// # SAFETY
/// `value` must be at most `0x10_FFFF`.
#[inline]
pub(super) const unsafe fn from_u32_unchecked(value: u32) -> Self {
+88 -58
View File
@@ -13,46 +13,50 @@ use crate::{Ident, JSChar, JSStrBuilder, Str};
/// An immutable JavaScript string borrowed from source text or arena memory.
///
/// JavaScript strings can contain lone surrogates, which Rust's [`prim@str`] cannot
/// represent. `JSStr` stores [canonical WTF-8](https://wtf-8.codeberg.page/):
/// Unicode scalar values use UTF-8, lone surrogates use three bytes, and a
/// surrogate pair uses one four-byte supplementary character.
/// JavaScript strings can contain lone surrogates, which Rust's [`str`] cannot represent.
///
/// [`JSStr`] stores [canonical WTF-8]:
/// * Unicode scalar values use UTF-8.
/// * Lone surrogates use three bytes.
/// * Surrogate pairs use one four-byte supplementary character.
///
/// # Invariants
///
/// * The bytes are canonical WTF-8. In particular, a lead surrogate encoding
/// cannot be immediately followed by a trail surrogate encoding.
/// * `has_lone_surrogate` is true if and only if the bytes encode a lone surrogate.
/// When false, the bytes are valid UTF-8.
/// * `has_lone_surrogate` is `true` if and only if the bytes encode at least 1 lone surrogate.
/// When `false`, the bytes are valid UTF-8.
/// * The pointer references `len` initialized bytes, valid and immutable for `'a`.
/// * The byte length fits in `u32` and does not exceed `isize::MAX`.
///
/// All constructors maintain these invariants. [`as_bytes`](Self::as_bytes)
/// exposes the bytes read-only and [`from_bytes_unchecked`](Self::from_bytes_unchecked)
/// re-borrows them under the same invariants; consumers read the value with
/// [`as_str`](Self::as_str), [`chars`](Self::chars), or
/// [`encode_utf16`](Self::encode_utf16).
/// All constructors maintain these invariants.
/// * [`as_bytes`] exposes the bytes read-only.
/// * [`from_bytes_unchecked`] re-borrows them under the same invariants.
///
/// Consumers read the value with [`as_str`], [`chars`], or [`encode_utf16`].
///
/// Equality uses the canonical bytes, and UTF-8 values hash like `str`. Ordering
/// by these bytes would differ from JavaScript's UTF-16 ordering, so `JSStr`
/// does not implement `Ord`.
///
/// ```
/// use oxc_allocator::Allocator;
/// # use oxc_allocator::Allocator;
/// # let allocator = Allocator::new();
/// use oxc_str::JSStrBuilder;
///
/// let allocator = Allocator::new();
/// let mut builder = JSStrBuilder::new_in(&&allocator);
/// builder.push_code_unit(0xD800);
/// builder.push('a');
/// let value = builder.into_js_str();
/// assert_eq!(value.as_str(), None);
/// assert_eq!(value.encode_utf16().collect::<Vec<_>>(), [0xD800, 0x61]);
/// let s = builder.into_js_str();
///
/// assert_eq!(s.as_str(), None);
/// assert_eq!(s.encode_utf16().collect::<Vec<_>>(), [0xD800, 0x61]);
/// ```
///
/// Borrowing source text does not extend its lifetime:
///
/// ```compile_fail
/// use oxc_str::JSStr;
/// # use oxc_str::JSStr;
/// let value;
/// {
/// let source = String::from("hello");
@@ -62,9 +66,10 @@ use crate::{Ident, JSChar, JSStrBuilder, Str};
/// ```
///
/// Arena-backed strings prevent resetting the arena while still in use:
///
/// ```compile_fail
/// use oxc_allocator::Allocator;
/// use oxc_str::JSStrBuilder;
/// # use oxc_allocator::Allocator;
/// # use oxc_str::JSStrBuilder;
/// let mut allocator = Allocator::new();
/// let mut builder = JSStrBuilder::new_in(&&allocator);
/// builder.push_code_unit(0xD800);
@@ -72,6 +77,14 @@ use crate::{Ident, JSChar, JSStrBuilder, Str};
/// allocator.reset();
/// println!("{value:?}");
/// ```
///
/// [`str`]: prim@str
/// [canonical WTF-8]: https://wtf-8.codeberg.page/
/// [`as_bytes`]: Self::as_bytes
/// [`from_bytes_unchecked`]: Self::from_bytes_unchecked
/// [`as_str`]: Self::as_str
/// [`chars`]: Self::chars
/// [`encode_utf16`]: Self::encode_utf16
#[derive(Clone, Copy)]
#[repr(C)]
pub struct JSStr<'a> {
@@ -91,15 +104,18 @@ impl JSStr<'static> {
impl<'a> JSStr<'a> {
/// Borrow UTF-8 bytes. Their validity already proves the WTF-8 invariant.
///
/// # Panics
/// Panics if `value.len()` exceeds `u32::MAX`.
#[inline]
const fn from_str(value: &'a str) -> Self {
assert!(value.len() <= u32::MAX as usize, "JavaScript string exceeds u32::MAX bytes");
// SAFETY: `str` is UTF-8 (thus canonical WTF-8 without surrogates), and
// its bytes are immutable and valid for `'a`. The length was checked.
// SAFETY: `str` is UTF-8 (thus canonical WTF-8 without surrogates),
// and its bytes are immutable and valid for `'a`. The length was checked.
unsafe { Self::from_bytes_unchecked(value.as_bytes(), false) }
}
/// Copy UTF-8 text into an arena.
/// Copy UTF-8 text into arena.
///
/// # Panics
/// Panics if the byte length exceeds `u32::MAX`.
@@ -111,8 +127,8 @@ impl<'a> JSStr<'a> {
/// Concatenate JavaScript strings into an arena.
///
/// Leading and trailing surrogates pair across string boundaries, including
/// empty strings between them. Inputs are copied into the destination arena.
/// Leading and trailing surrogates pair across string boundaries, including empty strings between them.
/// Inputs are copied into the destination arena.
///
/// # Panics
/// Panics if the sum of input byte lengths exceeds `u32::MAX` or `isize::MAX`.
@@ -134,21 +150,23 @@ impl<'a> JSStr<'a> {
/// Borrow the value as UTF-8, or return `None` if it contains a lone surrogate.
///
/// This checks the cached flag in O(1); it does not scan the bytes.
/// This checks the cached flag in O(1). It does not scan the bytes.
#[inline]
pub fn as_str(self) -> Option<&'a str> {
if self.has_lone_surrogate() {
None
} else {
// SAFETY: By `JSStr`'s invariant, canonical WTF-8 with a false
// surrogate flag is valid UTF-8.
// SAFETY: By `JSStr`'s invariant, canonical WTF-8 with no lone surrogates
// is valid UTF-8
Some(unsafe { str::from_utf8_unchecked(self.as_bytes()) })
}
}
/// Return the byte length of the WTF-8 representation.
///
/// Use [`len_utf16`](Self::len_utf16) for JavaScript's string length.
/// Use [`len_utf16`] for JavaScript's string length.
///
/// [`len_utf16`]: Self::len_utf16
#[inline]
pub const fn len(self) -> usize {
self.len as usize
@@ -160,7 +178,9 @@ impl<'a> JSStr<'a> {
self.len == 0
}
/// Return whether the string contains a lone surrogate, in O(1).
/// Return whether the string contains a lone surrogate.
///
/// This checks the cached flag in O(1). It does not scan the bytes.
#[inline]
pub const fn has_lone_surrogate(self) -> bool {
self.has_lone_surrogate
@@ -170,9 +190,10 @@ impl<'a> JSStr<'a> {
///
/// This scans the bytes in O(n) time.
pub fn len_utf16(self) -> usize {
// Every non-continuation byte starts one code point. Four-byte code
// points contribute a second code unit. Valid WTF-8 has no other bytes
// >= 0xF0, so this counts code units without decoding individual points.
// Every non-continuation byte starts one code point.
// Four-byte code points contribute a second code unit.
// Valid WTF-8 has no other bytes >= 0xF0, so this counts code units
// without decoding individual points.
self.as_bytes()
.iter()
.map(|&byte| usize::from(byte & 0xC0 != 0x80) + usize::from(byte >= 0xF0))
@@ -193,10 +214,12 @@ impl<'a> JSStr<'a> {
/// Borrow the underlying canonical WTF-8 bytes.
///
/// Together with [`has_lone_surrogate`](Self::has_lone_surrogate), the
/// bytes are a complete representation: storage held outside an arena can
/// keep them and borrow the value back with
/// [`from_bytes_unchecked`](Self::from_bytes_unchecked).
/// Together with [`has_lone_surrogate`], the bytes are a complete representation.
/// Storage held outside an arena can keep them and convert back to a [`JSStr`]
/// with [`from_bytes_unchecked`].
///
/// [`has_lone_surrogate`]: Self::has_lone_surrogate
/// [`from_bytes_unchecked`]: Self::from_bytes_unchecked
#[inline]
pub fn as_bytes(self) -> &'a [u8] {
// SAFETY: `JSStr`'s pointer references `len` initialized bytes, valid
@@ -205,17 +228,19 @@ impl<'a> JSStr<'a> {
}
/// Borrow bytes whose encoding and metadata have already been established,
/// such as bytes previously taken from [`as_bytes`](Self::as_bytes)
/// together with the value's surrogate flag.
/// such as bytes previously taken from [`as_bytes`] together with the lone surrogate flag.
///
/// # Safety
/// * `bytes` must be canonical WTF-8: valid WTF-8 in which a lead
/// surrogate encoding is never followed by a trail surrogate encoding.
/// # SAFETY
///
/// * `bytes` must be canonical WTF-8 - valid WTF-8 in which a lead surrogate encoding
/// is never followed by a trail surrogate encoding.
/// * `has_lone_surrogate` must exactly describe whether they encode a lone surrogate.
/// * `bytes.len()` must fit in `u32`.
///
/// The reference itself guarantees initialization, immutability, lifetime,
/// and the `isize::MAX` bound.
/// The `&[u8]` reference itself guarantees initialization, immutability, lifetime,
/// and that `bytes.len()` cannot exceed `isize::MAX`.
///
/// [`as_bytes`]: Self::as_bytes
#[inline]
#[expect(clippy::cast_possible_truncation, reason = "the caller guarantees the length fits")]
pub const unsafe fn from_bytes_unchecked(bytes: &'a [u8], has_lone_surrogate: bool) -> Self {
@@ -228,14 +253,17 @@ impl<'a> JSStr<'a> {
}
}
// SAFETY: The only referenced storage is an immutable byte slice valid for
// `'a`. `JSStr` exposes neither mutation nor an allocator reference.
/// SAFETY: The only referenced storage is an immutable byte slice valid for `'a`.
/// `JSStr` exposes neither mutation nor an allocator reference.
unsafe impl Send for JSStr<'_> {}
// SAFETY: Sharing `JSStr` only shares immutable bytes, as with `&[u8]`.
/// SAFETY: Sharing `JSStr` only shares immutable bytes, as with `&[u8]`.
unsafe impl Sync for JSStr<'_> {}
impl<'a> From<&'a str> for JSStr<'a> {
/// Borrow UTF-8 without allocating. Panics if its byte length exceeds `u32::MAX`.
/// Borrow UTF-8 without allocating.
///
/// # Panics
/// Panics if `value.len()` exceeds `u32::MAX`.
#[inline]
fn from(value: &'a str) -> Self {
Self::from_str(value)
@@ -323,8 +351,8 @@ impl fmt::Debug for JSStr<'_> {
f.write_char('"')?;
for c in self.chars() {
if let Some(c) = c.to_char() {
// `char::escape_debug` escapes a quote for `char` literals;
// `str`'s Debug prints it plain, and this output is a string.
// `char::escape_debug` escapes a quote for `char` literals.
// `str`'s `Debug` prints it plain, and this output is a string.
if c == '\'' {
f.write_char(c)?;
} else {
@@ -363,7 +391,9 @@ impl<'a> Dummy<'a> for JSStr<'a> {
}
}
/// `remaining` is always canonical WTF-8 starting at a code-point boundary.
/// Iterator over [`JSChar`]s comprising a [`JSStr`].
//
// `remaining` is always canonical WTF-8 starting at a code-point boundary.
#[derive(Clone)]
struct JSChars<'a> {
remaining: &'a [u8],
@@ -378,10 +408,9 @@ impl Iterator for JSChars<'_> {
let (value, len) = if first < 0x80 {
(u32::from(first), 0)
} else {
// SAFETY: `remaining` is complete, valid WTF-8 at a code-point
// boundary. Its leading byte therefore determines how many
// continuation bytes are present. Their bit patterns and the
// encoding's range restrictions guarantee a value <= 0x10FFFF.
// SAFETY: `remaining` is complete, valid WTF-8 at a code-point boundary.
// Its leading byte therefore determines how many continuation bytes are present.
// Their bit patterns and the encoding's range restrictions guarantee a value <= 0x10_FFFF.
unsafe {
let second = u32::from(*rest.get_unchecked(0) & 0x3F);
if first < 0xE0 {
@@ -397,10 +426,10 @@ impl Iterator for JSChars<'_> {
}
}
};
// SAFETY: The complete code point has `len` continuation bytes, as
// established above. The suffix begins at the next code-point boundary.
// SAFETY: The complete code point has `len` continuation bytes, as established above.
// The suffix begins at the next code-point boundary.
self.remaining = unsafe { rest.get_unchecked(len..) };
// SAFETY: Decoding valid WTF-8 produces a code point <= 0x10FFFF.
// SAFETY: Decoding valid WTF-8 produces a code point <= 0x10_FFFF
Some(unsafe { JSChar::from_u32_unchecked(value) })
}
@@ -412,10 +441,11 @@ impl Iterator for JSChars<'_> {
impl FusedIterator for JSChars<'_> {}
/// Iterator over `u16` code points comprising a [`JSStr`].
#[derive(Clone)]
struct EncodeUtf16<'a> {
chars: JSChars<'a>,
/// Zero when empty, otherwise a trailing surrogate (which cannot be zero).
/// 0 when empty, otherwise a trailing surrogate (which cannot be 0).
pending: u16,
}
+3 -2
View File
@@ -14,8 +14,9 @@ fn from_utf16_in<'a>(units: &[u16], allocator: &'a Allocator) -> JSStr<'a> {
builder.into_js_str()
}
/// Decode the input independently with Rust's UTF-16 decoder. Valid scalar
/// values use std's UTF-8 encoder; errors supply the exact unpaired code unit.
/// Decode the input independently with Rust's UTF-16 decoder.
/// Valid scalar values use std's UTF-8 encoder.
/// Errors supply the exact unpaired code unit.
fn assert_value(value: JSStr<'_>, units: &[u16]) {
let mut expected_points = Vec::new();
let mut expected_bytes = Vec::new();
+35 -23
View File
@@ -2,36 +2,39 @@ use oxc_allocator::{ArenaVec, GetAllocator};
use crate::{JSChar, JSStr};
/// Build a JavaScript string in an arena, preserving lone surrogates.
/// Build a [`JSStr`] in an arena, preserving lone surrogates.
///
/// Appends concatenate UTF-16 values. A leading surrogate at the end of one
/// append pairs with a trailing surrogate at the start of the next, including
/// across empty appends. [`into_js_str`](Self::into_js_str) returns canonical WTF-8
/// without copying the completed buffer.
/// Appends concatenated UTF-16 values. A leading surrogate at the end of one append
/// pairs with a trailing surrogate at the start of the next, including across empty appends.
/// [`into_js_str`] returns canonical WTF-8 without copying the completed buffer.
///
/// ```
/// use oxc_allocator::Allocator;
/// # use oxc_allocator::Allocator;
/// # let allocator = Allocator::new();
/// use oxc_str::JSStrBuilder;
///
/// let allocator = Allocator::new();
/// let mut builder = JSStrBuilder::new_in(&&allocator);
/// builder.push_code_unit(0xD800);
/// builder.push_str("");
/// builder.push_code_unit(0xDC00);
/// assert_eq!(builder.into_js_str().as_str(), Some("𐀀"));
/// let s = builder.into_js_str();
///
/// assert_eq!(s.as_str(), Some("𐀀"));
/// ```
///
/// [`into_js_str`]: Self::into_js_str
pub struct JSStrBuilder<'a> {
/// Canonical WTF-8, excluding a held final leading surrogate.
bytes: ArenaVec<'a, u8>,
/// Always in 0xD800..=0xDBFF when present, with three bytes of spare capacity
/// in `bytes`, so finishing the string never needs to grow the buffer.
/// Always in 0xD800..=0xDBFF when present, with three bytes of spare capacity in `bytes`,
/// so finishing the string never needs to grow the buffer.
pending_lead_surrogate: Option<u16>,
/// Describes only `bytes`, excluding `pending_lead_surrogate`.
has_lone_surrogate: bool,
}
impl<'a> JSStrBuilder<'a> {
/// Construct an empty builder without allocating.
/// Create an empty [`JSStrBuilder`] without allocating.
#[inline]
pub fn new_in(allocator: &impl GetAllocator<'a>) -> Self {
Self {
@@ -41,10 +44,12 @@ impl<'a> JSStrBuilder<'a> {
}
}
/// Reserve capacity in bytes. Zero capacity does not allocate.
/// Create an empty [`JSStrBuilder`] with reserved capacity.
///
/// If `capacity` is 0, does not allocate.
///
/// # Panics
/// Panics if capacity exceeds `u32::MAX` or `isize::MAX`.
/// Panics if `capacity` exceeds `u32::MAX` or `isize::MAX`.
#[inline]
pub fn with_capacity_in(capacity: usize, allocator: &impl GetAllocator<'a>) -> Self {
Self {
@@ -54,14 +59,15 @@ impl<'a> JSStrBuilder<'a> {
}
}
/// Append UTF-8 text. Empty text preserves a pending leading surrogate.
/// Append a `&str`.
#[inline]
pub fn push_str(&mut self, value: &str) {
// Empty text preserves a pending leading surrogate
if value.is_empty() {
return;
}
// Reserve before changing state, so a capacity panic cannot leave a
// flushed leading surrogate that a later append would fail to pair.
// Reserve before changing state, so a capacity panic cannot leave
// a flushed leading surrogate that a later append would fail to pair
if self.pending_lead_surrogate.is_some() {
self.bytes.reserve(value.len() + 3);
self.flush_pending();
@@ -105,8 +111,10 @@ impl<'a> JSStrBuilder<'a> {
/// Append one UTF-16 code unit.
///
/// Use this for input from a UTF-16 API; use [`push_js_char`](Self::push_js_char)
/// for an already decoded JavaScript code point.
/// Use this for input from a UTF-16 API.
/// Use [`push_js_char`] for an already decoded JavaScript code point.
///
/// [`push_js_char`]: Self::push_js_char
#[inline]
pub fn push_code_unit(&mut self, unit: u16) {
self.push_js_char(JSChar::from_code_unit(unit));
@@ -114,8 +122,8 @@ impl<'a> JSStrBuilder<'a> {
/// Append potentially ill-formed UTF-16.
///
/// This accepts code-unit buffers from UTF-16 APIs without replacing lone
/// surrogates. A pair may span consecutive calls, including empty buffers.
/// This accepts code-unit buffers from UTF-16 APIs without replacing lone surrogates.
/// A pair may span consecutive calls, including empty buffers.
#[inline]
pub fn push_utf16(&mut self, units: &[u16]) {
for &unit in units {
@@ -185,8 +193,9 @@ impl<'a> JSStrBuilder<'a> {
self.flush_pending();
let bytes = self.bytes.into_arena_slice();
// SAFETY: Appends maintain canonical WTF-8 and the exact surrogate flag.
// The final pending surrogate has been flushed. `ArenaVec<u8>` bounds
// its length by u32::MAX and isize::MAX; the slice owns the arena lifetime.
// The final pending surrogate has been flushed.
// `ArenaVec<u8>` bounds its length by `u32::MAX` and `isize::MAX`.
// The slice owns the arena lifetime.
unsafe { JSStr::from_bytes_unchecked(bytes, self.has_lone_surrogate) }
}
@@ -204,7 +213,10 @@ impl<'a> JSStrBuilder<'a> {
}
}
/// `lead` and `trail` have already been checked at the append boundary.
/// Append a character formed from lead and trail surrogates.
///
/// # Panics
/// Panics if `lead` and `trail` do not form a valid `char`.
#[inline]
fn append_pair(&mut self, lead: u16, trail: u16) {
let value = 0x10000 + ((u32::from(lead) - 0xD800) << 10) + (u32::from(trail) - 0xDC00);