Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ exclude = [
[workspace.package]
edition = "2024"
version = "0.20.0"
rust-version = "1.88.0"
rust-version = "1.90.0"
authors = ["boa-dev"]
repository = "https://github.com/boa-dev/boa"
license = "Unlicense OR MIT"
Expand Down
39 changes: 20 additions & 19 deletions core/engine/src/builtins/intl/number_format/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ use fixed_decimal::{
UnsignedRoundingMode,
};

use boa_macros::js_str;
use icu_decimal::preferences::NumberingSystem;
use icu_locale::extensions::unicode::Value;
use tinystr::TinyAsciiStr;

use crate::{
Context, JsNativeError, JsObject, JsResult, JsStr, JsString, JsValue,
Context, JsNativeError, JsObject, JsResult, JsString, JsValue,
builtins::{
intl::options::{default_number_option, get_number_option},
options::{OptionType, ParsableOptionType, get_option},
},
js_string,
};
use boa_string::Latin1JsStringBuilder;
use icu_decimal::preferences::NumberingSystem;
use icu_locale::extensions::unicode::Value;
use tinystr::TinyAsciiStr;

impl OptionType for SignedRoundingMode {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {
Expand Down Expand Up @@ -285,9 +284,9 @@ impl ParsableOptionType for Currency {}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Unit {
// INVARIANT: `numerator` must only contain ASCII lowercase alphabetic letters or `-`.
numerator: JsStr<'static>,
numerator: &'static str,
// INVARIANT: if `denominator` is not empty, it must only contain ASCII lowercase alphabetic letters or `-`
denominator: JsStr<'static>,
denominator: &'static str,
}

impl Unit {
Expand All @@ -296,9 +295,15 @@ impl Unit {
if self.denominator.is_empty() {
js_string!(self.numerator)
} else {
// TODO: this is not optimal for now, but the new JS strings should
// allow us to optimize this to simple casts from ASCII to JsString.
js_string!(self.numerator, js_str!("-per-"), self.denominator)
let mut builder = Latin1JsStringBuilder::with_capacity(
self.numerator.len() + self.denominator.len() + 5,
);
builder.extend_from_slice(self.numerator.as_bytes());
builder.extend_from_slice(b"-per-");
builder.extend_from_slice(self.denominator.as_bytes());
builder
.build()
.expect("Builder failed, this should not happen")
}
}
}
Expand Down Expand Up @@ -377,17 +382,13 @@ impl std::str::FromStr for Unit {
.map(|i| SANCTIONED_UNITS[i])
.map_err(|_| ParseUnitError)?;

let num = JsStr::latin1(num.as_bytes());

let den = if den.is_empty() {
JsStr::EMPTY
let den: &'static str = if den.is_empty() {
""
} else {
let value = SANCTIONED_UNITS
SANCTIONED_UNITS
.binary_search(&den)
.map(|i| SANCTIONED_UNITS[i])
.map_err(|_| ParseUnitError)?;

JsStr::latin1(value.as_bytes())
.map_err(|_| ParseUnitError)?
};

Ok(Self {
Expand Down
9 changes: 6 additions & 3 deletions core/engine/src/builtins/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ impl String {
Ok(js_string!().into())
} else {
// 13. Return the substring of S from from to to.
Ok(js_string!(string.get_expect(from..to)).into())
Ok(unsafe { JsString::slice_unchecked(string.clone(), from, to).into() })
}
}

Expand Down Expand Up @@ -1906,7 +1906,8 @@ impl String {
let to = max(final_start, final_end);

// 10. Return the substring of S from from to to.
Ok(js_string!(string.get_expect(from..to)).into())
// Ok(js_string!(string.get_expect(from..to)).into())
Ok(unsafe { JsString::slice_unchecked(string.clone(), from, to).into() })
}

/// `String.prototype.split ( separator, limit )`
Expand Down Expand Up @@ -2002,7 +2003,9 @@ impl String {
while let Some(index) = j {
// a. Let T be the substring of S from i to j.
// b. Append T as the last element of substrings.
substrings.push(this_str.get_expect(i..index).into());
// SAFETY: we already checked that i and index are within range.
let sliced = unsafe { JsString::slice_unchecked(this_str.clone(), i, index) };
substrings.push(sliced.into());

// c. If the number of elements of substrings is lim, return ! CreateArrayFromList(substrings).
if substrings.len() == lim {
Expand Down
2 changes: 1 addition & 1 deletion core/engine/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ macro_rules! js_string {
($s:literal) => {{
const LITERAL: &$crate::string::JsStr<'static> = &$crate::js_str!($s);

$crate::string::JsString::from_static_js_str(LITERAL)
$crate::string::JsString::from_static(LITERAL)
}};
($s:expr) => {
$crate::string::JsString::from($s)
Expand Down
6 changes: 3 additions & 3 deletions core/engine/src/value/inner/nan_boxed.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! A NaN-boxed inner value for JavaScript values.
//!
//! This [`JsValue`] is a float using `NaN` values to represent inner
//! This [`JsValue`] is a float using `NaN` values to represent an inner
//! JavaScript value.
//!
//! # Assumptions
Expand Down Expand Up @@ -111,7 +111,7 @@ use crate::{
symbol::RawJsSymbol,
};
use boa_gc::{Finalize, GcBox, Trace, custom_trace};
use boa_string::{JsString, RawJsString};
use boa_string::JsString;
use core::fmt;
use static_assertions::const_assert;
use std::{
Expand Down Expand Up @@ -676,7 +676,7 @@ impl NanBoxedValue {
// SAFETY: the inner address must hold a valid, non-null JsString.
unsafe {
ManuallyDrop::new(JsString::from_raw(NonNull::new_unchecked(
self.ptr.with_addr(addr).cast::<RawJsString>(),
self.ptr.with_addr(addr).cast(),
)))
}
}
Expand Down
19 changes: 11 additions & 8 deletions core/string/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{DATA_OFFSET, JsStr, JsStrVariant, JsString, RawJsString, TaggedLen, alloc_overflow};
use crate::{
DATA_OFFSET, InnerStringKind, JsStr, JsStrVariant, JsString, SeqString, TaggedLen,
alloc_overflow,
};

use std::{
alloc::{Layout, alloc, dealloc, realloc},
Expand All @@ -14,7 +17,7 @@ use std::{
pub struct JsStringBuilder<D: Copy> {
cap: usize,
len: usize,
inner: NonNull<RawJsString>,
inner: NonNull<SeqString>,
phantom_data: PhantomData<D>,
}

Expand Down Expand Up @@ -170,7 +173,7 @@ impl<D: Copy> JsStringBuilder<D> {
// the length of the string and the reference count.
unsafe { alloc(new_layout) }
};
let Some(new_ptr) = NonNull::new(new_ptr.cast::<RawJsString>()) else {
let Some(new_ptr) = NonNull::new(new_ptr.cast::<SeqString>()) else {
std::alloc::handle_alloc_error(new_layout)
};
self.inner = new_ptr;
Expand Down Expand Up @@ -221,7 +224,7 @@ impl<D: Copy> JsStringBuilder<D> {

fn new_layout(cap: usize) -> Layout {
let new_layout = Layout::array::<D>(cap)
.and_then(|arr| Layout::new::<RawJsString>().extend(arr))
.and_then(|arr| Layout::new::<SeqString>().extend(arr))
.map(|(layout, offset)| (layout.pad_to_align(), offset))
.map_err(|_| None);
match new_layout {
Expand Down Expand Up @@ -276,7 +279,7 @@ impl<D: Copy> JsStringBuilder<D> {
}

/// Allocates memory to the inner `RawJsString` by the given capacity.
/// Capacity calculation is from [`std::vec::Vec::reserve`].
/// Capacity calculation is from [`Vec::reserve`].
fn allocate(&mut self, cap: usize) {
let cap = std::cmp::max(self.capacity() * 2, cap);
let cap = std::cmp::max(Self::MIN_NON_ZERO_CAP, cap);
Expand Down Expand Up @@ -367,18 +370,18 @@ impl<D: Copy> JsStringBuilder<D> {
// `NonNull` verified for us that the pointer returned by `alloc` is valid,
// meaning we can write to its pointed memory.
unsafe {
inner.as_ptr().write(RawJsString {
inner.as_ptr().write(SeqString {
tagged_len: TaggedLen::new(len, latin1),
refcount: Cell::new(1),
data: [0; 0],
});
}

// Tell the compiler not to call the destructor of `JsStringBuilder`,
// becuase we move inner `RawJsString` to `JsString`.
// because we move inner `RawJsString` to `JsString`.
std::mem::forget(self);

JsString { ptr: inner }
JsString::from_inner(inner, InnerStringKind::Sequence)
}
}

Expand Down
78 changes: 78 additions & 0 deletions core/string/src/code_point.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::fmt::Write;

/// Represents a Unicode codepoint within a [`JsString`], which could be a valid
/// '[Unicode scalar value]', or an unpaired surrogate.
///
/// [Unicode scalar value]: https://www.unicode.org/glossary/#unicode_scalar_value
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CodePoint {
/// A valid Unicode scalar value.
Unicode(char),

/// An unpaired surrogate.
UnpairedSurrogate(u16),
}

impl CodePoint {
/// Get the number of UTF-16 code units needed to encode this code point.
#[inline]
#[must_use]
pub const fn code_unit_count(self) -> usize {
match self {
Self::Unicode(c) => c.len_utf16(),
Self::UnpairedSurrogate(_) => 1,
}
}

/// Convert the code point to its [`u32`] representation.
#[inline]
#[must_use]
pub fn as_u32(self) -> u32 {
match self {
Self::Unicode(c) => u32::from(c),
Self::UnpairedSurrogate(surr) => u32::from(surr),
}
}

/// If the code point represents a valid 'Unicode scalar value', returns its [`char`]
/// representation, otherwise returns [`None`] on unpaired surrogates.
#[inline]
#[must_use]
pub const fn as_char(self) -> Option<char> {
match self {
Self::Unicode(c) => Some(c),
Self::UnpairedSurrogate(_) => None,
}
}

/// Encodes this code point as UTF-16 into the provided u16 buffer, and then returns the subslice
/// of the buffer that contains the encoded character.
///
/// # Panics
///
/// Panics if the buffer is not large enough. A buffer of length 2 is large enough to encode any
/// code point.
#[inline]
#[must_use]
pub fn encode_utf16(self, dst: &mut [u16]) -> &mut [u16] {
match self {
Self::Unicode(c) => c.encode_utf16(dst),
Self::UnpairedSurrogate(surr) => {
dst[0] = surr;
&mut dst[0..=0]
}
}
}
}

impl std::fmt::Display for CodePoint {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CodePoint::Unicode(c) => f.write_char(*c),
CodePoint::UnpairedSurrogate(c) => {
write!(f, "\\u{c:04X}")
}
}
}
}
4 changes: 2 additions & 2 deletions core/string/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ macro_rules! well_known_statics {
paste!{
#[doc = "Gets the static `JsString` for `\"" $string "\"`."]
pub const $name: JsString = const {
JsString::from_static_js_str(Self::find_static_js_string($string))
JsString::from_static(Self::find_static_js_string($string))
};
}
)+
Expand Down Expand Up @@ -73,7 +73,7 @@ impl StaticJsStrings {
// SAFETY: Type of T in is `&'static JsStr<'static>`, so this is safe.
let ptr = unsafe { std::mem::transmute::<&JsStr<'_>, &'static JsStr<'static>>(str) };

Some(JsString::from_static_js_str(ptr))
Some(JsString::from_static(ptr))
}

// Some consts are only used on certain features, which triggers the unused lint.
Expand Down
Loading
Loading