use crate::iter::CodePointsIter;
use crate::r#type::{InternalStringType, Latin1, Utf16};
use crate::vtable::JsStringVTable;
use crate::{JsStr, JsStrVariant, JsString, JsStringKind};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::mem::{ManuallyDrop, offset_of};
use std::ptr::NonNull;
use std::sync::Arc;
trait SharedType: InternalStringType {
const SHARED_KIND: JsStringKind;
}
impl SharedType for Latin1 {
const SHARED_KIND: JsStringKind = JsStringKind::Latin1Shared;
}
impl SharedType for Utf16 {
const SHARED_KIND: JsStringKind = JsStringKind::Utf16Shared;
}
#[repr(C)]
struct SharedString<T: SharedType> {
vtable: JsStringVTable,
data: Box<[T::Byte]>,
_marker: PhantomData<fn() -> T>,
}
static_assertions::const_assert!(offset_of!(SharedString<Latin1>, vtable) == 0);
static_assertions::const_assert!(offset_of!(SharedString<Utf16>, vtable) == 0);
#[derive(Clone)]
enum SharedInner {
Latin1(Arc<SharedString<Latin1>>),
Utf16(Arc<SharedString<Utf16>>),
}
#[derive(Clone)]
pub struct SharedJsStr {
inner: SharedInner,
}
const _: fn() = || {
fn assert<T: Send + Sync>() {}
assert::<SharedJsStr>();
};
impl SharedJsStr {
#[inline]
#[must_use]
pub fn as_str(&self) -> JsStr<'_> {
match &self.inner {
SharedInner::Latin1(arc) => JsStr::latin1(&arc.data),
SharedInner::Utf16(arc) => JsStr::utf16(&arc.data),
}
}
#[inline]
fn from_latin1(bytes: &[u8]) -> Self {
Self {
inner: SharedInner::Latin1(SharedString::<Latin1>::from_units(bytes)),
}
}
#[inline]
fn from_utf16(units: &[u16]) -> Self {
Self {
inner: SharedInner::Utf16(SharedString::<Utf16>::from_units(units)),
}
}
#[inline]
#[must_use]
pub(crate) fn into_js_string(self) -> JsString {
let ptr = match self.inner {
SharedInner::Latin1(arc) => vtable_ptr(arc),
SharedInner::Utf16(arc) => vtable_ptr(arc),
};
unsafe { JsString::from_ptr(ptr) }
}
}
impl<T: SharedType> SharedString<T> {
fn from_units(units: &[T::Byte]) -> Arc<Self> {
let data: Box<[T::Byte]> = Box::from(units);
let len = data.len();
Arc::new(Self {
vtable: JsStringVTable {
clone: shared_clone::<T>,
drop: shared_drop::<T>,
as_str: shared_as_str::<T>,
code_points: shared_code_points::<T>,
refcount: shared_refcount::<T>,
len,
kind: T::SHARED_KIND,
},
data,
_marker: PhantomData,
})
}
}
#[inline]
fn shared_clone<T: SharedType>(vtable: NonNull<JsStringVTable>) -> JsString {
unsafe {
Arc::increment_strong_count(vtable.cast::<SharedString<T>>().as_ptr());
JsString::from_ptr(vtable)
}
}
#[inline]
fn shared_drop<T: SharedType>(vtable: NonNull<JsStringVTable>) {
unsafe {
drop(Arc::from_raw(vtable.cast::<SharedString<T>>().as_ptr()));
}
}
#[inline]
fn shared_as_str<T: SharedType>(vtable: NonNull<JsStringVTable>) -> JsStr<'static> {
let this: &SharedString<T> = unsafe { vtable.cast().as_ref() };
debug_assert_eq!(this.vtable.len, this.data.len());
let ptr = this.data.as_ptr();
let len = this.data.len();
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
T::str_ctor(slice)
}
#[inline]
fn shared_code_points<T: SharedType>(vtable: NonNull<JsStringVTable>) -> CodePointsIter<'static> {
CodePointsIter::new(shared_as_str::<T>(vtable))
}
#[inline]
#[allow(clippy::unnecessary_wraps)]
fn shared_refcount<T: SharedType>(vtable: NonNull<JsStringVTable>) -> Option<usize> {
let arc =
unsafe { ManuallyDrop::new(Arc::from_raw(vtable.cast::<SharedString<T>>().as_ptr())) };
Some(Arc::strong_count(&arc))
}
#[inline]
fn clone_arc<T: SharedType>(string: &JsString) -> Arc<SharedString<T>> {
debug_assert_eq!(string.kind(), T::SHARED_KIND);
let ptr = string.ptr.cast::<SharedString<T>>().as_ptr();
unsafe {
Arc::increment_strong_count(ptr);
Arc::from_raw(ptr)
}
}
#[inline]
fn vtable_ptr<T: SharedType>(arc: Arc<SharedString<T>>) -> NonNull<JsStringVTable> {
let raw = Arc::into_raw(arc);
unsafe { NonNull::new_unchecked(raw.cast_mut()).cast() }
}
impl From<&JsString> for SharedJsStr {
#[inline]
fn from(value: &JsString) -> Self {
match value.kind() {
JsStringKind::Latin1Shared => Self {
inner: SharedInner::Latin1(clone_arc::<Latin1>(value)),
},
JsStringKind::Utf16Shared => Self {
inner: SharedInner::Utf16(clone_arc::<Utf16>(value)),
},
_ => match value.as_str().variant() {
JsStrVariant::Latin1(bytes) => Self::from_latin1(bytes),
JsStrVariant::Utf16(units) => Self::from_utf16(units),
},
}
}
}
impl From<&str> for SharedJsStr {
#[inline]
fn from(s: &str) -> Self {
if s.is_ascii() {
return Self::from_latin1(s.as_bytes());
}
if s.chars().all(|c| c as u32 <= 0xFF) {
let bytes: Vec<u8> = s.chars().map(|c| c as u8).collect();
return Self::from_latin1(&bytes);
}
let units = s.encode_utf16().collect::<Vec<_>>();
Self::from_utf16(&units)
}
}
impl From<&[u16]> for SharedJsStr {
#[inline]
fn from(units: &[u16]) -> Self {
Self::from_utf16(units)
}
}
impl<const N: usize> From<&[u16; N]> for SharedJsStr {
#[inline]
fn from(units: &[u16; N]) -> Self {
Self::from(&units[..])
}
}
impl PartialEq for SharedJsStr {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for SharedJsStr {}
impl PartialOrd for SharedJsStr {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SharedJsStr {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(&other.as_str())
}
}
impl Hash for SharedJsStr {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialEq<JsString> for SharedJsStr {
#[inline]
fn eq(&self, other: &JsString) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<SharedJsStr> for JsString {
#[inline]
fn eq(&self, other: &SharedJsStr) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<str> for SharedJsStr {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for SharedJsStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<SharedJsStr> for str {
#[inline]
fn eq(&self, other: &SharedJsStr) -> bool {
other == self
}
}
impl std::fmt::Debug for SharedJsStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let units = self.as_str();
let content = units.display_lossy().to_string();
f.debug_struct("SharedJsStr")
.field("len", &units.len())
.field("latin1", &units.is_latin1())
.field("content", &content)
.finish_non_exhaustive()
}
}