use crate::iter::CodePointsIter;
use crate::r#type::InternalStringType;
use crate::vtable::JsStringVTable;
use crate::{JsStr, JsString, alloc_overflow};
use std::alloc::{Layout, alloc, dealloc};
use std::cell::Cell;
use std::marker::PhantomData;
use std::process::abort;
use std::ptr;
use std::ptr::NonNull;
#[repr(C)]
pub(crate) struct SequenceString<T: InternalStringType> {
vtable: JsStringVTable,
refcount: Cell<usize>,
_marker: PhantomData<fn() -> T>,
pub(crate) data: [u8; 0],
}
impl<T: InternalStringType> SequenceString<T> {
#[inline]
#[must_use]
pub(crate) fn new(len: usize) -> Self {
SequenceString {
vtable: JsStringVTable {
clone: seq_clone::<T>,
drop: seq_drop::<T>,
as_str: seq_as_str::<T>,
code_points: seq_code_points::<T>,
refcount: seq_refcount::<T>,
len,
kind: T::KIND,
},
refcount: Cell::new(1),
_marker: PhantomData,
data: [0; 0],
}
}
pub(crate) fn allocate(len: usize) -> NonNull<SequenceString<T>> {
match Self::try_allocate(len) {
Ok(v) => v,
Err(None) => alloc_overflow(),
Err(Some(layout)) => std::alloc::handle_alloc_error(layout),
}
}
pub(crate) fn try_allocate(len: usize) -> Result<NonNull<Self>, Option<Layout>> {
let (layout, offset) = Layout::array::<T::Byte>(len)
.and_then(|arr| T::base_layout().extend(arr))
.map(|(layout, offset)| (layout.pad_to_align(), offset))
.map_err(|_| None)?;
debug_assert_eq!(offset, T::DATA_OFFSET);
debug_assert_eq!(layout.align(), align_of::<Self>());
#[allow(clippy::cast_ptr_alignment)]
let inner = unsafe { alloc(layout).cast::<Self>() };
let inner = NonNull::new(inner).ok_or(Some(layout))?;
unsafe {
inner.as_ptr().write(Self::new(len));
}
debug_assert!({
let inner = inner.as_ptr();
unsafe {
ptr::eq(
inner.cast::<u8>().add(offset).cast(),
(*inner).data().cast_mut(),
)
}
});
Ok(inner)
}
#[inline]
#[must_use]
pub(crate) const fn data(&self) -> *const u8 {
self.data.as_ptr()
}
}
#[inline]
fn seq_clone<T: InternalStringType>(vtable: NonNull<JsStringVTable>) -> JsString {
let this: &SequenceString<T> = unsafe { vtable.cast().as_ref() };
let Some(strong) = this.refcount.get().checked_add(1) else {
abort();
};
this.refcount.set(strong);
unsafe { JsString::from_ptr(vtable) }
}
#[inline]
fn seq_drop<T: InternalStringType>(vtable: NonNull<JsStringVTable>) {
let this: &SequenceString<T> = unsafe { vtable.cast().as_ref() };
let Some(new) = this.refcount.get().checked_sub(1) else {
abort();
};
this.refcount.set(new);
if new != 0 {
return;
}
let layout = unsafe {
Layout::for_value(this)
.extend(Layout::array::<T::Byte>(this.vtable.len).unwrap_unchecked())
.unwrap_unchecked()
.0
.pad_to_align()
};
unsafe {
dealloc(vtable.as_ptr().cast(), layout);
}
}
#[inline]
fn seq_as_str<T: InternalStringType>(vtable: NonNull<JsStringVTable>) -> JsStr<'static> {
let this: &SequenceString<T> = unsafe { vtable.cast().as_ref() };
let len = this.vtable.len;
let data_ptr = (&raw const this.data).cast::<T::Byte>();
let slice = unsafe { std::slice::from_raw_parts(data_ptr, len) };
T::str_ctor(slice)
}
#[inline]
fn seq_code_points<T: InternalStringType>(
vtable: NonNull<JsStringVTable>,
) -> CodePointsIter<'static> {
CodePointsIter::new(seq_as_str::<T>(vtable))
}
#[inline]
#[allow(clippy::unnecessary_wraps)]
fn seq_refcount<T: InternalStringType>(vtable: NonNull<JsStringVTable>) -> Option<usize> {
let this: &SequenceString<T> = unsafe { vtable.cast().as_ref() };
Some(this.refcount.get())
}