diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 173df872..62d2deec 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -350,16 +350,67 @@ impl<'a> Context<'a> { )) })?; - self.bind("__wbindgen_to_string", &|me| { + self.bind("__wbindgen_debug_string", &|me| { me.expose_pass_string_to_wasm()?; me.expose_get_object(); me.expose_uint32_memory(); Ok(String::from( " function(i, len_ptr) { - let toString = getObject(i).toString(); - if (typeof(toString) !== 'string') return 0; - const ptr = passStringToWasm(toString); + const debug_str = val => { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return val + ''; + } + if (type == 'string') { + return '\"' + val + '\"'; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol()'; + } else { + return 'Symbol(' + description + ')'; + } + } + if (type == 'function') { + return 'Function'; + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debug_str(val[0]); + } + for(let i = 1; i < length; i++) { + debug += debug_str(val[i]) + ', '; + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\\[object ([^])+\\]/.exec(val.toString()); + let className; + if (builtInMatches.len > 0) { + className = builtInMatches[0]; + } else { + // Failed to match the standard '[object ClassName]' + return val.toString(); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + return 'Object(' + JSON.stringify(val) + ')'; + } else { + return className; + } + }; + const val = getObject(i); + const debug = debug_str(val); + const ptr = passStringToWasm(debug); getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN; return ptr; } diff --git a/src/lib.rs b/src/lib.rs index e507af0e..7834819b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,7 +204,13 @@ impl JsValue { where T: for<'a> serde::de::Deserialize<'a>, { - serde_json::from_str(&self.as_json()) + unsafe { + let mut ptr = ptr::null_mut(); + let len = __wbindgen_json_serialize(self.idx, &mut ptr); + let s = Vec::from_raw_parts(ptr, len, len); + let s = String::from_utf8_unchecked(s); + serde_json::from_str(&s) + } } /// Returns the `f64` value of this JS value if it's an instance of a @@ -304,24 +310,14 @@ impl JsValue { unsafe { __wbindgen_is_function(self.idx) == 1 } } - /// Helper method to get the value as a json String (serialized in javascript) - fn as_json(&self) -> String { - unsafe { - let mut ptr = ptr::null_mut(); - let len = __wbindgen_json_serialize(self.idx, &mut ptr); - let s = Vec::from_raw_parts(ptr, len, len); - String::from_utf8_unchecked(s) - } - } - - /// Get the string value of self using the JS `toString` method. + /// Get a string representation of the JavaScript object for debugging #[cfg(feature = "std")] - fn js_to_string(&self) -> String { + fn as_debug_string(&self) -> String { unsafe { let mut len = 0; - let ptr = __wbindgen_to_string(self.idx, &mut len); + let ptr = __wbindgen_debug_string(self.idx, &mut len); if ptr.is_null() { - unreachable!("Object.toString must return a valid string") + unreachable!("`__wbindgen_debug_string` must return a valid string") } else { let data = Vec::from_raw_parts(ptr, len, len); String::from_utf8_unchecked(data) @@ -503,7 +499,7 @@ externs! { fn __wbindgen_is_function(idx: u32) -> u32; fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; - fn __wbindgen_to_string(idx: u32, len: *mut usize) -> *mut u8; + fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8; fn __wbindgen_throw(a: *const u8, b: usize) -> !; fn __wbindgen_rethrow(a: u32) -> !; @@ -530,35 +526,19 @@ impl Clone for JsValue { } } +#[cfg(feature = "std")] impl fmt::Debug for JsValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(n) = self.as_f64() { - return n.fmt(f); - } - #[cfg(feature = "std")] - { - if let Some(n) = self.as_string() { - return n.fmt(f); - } - } - if let Some(n) = self.as_bool() { - return n.fmt(f); - } - if self.is_null() { - return fmt::Display::fmt("null", f); - } - if self.is_undefined() { - return fmt::Display::fmt("undefined", f); - } - if self.is_symbol() { - return fmt::Display::fmt("Symbol(..)", f); - } - let json = self.as_json(); - if json == "{}" { - f.write_str(&self.js_to_string()) - } else { - f.write_str(&json) - } + f.write_str(&self.as_debug_string()) + } +} + +#[cfg(not(feature = "std"))] +impl fmt::Debug for JsValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO before merge - this is less info than before - is this OK? Can we do the above + // without using allocation (no_std)? + f.write_str("[object]") } } diff --git a/tests/wasm/api.rs b/tests/wasm/api.rs index e7d95133..5a2e11c8 100644 --- a/tests/wasm/api.rs +++ b/tests/wasm/api.rs @@ -80,7 +80,7 @@ pub fn api_test_bool(a: &JsValue, b: &JsValue, c: &JsValue) { pub fn api_mk_symbol() -> JsValue { let a = JsValue::symbol(None); assert!(a.is_symbol()); - assert_eq!(format!("{:?}", a), "Symbol(..)"); + assert_eq!(format!("{:?}", a), "Symbol()"); return a; }