Skip to content

Commit bc4a98d

Browse files
committed
Add JsString <-> char conversions
These are pretty common and already supported via ABI conversions, yet pretty easy to get wrong when converting them manually. Fixes #1363.
1 parent ac7230b commit bc4a98d

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

crates/js-sys/src/lib.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3641,6 +3641,36 @@ impl JsString {
36413641
) -> impl ExactSizeIterator<Item = u16> + DoubleEndedIterator<Item = u16> + 'a {
36423642
(0..self.length()).map(move |i| self.char_code_at(i) as u16)
36433643
}
3644+
3645+
/// If this string consists of a single Unicode code point, then this method
3646+
/// converts it into a Rust `char` without doing any allocations.
3647+
///
3648+
/// If this JS value is not a valid UTF-8 or consists of more than a single
3649+
/// codepoint, then this returns `None`.
3650+
///
3651+
/// Note that a single Unicode code point might be represented as more than
3652+
/// one code unit on the JavaScript side. For example, a JavaScript string
3653+
/// `"\uD801\uDC37"` is actually a single Unicode code point U+10437 which
3654+
/// corresponds to a character '𐐷'.
3655+
pub fn as_char(&self) -> Option<char> {
3656+
let len = self.length();
3657+
3658+
if len == 0 || len > 2 {
3659+
return None;
3660+
}
3661+
3662+
// This will be simplified when definitions are fixed:
3663+
// https://github.com/rustwasm/wasm-bindgen/issues/1362
3664+
let cp = self.code_point_at(0).as_f64().unwrap_throw() as u32;
3665+
3666+
let c = std::char::from_u32(cp)?;
3667+
3668+
if c.len_utf16() as u32 == len {
3669+
Some(c)
3670+
} else {
3671+
None
3672+
}
3673+
}
36443674
}
36453675

36463676
impl PartialEq<str> for JsString {
@@ -3649,6 +3679,12 @@ impl PartialEq<str> for JsString {
36493679
}
36503680
}
36513681

3682+
impl PartialEq<char> for JsString {
3683+
fn eq(&self, other: &char) -> bool {
3684+
self.as_char() == Some(*other)
3685+
}
3686+
}
3687+
36523688
impl<'a> PartialEq<&'a str> for JsString {
36533689
fn eq(&self, other: &&'a str) -> bool {
36543690
<JsString as PartialEq<str>>::eq(self, other)
@@ -3679,6 +3715,13 @@ impl From<String> for JsString {
36793715
}
36803716
}
36813717

3718+
impl From<char> for JsString {
3719+
#[inline]
3720+
fn from(c: char) -> Self {
3721+
JsString::from_code_point1(c as u32).unwrap_throw()
3722+
}
3723+
}
3724+
36823725
impl<'a> From<&'a JsString> for String {
36833726
fn from(s: &'a JsString) -> Self {
36843727
s.obj.as_string().unwrap_throw()

crates/js-sys/tests/wasm/JsString.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,3 +553,14 @@ fn is_valid_utf16() {
553553
assert!(!JsString::from_char_code1(0xd800).is_valid_utf16());
554554
assert!(!JsString::from_char_code1(0xdc00).is_valid_utf16());
555555
}
556+
557+
#[wasm_bindgen_test]
558+
fn as_char() {
559+
assert_eq!(JsString::from('a').as_char(), Some('a'));
560+
assert_eq!(JsString::from('🥑').as_char(), Some('🥑'));
561+
assert_eq!(JsString::from("").as_char(), None);
562+
assert_eq!(JsString::from("ab").as_char(), None);
563+
assert_eq!(JsString::from_char_code1(0xd800).as_char(), None);
564+
assert_eq!(JsString::from_char_code1(0xdc00).as_char(), None);
565+
assert_eq!(JsString::from_char_code1(0xdfff).as_char(), None);
566+
}

0 commit comments

Comments
 (0)