From 0b81185c990d301dd104a1910c376543c543ef57 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 31 Dec 2017 15:45:47 -0800 Subject: [PATCH] Expose primitive information about `JsObject` Adds bindings for wbindgen to fill in via JS bindings to read the various primitive properties of a JS value. --- crates/wasm-bindgen-cli-support/src/ts.rs | 187 ++++++++++++++++------ src/lib.rs | 155 +++++++++++++++++- tests/api.rs | 97 +++++++++++ 3 files changed, 391 insertions(+), 48 deletions(-) diff --git a/crates/wasm-bindgen-cli-support/src/ts.rs b/crates/wasm-bindgen-cli-support/src/ts.rs index 52d11b4e..9a6f582d 100644 --- a/crates/wasm-bindgen-cli-support/src/ts.rs +++ b/crates/wasm-bindgen-cli-support/src/ts.rs @@ -525,58 +525,151 @@ impl Js { extra_imports_interface.push_str("}\n"); } - if self.wasm_import_needed("__wbindgen_object_clone_ref", m) { - self.expose_add_heap_object(); - self.expose_get_object(); - let bump_cnt = if self.debug { - String::from(" - if (typeof(val) === 'number') - throw new Error('corrupt slab'); - val.cnt += 1; - ") - } else { - String::from("(val as {cnt:number}).cnt += 1;") + { + let mut bind = |name: &str, f: &Fn(&mut Self) -> String| { + if !self.wasm_import_needed(name, m) { + return + } + imports_object.push_str(&format!(" + {}: {}, + ", m.import_name(name), f(self))); }; - imports_object.push_str(&format!(" - {}: function(idx: number): number {{ - // If this object is on the stack promote it to the heap. - if ((idx & 1) === 1) - return addHeapObject(getObject(idx)); - // Otherwise if the object is on the heap just bump the - // refcount and move on - const val = slab[idx >> 1]; - {} - return idx; - }}, - ", m.import_name("__wbindgen_object_clone_ref"), bump_cnt)); - } + bind("__wbindgen_object_clone_ref", &|me| { + me.expose_add_heap_object(); + me.expose_get_object(); + let bump_cnt = if me.debug { + String::from(" + if (typeof(val) === 'number') + throw new Error('corrupt slab'); + val.cnt += 1; + ") + } else { + String::from("(val as {cnt:number}).cnt += 1;") + }; + format!(" + function(idx: number): number {{ + // If this object is on the stack promote it to the heap. + if ((idx & 1) === 1) + return addHeapObject(getObject(idx)); - if self.wasm_import_needed("__wbindgen_object_drop_ref", m) { - self.expose_drop_ref(); - let name = m.import_name("__wbindgen_object_drop_ref"); - imports_object.push_str(&format!("{}: dropRef,\n", name)); - } + // Otherwise if the object is on the heap just bump the + // refcount and move on + const val = slab[idx >> 1]; + {} + return idx; + }} + ", bump_cnt) + }); - if self.wasm_import_needed("__wbindgen_string_new", m) { - self.expose_add_heap_object(); - self.expose_get_string_from_wasm(); - imports_object.push_str(&format!(" - {}: function(ptr: number, len: number): number {{ - return addHeapObject(getStringFromWasm(ptr, len)); - }}, - ", m.import_name("__wbindgen_string_new"))); - } + bind("__wbindgen_object_drop_ref", &|me| { + me.expose_drop_ref(); + "dropRef".to_string() + }); - if self.wasm_import_needed("__wbindgen_throw", m) { - self.expose_get_string_from_wasm(); - imports_object.push_str(&format!("\ - {}: function(ptr: number, len: number) {{ - throw new Error(getStringFromWasm(ptr, len)); - }}, - ", - m.import_name("__wbindgen_throw"), - )); + bind("__wbindgen_string_new", &|me| { + me.expose_add_heap_object(); + me.expose_get_string_from_wasm(); + String::from("(p, l) => addHeapObject(getStringFromWasm(p, l))") + }); + + bind("__wbindgen_number_new", &|me| { + me.expose_add_heap_object(); + String::from("addHeapObject") + }); + + bind("__wbindgen_number_get", &|me| { + me.expose_global_memory(); + String::from(" + function(n: number, invalid: number): number { + let obj = getObject(n); + if (typeof(obj) === 'number') + return obj; + (new Uint8Array(memory.buffer))[invalid] = 1; + return 0; + } + ") + }); + + bind("__wbindgen_undefined_new", &|me| { + me.expose_add_heap_object(); + String::from("() => addHeapObject(undefined)") + }); + + bind("__wbindgen_null_new", &|me| { + me.expose_add_heap_object(); + String::from("() => addHeapObject(null)") + }); + + bind("__wbindgen_is_null", &|me| { + me.expose_get_object(); + String::from("(idx) => getObject(idx) === null ? 1 : 0") + }); + + bind("__wbindgen_is_undefined", &|me| { + me.expose_get_object(); + String::from("(idx) => getObject(idx) === undefined ? 1 : 0") + }); + + bind("__wbindgen_boolean_new", &|me| { + me.expose_add_heap_object(); + String::from("(v) => addHeapObject(v == 1)") + }); + + bind("__wbindgen_boolean_get", &|me| { + me.expose_get_object(); + String::from("(i) => { + let v = getObject(i); + if (typeof(v) == 'boolean') { + return v ? 1 : 0; + } else { + return 2; + } + }") + }); + + bind("__wbindgen_symbol_new", &|me| { + me.expose_get_string_from_wasm(); + me.expose_add_heap_object(); + String::from("(ptr, len) => { + let a: Symbol; + console.log(ptr, len); + if (ptr === 0) { + a = Symbol(); + } else { + a = Symbol(getStringFromWasm(ptr, len)); + } + return addHeapObject(a); + }") + }); + + bind("__wbindgen_is_symbol", &|me| { + me.expose_get_object(); + String::from("(i) => typeof(getObject(i)) == 'symbol' ? 1 : 0") + }); + + bind("__wbindgen_throw", &|me| { + me.expose_get_string_from_wasm(); + String::from(" + function(ptr: number, len: number) { + throw new Error(getStringFromWasm(ptr, len)); + } + ") + }); + + bind("__wbindgen_string_get", &|me| { + me.expose_pass_string_to_wasm(m); + me.expose_get_object(); + me.expose_global_memory(); + String::from("(i, len_ptr) => { + let obj = getObject(i); + if (typeof(obj) !== 'string') + return 0; + const [ptr, len] = passStringToWasm(obj); + (new Uint32Array(memory.buffer))[len_ptr / 4] = len; + return ptr; + }") + }); } let mut writes = String::new(); diff --git a/src/lib.rs b/src/lib.rs index 847f3ed4..a8efe69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ extern crate wasm_bindgen_macro; use std::mem; +use std::ptr; /// A module which is typically glob imported from: /// @@ -31,7 +32,7 @@ pub struct JsObject { } impl JsObject { - /// Creates a new JS object which is a string. + /// Creates a new JS value which is a string. /// /// The utf-8 string provided is copied to the JS heap and the string will /// be owned by the JS garbage collector. @@ -41,6 +42,52 @@ impl JsObject { } } + /// Creates a new JS value which is a number. + /// + /// This function creates a JS value representing a number (a heap + /// allocated number) and returns a handle to the JS version of it. + pub fn from_f64(n: f64) -> JsObject { + unsafe { + JsObject::__from_idx(__wbindgen_number_new(n)) + } + } + + /// Creates a new JS value which is a boolean. + /// + /// This function creates a JS object representing a boolean (a heap + /// allocated boolean) and returns a handle to the JS version of it. + pub fn from_bool(b: bool) -> JsObject { + unsafe { + JsObject::__from_idx(__wbindgen_boolean_new(b as u32)) + } + } + + /// Creates a new JS value representing `undefined`. + pub fn undefined() -> JsObject { + unsafe { + JsObject::__from_idx(__wbindgen_undefined_new()) + } + } + + /// Creates a new JS value representing `null`. + pub fn null() -> JsObject { + unsafe { + JsObject::__from_idx(__wbindgen_null_new()) + } + } + + /// Creates a new JS symbol with the optional description specified. + /// + /// This function will invoke the `Symbol` constructor in JS and return the + /// JS object corresponding to the symbol created. + pub fn symbol(description: Option<&str>) -> JsObject { + unsafe { + let ptr = description.map(|s| s.as_ptr()).unwrap_or(ptr::null()); + let len = description.map(|s| s.len()).unwrap_or(0); + JsObject::__from_idx(__wbindgen_symbol_new(ptr, len)) + } + } + #[doc(hidden)] pub fn __from_idx(idx: u32) -> JsObject { JsObject { idx } @@ -57,6 +104,77 @@ impl JsObject { mem::forget(self); return ret } + + /// Returns the `f64` value of this JS value if it's an instance of a + /// number. + /// + /// If this JS value is not an instance of a number then this returns + /// `None`. + pub fn as_f64(&self) -> Option { + let mut invalid = 0; + unsafe { + let ret = __wbindgen_number_get(self.idx, &mut invalid); + if invalid == 1 { + None + } else { + Some(ret) + } + } + } + + /// Returns the `String` of this JS value if it's an instance of a + /// string and it's valid utf-8. + /// + /// If this JS value is not an instance of a string or if it's not valid + /// utf-8 then this returns `None`. + pub fn as_string(&self) -> Option { + unsafe { + let mut len = 0; + let ptr = __wbindgen_string_get(self.idx, &mut len); + if ptr.is_null() { + None + } else { + let data = Vec::from_raw_parts(ptr, len, len); + Some(String::from_utf8_unchecked(data)) + } + } + } + + /// Returns the `bool` value of this JS value if it's an instance of a + /// boolean. + /// + /// If this JS value is not an instance of a boolean then this returns + /// `None`. + pub fn as_bool(&self) -> Option { + unsafe { + match __wbindgen_boolean_get(self.idx) { + 0 => Some(false), + 1 => Some(true), + _ => None, + } + } + } + + /// Tests whether this JS value is `null` + pub fn is_null(&self) -> bool { + unsafe { + __wbindgen_is_null(self.idx) == 1 + } + } + + /// Tests whether this JS value is `undefined` + pub fn is_undefined(&self) -> bool { + unsafe { + __wbindgen_is_undefined(self.idx) == 1 + } + } + + /// Tests whether the type of this JS value is `symbol` + pub fn is_symbol(&self) -> bool { + unsafe { + __wbindgen_is_symbol(self.idx) == 1 + } + } } impl<'a> From<&'a str> for JsObject { @@ -65,10 +183,45 @@ impl<'a> From<&'a str> for JsObject { } } +impl<'a> From<&'a String> for JsObject { + fn from(s: &'a String) -> JsObject { + JsObject::from_str(s) + } +} + +impl From for JsObject { + fn from(s: bool) -> JsObject { + JsObject::from_bool(s) + } +} + +macro_rules! numbers { + ($($n:ident)*) => ($( + impl From<$n> for JsObject { + fn from(n: $n) -> JsObject { + JsObject::from_f64(n.into()) + } + } + )*) +} + +numbers! { i8 u8 i16 u16 i32 u32 f32 f64 } + extern { fn __wbindgen_object_clone_ref(idx: u32) -> u32; fn __wbindgen_object_drop_ref(idx: u32); fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_number_new(f: f64) -> u32; + fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; + fn __wbindgen_null_new() -> u32; + fn __wbindgen_undefined_new() -> u32; + fn __wbindgen_is_null(idx: u32) -> u32; + fn __wbindgen_is_undefined(idx: u32) -> u32; + fn __wbindgen_boolean_new(val: u32) -> u32; + fn __wbindgen_boolean_get(idx: u32) -> u32; + fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_is_symbol(idx: u32) -> u32; + fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; } impl Clone for JsObject { diff --git a/tests/api.rs b/tests/api.rs index f1073215..84d609fa 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -18,6 +18,82 @@ fn works() { pub fn bar(s: &str) -> JsObject { JsObject::from(s) } + + pub fn baz() -> JsObject { + JsObject::from(1.0) + } + + pub fn baz2(a: &JsObject, b: &JsObject) { + assert_eq!(a.as_f64(), Some(2.0)); + assert_eq!(b.as_f64(), None); + } + + pub fn js_null() -> JsObject { + JsObject::null() + } + + pub fn js_undefined() -> JsObject { + JsObject::undefined() + } + + pub fn test_is_null_undefined( + a: &JsObject, + b: &JsObject, + c: &JsObject, + ) { + assert!(a.is_null()); + assert!(!a.is_undefined()); + + assert!(!b.is_null()); + assert!(b.is_undefined()); + + assert!(!c.is_null()); + assert!(!c.is_undefined()); + } + + pub fn get_true() -> JsObject { + JsObject::from(true) + } + + pub fn get_false() -> JsObject { + JsObject::from(false) + } + + pub fn test_bool( + a: &JsObject, + b: &JsObject, + c: &JsObject, + ) { + assert_eq!(a.as_bool(), Some(true)); + assert_eq!(b.as_bool(), Some(false)); + assert_eq!(c.as_bool(), None); + } + + pub fn mk_symbol() -> JsObject { + let a = JsObject::symbol(None); + assert!(a.is_symbol()); + return a + } + + pub fn mk_symbol2(s: &str) -> JsObject { + let a = JsObject::symbol(Some(s)); + assert!(a.is_symbol()); + return a + } + + pub fn assert_symbols(a: &JsObject, b: &JsObject) { + assert!(a.is_symbol()); + assert!(!b.is_symbol()); + } + + pub fn acquire_string(a: &JsObject, b: &JsObject) { + assert_eq!(a.as_string().unwrap(), "foo"); + assert_eq!(b.as_string(), None); + } + + pub fn acquire_string2(a: &JsObject) -> String { + a.as_string().unwrap_or("wrong".to_string()) + } } "#) .file("test.ts", r#" @@ -29,6 +105,27 @@ fn works() { export function test(wasm: Exports) { assert.strictEqual(wasm.foo(), 'foo'); assert.strictEqual(wasm.bar('a'), 'a'); + assert.strictEqual(wasm.baz(), 1); + wasm.baz2(2, 'a'); + + assert.strictEqual(wasm.js_null(), null); + assert.strictEqual(wasm.js_undefined(), undefined); + + wasm.test_is_null_undefined(null, undefined, 1.0); + + assert.strictEqual(wasm.get_true(), true); + assert.strictEqual(wasm.get_false(), false); + wasm.test_bool(true, false, 1.0); + + assert.strictEqual(typeof(wasm.mk_symbol()), 'symbol'); + assert.strictEqual(typeof(wasm.mk_symbol2('a')), 'symbol'); + assert.strictEqual(Symbol.keyFor(wasm.mk_symbol()), undefined); + assert.strictEqual(Symbol.keyFor(wasm.mk_symbol2('b')), undefined); + + wasm.assert_symbols(Symbol(), 'a'); + wasm.acquire_string('foo', null) + assert.strictEqual(wasm.acquire_string2(''), ''); + assert.strictEqual(wasm.acquire_string2('a'), 'a'); } "#) .test();