1#![deny(
9 unsafe_op_in_unsafe_fn,
10 clippy::undocumented_unsafe_blocks,
11 clippy::missing_safety_doc
12)]
13#![allow(clippy::module_name_repetitions)]
14
15mod builder;
16mod code_point;
17mod common;
18mod display;
19mod iter;
20mod str;
21mod r#type;
22mod vtable;
23
24#[cfg(test)]
25mod tests;
26
27use self::iter::Windows;
28use crate::display::{JsStrDisplayEscaped, JsStrDisplayLossy, JsStringDebugInfo};
29use crate::iter::CodePointsIter;
30use crate::r#type::{Latin1, Utf16};
31use crate::vtable::{SequenceString, SliceString};
32pub use crate::vtable::{SharedJsStr, StaticString};
33#[doc(inline)]
34pub use crate::{
35 builder::{CommonJsStringBuilder, Latin1JsStringBuilder, Utf16JsStringBuilder},
36 code_point::CodePoint,
37 common::StaticJsStrings,
38 iter::Iter,
39 str::{JsStr, JsStrVariant},
40};
41use std::marker::PhantomData;
42use std::{borrow::Cow, mem::ManuallyDrop};
43use std::{
44 convert::Infallible,
45 hash::{Hash, Hasher},
46 ptr::{self, NonNull},
47 str::FromStr,
48};
49use vtable::JsStringVTable;
50
51fn alloc_overflow() -> ! {
52 panic!("detected overflow during string allocation")
53}
54
55pub(crate) const fn is_trimmable_whitespace(c: char) -> bool {
57 matches!(
66 c,
67 '\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' |
68 '\u{1680}' | '\u{2000}'
70 ..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' |
71 '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}'
73 )
74}
75
76pub(crate) const fn is_trimmable_whitespace_latin1(c: u8) -> bool {
78 matches!(
87 c,
88 0x09 | 0x0B | 0x0C | 0x20 | 0xA0 |
89 0x0A | 0x0D
91 )
92}
93
94#[allow(missing_copy_implementations, missing_debug_implementations)]
96pub struct RawJsString {
97 phantom_data: PhantomData<*mut ()>,
99}
100
101#[derive(Debug, Clone, Copy, Eq, PartialEq)]
104#[repr(u8)]
105pub(crate) enum JsStringKind {
106 Latin1Sequence = 0,
108
109 Utf16Sequence = 1,
111
112 Slice = 2,
114
115 Static = 3,
117
118 Latin1Shared = 4,
120
121 Utf16Shared = 5,
123}
124
125#[allow(clippy::module_name_repetitions)]
144pub struct JsString {
145 ptr: NonNull<JsStringVTable>,
148}
149
150static_assertions::assert_eq_size!(JsString, *const ());
152
153impl<'a> From<&'a JsString> for JsStr<'a> {
154 #[inline]
155 fn from(value: &'a JsString) -> Self {
156 value.as_str()
157 }
158}
159
160impl<'a> IntoIterator for &'a JsString {
161 type Item = u16;
162 type IntoIter = Iter<'a>;
163
164 #[inline]
165 fn into_iter(self) -> Self::IntoIter {
166 self.iter()
167 }
168}
169
170impl JsString {
171 #[inline]
173 #[must_use]
174 pub fn iter(&self) -> Iter<'_> {
175 self.as_str().iter()
176 }
177
178 #[inline]
180 #[must_use]
181 pub fn windows(&self, size: usize) -> Windows<'_> {
182 self.as_str().windows(size)
183 }
184
185 #[inline]
188 #[must_use]
189 pub fn to_std_string_escaped(&self) -> String {
190 self.display_escaped().to_string()
191 }
192
193 #[inline]
196 #[must_use]
197 pub fn to_std_string_lossy(&self) -> String {
198 self.display_lossy().to_string()
199 }
200
201 #[inline]
208 pub fn to_std_string(&self) -> Result<String, std::string::FromUtf16Error> {
209 self.as_str().to_std_string()
210 }
211
212 #[inline]
215 #[allow(clippy::missing_panics_doc)]
216 pub fn to_std_string_with_surrogates(
217 &self,
218 ) -> impl Iterator<Item = Result<String, u16>> + use<'_> {
219 let mut iter = self.code_points().peekable();
220
221 std::iter::from_fn(move || {
222 let cp = iter.next()?;
223 let char = match cp {
224 CodePoint::Unicode(c) => c,
225 CodePoint::UnpairedSurrogate(surr) => return Some(Err(surr)),
226 };
227
228 let mut string = String::from(char);
229
230 while let Some(cp) = iter.peek().and_then(|cp| match cp {
231 CodePoint::Unicode(c) => Some(*c),
232 CodePoint::UnpairedSurrogate(_) => None,
233 }) {
234 string.push(cp);
235 iter.next().expect("iter.peek() ensures that next is Some");
236 }
237
238 Some(Ok(string))
239 })
240 }
241
242 #[inline]
244 #[must_use]
245 pub fn map_valid_segments<F>(&self, mut f: F) -> Self
246 where
247 F: FnMut(String) -> String,
248 {
249 let mut text = Vec::new();
250
251 for part in self.to_std_string_with_surrogates() {
252 match part {
253 Ok(string) => text.extend(f(string).encode_utf16()),
254 Err(surr) => text.push(surr),
255 }
256 }
257
258 Self::from(&text[..])
259 }
260
261 #[inline]
263 #[must_use]
264 pub fn code_points(&self) -> CodePointsIter<'_> {
265 (self.vtable().code_points)(self.ptr)
266 }
267
268 #[inline]
270 #[must_use]
271 pub fn variant(&self) -> JsStrVariant<'_> {
272 self.as_str().variant()
273 }
274
275 #[inline]
285 #[must_use]
286 pub fn index_of(&self, search_value: JsStr<'_>, from_index: usize) -> Option<usize> {
287 self.as_str().index_of(search_value, from_index)
288 }
289
290 #[inline]
307 #[must_use]
308 pub fn code_point_at(&self, position: usize) -> CodePoint {
309 self.as_str().code_point_at(position)
310 }
311
312 #[inline]
319 #[must_use]
320 pub fn to_number(&self) -> f64 {
321 self.as_str().to_number()
322 }
323
324 #[inline]
326 #[must_use]
327 pub fn len(&self) -> usize {
328 self.vtable().len
329 }
330
331 #[inline]
333 #[must_use]
334 pub fn is_empty(&self) -> bool {
335 self.len() == 0
336 }
337
338 #[inline]
340 #[must_use]
341 pub fn to_vec(&self) -> Vec<u16> {
342 self.as_str().to_vec()
343 }
344
345 #[inline]
347 #[must_use]
348 pub fn contains(&self, element: u8) -> bool {
349 self.as_str().contains(element)
350 }
351
352 #[inline]
354 #[must_use]
355 pub fn trim(&self) -> JsString {
356 let (start, end) = match self.variant() {
358 JsStrVariant::Latin1(v) => {
359 let Some(start) = v.iter().position(|c| !is_trimmable_whitespace_latin1(*c)) else {
360 return StaticJsStrings::EMPTY_STRING;
361 };
362 let end = v
363 .iter()
364 .rposition(|c| !is_trimmable_whitespace_latin1(*c))
365 .unwrap_or(start);
366 (start, end)
367 }
368 JsStrVariant::Utf16(v) => {
369 let Some(start) = v.iter().copied().position(|r| {
370 !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)
371 }) else {
372 return StaticJsStrings::EMPTY_STRING;
373 };
374 let end = v
375 .iter()
376 .copied()
377 .rposition(|r| {
378 !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)
379 })
380 .unwrap_or(start);
381 (start, end)
382 }
383 };
384
385 unsafe { Self::slice_unchecked(self, start, end + 1) }
387 }
388
389 #[inline]
391 #[must_use]
392 pub fn trim_start(&self) -> JsString {
393 let Some(start) = (match self.variant() {
394 JsStrVariant::Latin1(v) => v.iter().position(|c| !is_trimmable_whitespace_latin1(*c)),
395 JsStrVariant::Utf16(v) => v
396 .iter()
397 .copied()
398 .position(|r| !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)),
399 }) else {
400 return StaticJsStrings::EMPTY_STRING;
401 };
402
403 unsafe { Self::slice_unchecked(self, start, self.len()) }
405 }
406
407 #[inline]
409 #[must_use]
410 pub fn trim_end(&self) -> JsString {
411 let Some(end) = (match self.variant() {
412 JsStrVariant::Latin1(v) => v.iter().rposition(|c| !is_trimmable_whitespace_latin1(*c)),
413 JsStrVariant::Utf16(v) => v
414 .iter()
415 .copied()
416 .rposition(|r| !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)),
417 }) else {
418 return StaticJsStrings::EMPTY_STRING;
419 };
420
421 unsafe { Self::slice_unchecked(self, 0, end + 1) }
424 }
425
426 #[inline]
428 #[must_use]
429 #[allow(clippy::missing_panics_doc)]
431 pub fn starts_with(&self, needle: JsStr<'_>) -> bool {
432 self.as_str().starts_with(needle)
433 }
434
435 #[inline]
437 #[must_use]
438 #[allow(clippy::missing_panics_doc)]
440 pub fn ends_with(&self, needle: JsStr<'_>) -> bool {
441 self.as_str().ends_with(needle)
442 }
443
444 #[inline]
447 #[must_use]
448 pub fn code_unit_at(&self, index: usize) -> Option<u16> {
449 self.as_str().get(index)
450 }
451
452 #[inline]
454 #[must_use]
455 pub fn get<I>(&self, index: I) -> Option<JsString>
456 where
457 I: JsStringSliceIndex,
458 {
459 index.get(self)
460 }
461
462 #[inline]
467 #[must_use]
468 pub fn get_expect<I>(&self, index: I) -> JsString
469 where
470 I: JsStringSliceIndex,
471 {
472 index.get(self).expect("Unexpected get()")
473 }
474
475 #[inline]
479 #[must_use]
480 pub fn display_escaped(&self) -> JsStrDisplayEscaped<'_> {
481 JsStrDisplayEscaped::from(self)
482 }
483
484 #[inline]
487 #[must_use]
488 pub fn display_lossy(&self) -> JsStrDisplayLossy<'_> {
489 self.as_str().display_lossy()
490 }
491
492 #[inline]
494 #[must_use]
495 pub fn debug_info(&self) -> JsStringDebugInfo<'_> {
496 self.into()
497 }
498
499 #[inline]
504 #[must_use]
505 pub fn into_raw(self) -> NonNull<RawJsString> {
506 ManuallyDrop::new(self).ptr.cast()
507 }
508
509 #[inline]
519 #[must_use]
520 pub const unsafe fn from_raw(ptr: NonNull<RawJsString>) -> Self {
521 Self { ptr: ptr.cast() }
522 }
523
524 #[inline]
531 #[must_use]
532 pub(crate) const unsafe fn from_ptr(ptr: NonNull<JsStringVTable>) -> Self {
533 Self { ptr }
534 }
535}
536
537static_assertions::const_assert!(align_of::<*const JsStr<'static>>() >= 2);
539
540impl JsString {
542 #[inline]
544 #[must_use]
545 pub fn is_static(&self) -> bool {
546 self.vtable().kind == JsStringKind::Static
548 }
549
550 #[inline]
552 #[must_use]
553 const fn vtable(&self) -> &JsStringVTable {
554 unsafe { self.ptr.as_ref() }
556 }
557
558 #[inline]
562 #[must_use]
563 pub const fn from_static(str: &'static StaticString) -> Self {
564 Self {
565 ptr: NonNull::from_ref(str).cast(),
566 }
567 }
568
569 #[inline]
580 #[must_use]
581 pub fn from_shared(shared: SharedJsStr) -> Self {
582 shared.into_js_string()
583 }
584
585 #[inline]
594 #[must_use]
595 pub unsafe fn slice_unchecked(data: &JsString, start: usize, end: usize) -> Self {
596 let slice = Box::new(unsafe { SliceString::new(data, start, end) });
598
599 Self {
600 ptr: NonNull::from(Box::leak(slice)).cast(),
601 }
602 }
603
604 #[inline]
607 #[must_use]
608 pub fn slice(&self, p1: usize, mut p2: usize) -> JsString {
609 if p2 > self.len() {
610 p2 = self.len();
611 }
612 if p1 >= p2 {
613 StaticJsStrings::EMPTY_STRING
614 } else {
615 unsafe { Self::slice_unchecked(self, p1, p2) }
617 }
618 }
619
620 #[inline]
622 #[must_use]
623 pub(crate) fn kind(&self) -> JsStringKind {
624 self.vtable().kind
625 }
626
627 #[inline]
633 pub(crate) unsafe fn as_inner<T>(&self) -> &T {
634 unsafe { self.ptr.cast::<T>().as_ref() }
636 }
637}
638
639impl JsString {
640 #[inline]
642 #[must_use]
643 pub fn as_str(&self) -> JsStr<'_> {
644 (self.vtable().as_str)(self.ptr)
645 }
646
647 #[inline]
649 #[must_use]
650 pub fn concat(x: JsStr<'_>, y: JsStr<'_>) -> Self {
651 Self::concat_array(&[x, y])
652 }
653
654 #[inline]
657 #[must_use]
658 pub fn concat_array(strings: &[JsStr<'_>]) -> Self {
659 let mut latin1_encoding = true;
660 let mut full_count = 0usize;
661 for string in strings {
662 let Some(sum) = full_count.checked_add(string.len()) else {
663 alloc_overflow()
664 };
665 if !string.is_latin1() {
666 latin1_encoding = false;
667 }
668 full_count = sum;
669 }
670
671 let (ptr, data_offset) = if latin1_encoding {
672 let p = SequenceString::<Latin1>::allocate(full_count);
673 (p.cast::<u8>(), size_of::<SequenceString<Latin1>>())
674 } else {
675 let p = SequenceString::<Utf16>::allocate(full_count);
676 (p.cast::<u8>(), size_of::<SequenceString<Utf16>>())
677 };
678
679 let string = {
680 let mut data = unsafe {
682 let seq_ptr = ptr.as_ptr();
683 seq_ptr.add(data_offset)
684 };
685 for &string in strings {
686 unsafe {
697 #[allow(clippy::cast_ptr_alignment)]
699 match (latin1_encoding, string.variant()) {
700 (true, JsStrVariant::Latin1(s)) => {
701 let count = s.len();
702 ptr::copy_nonoverlapping(s.as_ptr(), data.cast::<u8>(), count);
703 data = data.cast::<u8>().add(count).cast::<u8>();
704 }
705 (false, JsStrVariant::Latin1(s)) => {
706 let count = s.len();
707 for (i, byte) in s.iter().enumerate() {
708 *data.cast::<u16>().add(i) = u16::from(*byte);
709 }
710 data = data.cast::<u16>().add(count).cast::<u8>();
711 }
712 (false, JsStrVariant::Utf16(s)) => {
713 let count = s.len();
714 ptr::copy_nonoverlapping(s.as_ptr(), data.cast::<u16>(), count);
715 data = data.cast::<u16>().add(count).cast::<u8>();
716 }
717 (true, JsStrVariant::Utf16(_)) => {
718 unreachable!("Already checked that it's latin1 encoding")
719 }
720 }
721 }
722 }
723
724 Self { ptr: ptr.cast() }
725 };
726
727 StaticJsStrings::get_string(&string.as_str()).unwrap_or(string)
728 }
729
730 fn from_slice_skip_interning(string: JsStr<'_>) -> Self {
732 let count = string.len();
733
734 unsafe {
743 #[allow(clippy::cast_ptr_alignment)]
745 match string.variant() {
746 JsStrVariant::Latin1(s) => {
747 let ptr = SequenceString::<Latin1>::allocate(count);
748 let data = (&raw mut (*ptr.as_ptr()).data)
749 .cast::<<Latin1 as r#type::StringType>::Byte>();
750 ptr::copy_nonoverlapping(s.as_ptr(), data, count);
751 Self { ptr: ptr.cast() }
752 }
753 JsStrVariant::Utf16(s) => {
754 let ptr = SequenceString::<Utf16>::allocate(count);
755 let data = (&raw mut (*ptr.as_ptr()).data)
756 .cast::<<Utf16 as r#type::StringType>::Byte>();
757 ptr::copy_nonoverlapping(s.as_ptr(), data, count);
758 Self { ptr: ptr.cast() }
759 }
760 }
761 }
762 }
763
764 fn from_js_str(string: JsStr<'_>) -> Self {
766 if let Some(s) = StaticJsStrings::get_string(&string) {
767 return s;
768 }
769 Self::from_slice_skip_interning(string)
770 }
771
772 #[inline]
779 #[must_use]
780 pub fn refcount(&self) -> Option<usize> {
781 (self.vtable().refcount)(self.ptr)
782 }
783}
784
785impl Clone for JsString {
786 #[inline]
787 fn clone(&self) -> Self {
788 (self.vtable().clone)(self.ptr)
789 }
790}
791
792impl Default for JsString {
793 #[inline]
794 fn default() -> Self {
795 StaticJsStrings::EMPTY_STRING
796 }
797}
798
799impl Drop for JsString {
800 #[inline]
801 fn drop(&mut self) {
802 (self.vtable().drop)(self.ptr);
803 }
804}
805
806impl std::fmt::Debug for JsString {
807 #[inline]
808 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
809 f.debug_tuple("JsString")
810 .field(&self.display_escaped().to_string())
811 .finish()
812 }
813}
814
815impl Eq for JsString {}
816
817macro_rules! impl_from_number_for_js_string {
818 ($($module: ident => $($ty:ty),+)+) => {
819 $(
820 $(
821 impl From<$ty> for JsString {
822 #[inline]
823 fn from(value: $ty) -> Self {
824 JsString::from_slice_skip_interning(JsStr::latin1(
825 $module::Buffer::new().format(value).as_bytes(),
826 ))
827 }
828 }
829 )+
830 )+
831 };
832}
833
834impl_from_number_for_js_string!(
835 itoa => i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, isize, usize
836 ryu_js => f32, f64
837);
838
839impl From<&[u16]> for JsString {
840 #[inline]
841 fn from(s: &[u16]) -> Self {
842 JsString::from_js_str(JsStr::utf16(s))
843 }
844}
845
846impl From<&str> for JsString {
847 #[inline]
848 fn from(s: &str) -> Self {
849 if s.is_ascii() {
850 let js_str = JsStr::latin1(s.as_bytes());
851 return StaticJsStrings::get_string(&js_str)
852 .unwrap_or_else(|| JsString::from_slice_skip_interning(js_str));
853 }
854 if s.chars().all(|c| c as u32 <= 0xFF) {
856 let bytes: Vec<u8> = s.chars().map(|c| c as u8).collect();
857 let js_str = JsStr::latin1(&bytes);
858 return StaticJsStrings::get_string(&js_str)
859 .unwrap_or_else(|| JsString::from_slice_skip_interning(js_str));
860 }
861 let s = s.encode_utf16().collect::<Vec<_>>();
862 JsString::from_slice_skip_interning(JsStr::utf16(&s[..]))
863 }
864}
865
866impl From<JsStr<'_>> for JsString {
867 #[inline]
868 fn from(value: JsStr<'_>) -> Self {
869 StaticJsStrings::get_string(&value)
870 .unwrap_or_else(|| JsString::from_slice_skip_interning(value))
871 }
872}
873
874impl From<SharedJsStr> for JsString {
875 #[inline]
876 fn from(shared: SharedJsStr) -> Self {
877 Self::from_shared(shared)
878 }
879}
880
881impl From<&[JsString]> for JsString {
882 #[inline]
883 fn from(value: &[JsString]) -> Self {
884 Self::concat_array(&value.iter().map(Self::as_str).collect::<Vec<_>>()[..])
885 }
886}
887
888impl<const N: usize> From<&[JsString; N]> for JsString {
889 #[inline]
890 fn from(value: &[JsString; N]) -> Self {
891 Self::concat_array(&value.iter().map(Self::as_str).collect::<Vec<_>>()[..])
892 }
893}
894
895impl From<String> for JsString {
896 #[inline]
897 fn from(s: String) -> Self {
898 Self::from(s.as_str())
899 }
900}
901
902impl<'a> From<Cow<'a, str>> for JsString {
903 #[inline]
904 fn from(s: Cow<'a, str>) -> Self {
905 match s {
906 Cow::Borrowed(s) => s.into(),
907 Cow::Owned(s) => s.into(),
908 }
909 }
910}
911
912impl<const N: usize> From<&[u16; N]> for JsString {
913 #[inline]
914 fn from(s: &[u16; N]) -> Self {
915 Self::from(&s[..])
916 }
917}
918
919impl Hash for JsString {
920 #[inline]
921 fn hash<H: Hasher>(&self, state: &mut H) {
922 self.as_str().hash(state);
923 }
924}
925
926impl PartialOrd for JsStr<'_> {
927 #[inline]
928 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
929 Some(self.cmp(other))
930 }
931}
932
933impl Ord for JsString {
934 #[inline]
935 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
936 self.as_str().cmp(&other.as_str())
937 }
938}
939
940impl PartialEq for JsString {
941 #[inline]
942 fn eq(&self, other: &Self) -> bool {
943 self.as_str() == other.as_str()
944 }
945}
946
947impl PartialEq<JsString> for [u16] {
948 #[inline]
949 fn eq(&self, other: &JsString) -> bool {
950 if self.len() != other.len() {
951 return false;
952 }
953 for (x, y) in self.iter().copied().zip(other.iter()) {
954 if x != y {
955 return false;
956 }
957 }
958 true
959 }
960}
961
962impl<const N: usize> PartialEq<JsString> for [u16; N] {
963 #[inline]
964 fn eq(&self, other: &JsString) -> bool {
965 self[..] == *other
966 }
967}
968
969impl PartialEq<[u16]> for JsString {
970 #[inline]
971 fn eq(&self, other: &[u16]) -> bool {
972 other == self
973 }
974}
975
976impl<const N: usize> PartialEq<[u16; N]> for JsString {
977 #[inline]
978 fn eq(&self, other: &[u16; N]) -> bool {
979 *self == other[..]
980 }
981}
982
983impl PartialEq<str> for JsString {
984 #[inline]
985 fn eq(&self, other: &str) -> bool {
986 self.as_str() == other
987 }
988}
989
990impl PartialEq<&str> for JsString {
991 #[inline]
992 fn eq(&self, other: &&str) -> bool {
993 self.as_str() == *other
994 }
995}
996
997impl PartialEq<JsString> for str {
998 #[inline]
999 fn eq(&self, other: &JsString) -> bool {
1000 other == self
1001 }
1002}
1003
1004impl PartialEq<JsStr<'_>> for JsString {
1005 #[inline]
1006 fn eq(&self, other: &JsStr<'_>) -> bool {
1007 self.as_str() == *other
1008 }
1009}
1010
1011impl PartialEq<JsString> for JsStr<'_> {
1012 #[inline]
1013 fn eq(&self, other: &JsString) -> bool {
1014 other == self
1015 }
1016}
1017
1018impl PartialOrd for JsString {
1019 #[inline]
1020 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1021 Some(self.cmp(other))
1022 }
1023}
1024
1025impl FromStr for JsString {
1026 type Err = Infallible;
1027
1028 #[inline]
1029 fn from_str(s: &str) -> Result<Self, Self::Err> {
1030 Ok(Self::from(s))
1031 }
1032}
1033
1034pub trait JsStringSliceIndex {
1037 fn get(self, str: &JsString) -> Option<JsString>;
1039}
1040
1041macro_rules! impl_js_string_slice_index {
1042 ($($type:ty),+ $(,)?) => {
1043 $(
1044 impl JsStringSliceIndex for $type {
1045 fn get(self, str: &JsString) -> Option<JsString> {
1046 let start = match std::ops::RangeBounds::<usize>::start_bound(&self) {
1047 std::ops::Bound::Included(start) => *start,
1048 std::ops::Bound::Excluded(start) => *start + 1,
1049 std::ops::Bound::Unbounded => 0,
1050 };
1051
1052 let end = match std::ops::RangeBounds::<usize>::end_bound(&self) {
1053 std::ops::Bound::Included(end) => *end + 1,
1054 std::ops::Bound::Excluded(end) => *end,
1055 std::ops::Bound::Unbounded => str.len(),
1056 };
1057
1058 if end > str.len() || start > end {
1059 None
1060 } else {
1061 Some(unsafe { JsString::slice_unchecked(str, start, end) })
1063 }
1064 }
1065 }
1066 )+
1067 };
1068}
1069
1070impl_js_string_slice_index!(
1071 std::ops::Range<usize>,
1072 std::ops::RangeInclusive<usize>,
1073 std::ops::RangeTo<usize>,
1074 std::ops::RangeToInclusive<usize>,
1075 std::ops::RangeFrom<usize>,
1076 std::ops::RangeFull,
1077);