diff --git a/CHANGELOG.md b/CHANGELOG.md index 47872006..ddb5c2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,78 @@ Released YYYY-MM-DD. -------------------------------------------------------------------------------- +## 0.2.42 + +Released 2019-04-11. + +### Fixed + +* Fixed an issue in Firefox where using `encodeInto` accidentally caused empty + strings to keep getting passed to Rust. + [#1434](https://github.com/rustwasm/wasm-bindgen/pull/1434) + +-------------------------------------------------------------------------------- + +## 0.2.41 + +Released 2019-04-10. + +### Added + +* Initial support for transitive NPM dependencies has been added, although + support has not fully landed in `wasm-pack` yet so it's not 100% integrated. + [#1305](https://github.com/rustwasm/wasm-bindgen/pull/1305) + +* The `constructor` property of `Object` is now bound in `js-sys`. + [#1403](https://github.com/rustwasm/wasm-bindgen/pull/1403) + +* The `Closure` type now always implements `Debug`. + [#1408](https://github.com/rustwasm/wasm-bindgen/pull/1408) + +* Closures which take one `&T` argument are now supported. More implementations + may be added in the future, but for now it's just one argument closures. + [#1417](https://github.com/rustwasm/wasm-bindgen/pull/1417) + +* The TypeScript bindings for `--web` now expose the `init` function. + [#1412](https://github.com/rustwasm/wasm-bindgen/pull/1412) + +* A `js_sys::JsString::is_valid_utf16` method has been added to handle unpaired + surrogates in JS strings. Surrounding documentation has also been updated to + document this potential pitfall. + [#1416](https://github.com/rustwasm/wasm-bindgen/pull/1416) + +* A `wasm_bindgen::function_table()` function has been added to expose the + `WebAssembly.Table` and get access to it in wasm code. + [#1431](https://github.com/rustwasm/wasm-bindgen/pull/1431) + +### Fixed + +* Reexporting the `wasm_bindgen` macro in crates has been fixed. + [#1359](https://github.com/rustwasm/wasm-bindgen/pull/1359) + +* Returning `u32` to JS has been fixed where large `u32` values would show up in + JS as large negative numbers. + [#1401](https://github.com/rustwasm/wasm-bindgen/pull/1401) + +* Manual instantiation with `WebAssembly.Module` has been fixed. + [#1419](https://github.com/rustwasm/wasm-bindgen/pull/1419) + +* Error message for non-`Copy` public struct fields has been improved. + [#1430](https://github.com/rustwasm/wasm-bindgen/pull/1430) + +### Changed + +* Performance of passing strings to Rust in Node.js has been improved. + [#1391](https://github.com/rustwasm/wasm-bindgen/pull/1391) + +* Performance of `js_sys::try_iter` has been improved. + [#1393](https://github.com/rustwasm/wasm-bindgen/pull/1393) + +* Performance of using `TextEncoder#encodeInto` has been improved. + [#1414](https://github.com/rustwasm/wasm-bindgen/pull/1414) + +-------------------------------------------------------------------------------- + ## 0.2.40 Released 2019-03-21. diff --git a/Cargo.toml b/Cargo.toml index 68044f2c..67ddabbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" readme = "README.md" @@ -35,13 +35,13 @@ strict-macro = ["wasm-bindgen-macro/strict-macro"] xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"] [dependencies] -wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.40" } +wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.42" } serde = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -js-sys = { path = 'crates/js-sys', version = '0.3.17' } -wasm-bindgen-test = { path = 'crates/test', version = '=0.2.40' } +js-sys = { path = 'crates/js-sys', version = '0.3.19' } +wasm-bindgen-test = { path = 'crates/test', version = '=0.2.42' } serde_derive = "1.0" wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' } wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' } diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index 7c8e5419..e6afb759 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-anyref-xform" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/anyref-xform" diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 77489235..1ad908c5 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-backend" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend" @@ -22,4 +22,4 @@ log = "0.4" proc-macro2 = "0.4.8" quote = '0.6' syn = { version = '0.15', features = ['full'] } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.40" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.42" } diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index e61f5878..aeb56ccb 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -183,6 +183,7 @@ pub struct ImportType { pub attrs: Vec, pub doc_comment: Option, pub instanceof_shim: String, + pub is_type_of: Option, pub extends: Vec, pub vendor_prefixes: Vec, } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index f53cbb9c..310c5847 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -291,11 +291,13 @@ impl ToTokens for ast::StructField { let ty = &self.ty; let getter = &self.getter; let setter = &self.setter; + + let assert_copy = quote! { assert_copy::<#ty>() }; + let assert_copy = respan(assert_copy, ty); (quote! { - #[no_mangle] #[doc(hidden)] - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] #[allow(clippy::all)] + #[cfg_attr(all(target_arch = "wasm32", not(target_os = "emscripten")), no_mangle)] pub unsafe extern "C" fn #getter(js: u32) -> <#ty as wasm_bindgen::convert::IntoWasmAbi>::Abi { @@ -303,7 +305,7 @@ impl ToTokens for ast::StructField { use wasm_bindgen::convert::{GlobalStack, IntoWasmAbi}; fn assert_copy(){} - assert_copy::<#ty>(); + #assert_copy; let js = js as *mut WasmRefCell<#struct_name>; assert_not_null(js); @@ -576,6 +578,24 @@ impl ToTokens for ast::ImportType { let const_name = format!("__wbg_generated_const_{}", rust_name); let const_name = Ident::new(&const_name, Span::call_site()); let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site()); + + let internal_obj = match self.extends.first() { + Some(target) => { + quote! { #target } + } + None => { + quote! { wasm_bindgen::JsValue } + } + }; + + let is_type_of = self.is_type_of.as_ref().map(|is_type_of| quote! { + #[inline] + fn is_type_of(val: &JsValue) -> bool { + let is_type_of: fn(&JsValue) -> bool = #is_type_of; + is_type_of(val) + } + }); + (quote! { #[allow(bad_style)] #(#attrs)* @@ -583,7 +603,7 @@ impl ToTokens for ast::ImportType { #[repr(transparent)] #[allow(clippy::all)] #vis struct #rust_name { - obj: wasm_bindgen::JsValue, + obj: #internal_obj } #[allow(bad_style)] @@ -602,6 +622,15 @@ impl ToTokens for ast::ImportType { } } + impl core::ops::Deref for #rust_name { + type Target = #internal_obj; + + #[inline] + fn deref(&self) -> &#internal_obj { + &self.obj + } + } + impl IntoWasmAbi for #rust_name { type Abi = ::Abi; @@ -627,7 +656,7 @@ impl ToTokens for ast::ImportType { #[inline] unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { #rust_name { - obj: JsValue::from_abi(js, extra), + obj: JsValue::from_abi(js, extra).into(), } } } @@ -654,7 +683,7 @@ impl ToTokens for ast::ImportType { unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { let tmp = ::ref_from_abi(js, extra); core::mem::ManuallyDrop::new(#rust_name { - obj: core::mem::ManuallyDrop::into_inner(tmp), + obj: core::mem::ManuallyDrop::into_inner(tmp).into(), }) } } @@ -663,20 +692,20 @@ impl ToTokens for ast::ImportType { impl From for #rust_name { #[inline] fn from(obj: JsValue) -> #rust_name { - #rust_name { obj } + #rust_name { obj: obj.into() } } } impl AsRef for #rust_name { #[inline] - fn as_ref(&self) -> &JsValue { &self.obj } + fn as_ref(&self) -> &JsValue { self.obj.as_ref() } } impl From<#rust_name> for JsValue { #[inline] fn from(obj: #rust_name) -> JsValue { - obj.obj + obj.obj.into() } } @@ -699,9 +728,11 @@ impl ToTokens for ast::ImportType { panic!("cannot check instanceof on non-wasm targets"); } + #is_type_of + #[inline] fn unchecked_from_js(val: JsValue) -> Self { - #rust_name { obj: val } + #rust_name { obj: val.into() } } #[inline] @@ -714,24 +745,9 @@ impl ToTokens for ast::ImportType { () }; - }).to_tokens(tokens); - - let deref_target = match self.extends.first() { - Some(target) => quote! { #target }, - None => quote! { JsValue }, - }; - (quote! { - #[allow(clippy::all)] - impl core::ops::Deref for #rust_name { - type Target = #deref_target; - - #[inline] - fn deref(&self) -> &#deref_target { - self.as_ref() - } - } }) .to_tokens(tokens); + for superclass in self.extends.iter() { (quote! { #[allow(clippy::all)] @@ -1430,3 +1446,31 @@ impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> { .to_tokens(tokens); } } + +fn respan( + input: TokenStream, + span: &dyn ToTokens, +) -> TokenStream { + let mut first_span = Span::call_site(); + let mut last_span = Span::call_site(); + let mut spans = TokenStream::new(); + span.to_tokens(&mut spans); + + for (i, token) in spans.into_iter().enumerate() { + if i == 0 { + first_span = token.span(); + } + last_span = token.span(); + } + + let mut new_tokens = Vec::new(); + for (i, mut token) in input.into_iter().enumerate() { + if i == 0 { + token.set_span(first_span); + } else { + token.set_span(last_span); + } + new_tokens.push(token); + } + new_tokens.into_iter().collect() +} diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 66082c09..c31be03f 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-cli-support" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support" @@ -19,7 +19,7 @@ rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" walrus = "0.5.0" -wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.40' } -wasm-bindgen-shared = { path = "../shared", version = '=0.2.40' } -wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.40' } -wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.40' } +wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.42' } +wasm-bindgen-shared = { path = "../shared", version = '=0.2.42' } +wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.42' } +wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.42' } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 4a9fabcd..d9f02ad6 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -311,6 +311,7 @@ impl<'a> Context<'a> { /// `--target no-modules`, `--target web`, or for bundlers. This is the very /// last step performed in `finalize`. fn finalize_js(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) { + let mut ts = self.typescript.clone(); let mut js = String::new(); if self.config.mode.no_modules() { js.push_str("(function() {\n"); @@ -318,7 +319,7 @@ impl<'a> Context<'a> { // Depending on the output mode, generate necessary glue to actually // import the wasm file in one way or another. - let mut init = String::new(); + let mut init = (String::new(), String::new()); match &self.config.mode { // In `--target no-modules` mode we need to both expose a name on // the global object as well as generate our own custom start @@ -353,7 +354,8 @@ impl<'a> Context<'a> { | OutputMode::Node { experimental_modules: true, } => { - js.push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); + self.imports + .push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); if needs_manual_start { self.footer.push_str("wasm.__wbindgen_start();\n"); } @@ -364,14 +366,22 @@ impl<'a> Context<'a> { // expose the same initialization function as `--target no-modules` // as the default export of the module. OutputMode::Web => { - js.push_str("const __exports = {};\n"); + self.imports_post.push_str("const __exports = {};\n"); self.imports_post.push_str("let wasm;\n"); init = self.gen_init(&module_name, needs_manual_start); self.footer.push_str("export default init;\n"); } } + let (init_js, init_ts) = init; + + ts.push_str(&init_ts); + // Emit all the JS for importing all our functionality + assert!( + !self.config.mode.uses_es_modules() || js.is_empty(), + "ES modules require imports to be at the start of the file" + ); js.push_str(&self.imports); js.push_str("\n"); js.push_str(&self.imports_post); @@ -382,7 +392,7 @@ impl<'a> Context<'a> { js.push_str("\n"); // Generate the initialization glue, if there was any - js.push_str(&init); + js.push_str(&init_js); js.push_str("\n"); js.push_str(&self.footer); js.push_str("\n"); @@ -394,7 +404,7 @@ impl<'a> Context<'a> { js = js.replace("\n\n\n", "\n\n"); } - (js, self.typescript.clone()) + (js, ts) } fn wire_up_initial_intrinsics(&mut self) -> Result<(), Error> { @@ -773,6 +783,14 @@ impl<'a> Context<'a> { )) })?; + self.bind("__wbindgen_function_table", &|me| { + me.function_table_needed = true; + Ok(format!( + "function() {{ return {}; }}", + me.add_heap_object("wasm.__wbg_function_table") + )) + })?; + self.bind("__wbindgen_rethrow", &|me| { Ok(format!( "function(idx) {{ throw {}; }}", @@ -842,7 +860,34 @@ impl<'a> Context<'a> { Ok(()) } - fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> String { + fn ts_for_init_fn(has_memory: bool) -> String { + let (memory_doc, memory_param) = if has_memory { + ( + "* @param {WebAssembly.Memory} maybe_memory\n", + ", maybe_memory: WebAssembly.Memory", + ) + } else { + ("", "") + }; + format!( + "\n\ + /**\n\ + * If `module_or_path` is {{RequestInfo}}, makes a request and\n\ + * for everything else, calls `WebAssembly.instantiate` directly.\n\ + *\n\ + * @param {{RequestInfo | BufferSource | WebAssembly.Module}} module_or_path\n\ + {}\ + *\n\ + * @returns {{Promise}}\n\ + */\n\ + export function init \ + (module_or_path: RequestInfo | BufferSource | WebAssembly.Module{}): Promise; + ", + memory_doc, memory_param + ) + } + + fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) { let mem = self.module.memories.get(self.memory); let (init_memory1, init_memory2) = if mem.import.is_some() { let mut memory = String::from("new WebAssembly.Memory({"); @@ -862,15 +907,21 @@ impl<'a> Context<'a> { } else { (String::new(), String::new()) }; + let init_memory_arg = if mem.import.is_some() { + ", maybe_memory" + } else { + "" + }; - format!( + let ts = Self::ts_for_init_fn(mem.import.is_some()); + let js = format!( "\ - function init(module_or_path, maybe_memory) {{ + function init(module{init_memory_arg}) {{ let result; const imports = {{ './{module}': __exports }}; - if (module_or_path instanceof URL || typeof module_or_path === 'string' || module_or_path instanceof Request) {{ + if (module instanceof URL || typeof module === 'string' || module instanceof Request) {{ {init_memory2} - const response = fetch(module_or_path); + const response = fetch(module); if (typeof WebAssembly.instantiateStreaming === 'function') {{ result = WebAssembly.instantiateStreaming(response, imports) .catch(e => {{ @@ -890,9 +941,13 @@ impl<'a> Context<'a> { }} }} else {{ {init_memory1} - result = WebAssembly.instantiate(module_or_path, imports) - .then(instance => {{ - return {{ instance, module: module_or_path }}; + result = WebAssembly.instantiate(module, imports) + .then(result => {{ + if (result instanceof WebAssembly.Instance) {{ + return {{ instance: result, module }}; + }} else {{ + return result; + }} }}); }} return result.then(({{instance, module}}) => {{ @@ -903,6 +958,7 @@ impl<'a> Context<'a> { }}); }} ", + init_memory_arg = init_memory_arg, module = module_name, init_memory1 = init_memory1, init_memory2 = init_memory2, @@ -911,7 +967,9 @@ impl<'a> Context<'a> { } else { "" }, - ) + ); + + (js, ts) } fn bind( @@ -1306,13 +1364,12 @@ impl<'a> Context<'a> { while (true) {{ const view = getUint8Memory().subarray(ptr + writeOffset, ptr + size); const {{ read, written }} = cachedTextEncoder.encodeInto(arg, view); - arg = arg.substring(read); writeOffset += written; - if (arg.length === 0) {{ + if (read === arg.length) {{ break; }} - ptr = wasm.__wbindgen_realloc(ptr, size, size * 2); - size *= 2; + arg = arg.substring(read); + ptr = wasm.__wbindgen_realloc(ptr, size, size += arg.length * 3); }} WASM_VECTOR_LEN = writeOffset; return ptr; diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 45186593..5793b83b 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -42,6 +42,19 @@ enum OutputMode { Node { experimental_modules: bool }, } +impl OutputMode { + fn uses_es_modules(&self) -> bool { + match self { + OutputMode::Bundler { .. } + | OutputMode::Web + | OutputMode::Node { + experimental_modules: true, + } => true, + _ => false, + } + } +} + enum Input { Path(PathBuf), Module(Module, String), diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 478e661a..5635da9c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-cli" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli" @@ -25,8 +25,8 @@ serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" walrus = "0.5" -wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.40" } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.40" } +wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.42" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.42" } [dev-dependencies] assert_cmd = "0.11" diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml index 097541b5..9e6072ec 100644 --- a/crates/futures/Cargo.toml +++ b/crates/futures/Cargo.toml @@ -7,13 +7,13 @@ license = "MIT/Apache-2.0" name = "wasm-bindgen-futures" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures" readme = "./README.md" -version = "0.3.17" +version = "0.3.19" edition = "2018" [dependencies] futures = "0.1.20" -js-sys = { path = "../js-sys", version = '0.3.17' } -wasm-bindgen = { path = "../..", version = '0.2.40' } +js-sys = { path = "../js-sys", version = '0.3.19' } +wasm-bindgen = { path = "../..", version = '0.2.42' } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '0.2.40' } +wasm-bindgen-test = { path = '../test', version = '0.2.42' } diff --git a/crates/js-sys/Cargo.toml b/crates/js-sys/Cargo.toml index 2cad468e..41bd9b2c 100644 --- a/crates/js-sys/Cargo.toml +++ b/crates/js-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "js-sys" -version = "0.3.17" +version = "0.3.19" authors = ["The wasm-bindgen Developers"] readme = "./README.md" categories = ["wasm"] @@ -19,9 +19,9 @@ test = false doctest = false [dependencies] -wasm-bindgen = { path = "../..", version = "0.2.40" } +wasm-bindgen = { path = "../..", version = "0.2.42" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] futures = "0.1.20" -wasm-bindgen-test = { path = '../test', version = '=0.2.40' } -wasm-bindgen-futures = { path = '../futures', version = '=0.3.17' } +wasm-bindgen-test = { path = '../test', version = '=0.2.42' } +wasm-bindgen-futures = { path = '../futures', version = '=0.3.19' } diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 85fcab47..09468ed9 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -126,8 +126,8 @@ extern "C" { // Array #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[wasm_bindgen(extends = Object, is_type_of = Array::is_array)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Array; /// Creates a new empty array @@ -392,7 +392,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type ArrayBuffer; /// The `ArrayBuffer` object is used to represent a generic, @@ -466,14 +466,15 @@ extern "C" { // Boolean #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_bool().is_some())] + #[derive(Clone, PartialEq, Eq)] pub type Boolean; /// The `Boolean()` constructor creates an object wrapper for a boolean value. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) #[wasm_bindgen(constructor)] + #[deprecated(note = "recommended to use `Boolean::from` instead")] pub fn new(value: &JsValue) -> Boolean; /// The `valueOf()` method returns the primitive value of a `Boolean` object. @@ -483,11 +484,38 @@ extern "C" { pub fn value_of(this: &Boolean) -> bool; } +impl From for Boolean { + #[inline] + fn from(b: bool) -> Boolean { + Boolean::unchecked_from_js(JsValue::from(b)) + } +} + +impl From for bool { + #[inline] + fn from(b: Boolean) -> bool { + b.value_of() + } +} + +impl PartialEq for Boolean { + #[inline] + fn eq(&self, other: &bool) -> bool { + self.value_of() == *other + } +} + +impl fmt::Debug for Boolean { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value_of().fmt(f) + } +} + // DataView #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type DataView; /// The `DataView` view provides a low-level interface for reading and @@ -719,7 +747,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Error; /// The Error constructor creates an error object. @@ -758,7 +786,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object, extends = Error)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type EvalError; /// The EvalError object indicates an error regarding the global eval() function. This @@ -773,8 +801,8 @@ extern "C" { // Function #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[wasm_bindgen(extends = Object, is_type_of = JsValue::is_function)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Function; /// The `Function` constructor creates a new `Function` object. Calling the @@ -869,11 +897,7 @@ impl Function { /// If this JS value is not an instance of a function then this returns /// `None`. pub fn try_from(val: &JsValue) -> Option<&Function> { - if val.is_function() { - Some(unsafe { mem::transmute(val) }) - } else { - None - } + val.dyn_ref() } } @@ -881,7 +905,7 @@ impl Function { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Generator; /// The next() method returns an object with two properties done and value. @@ -909,7 +933,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Map; /// The clear() method removes all elements from a Map object. @@ -1001,6 +1025,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) #[derive(Clone, Debug)] + #[wasm_bindgen(is_type_of = Iterator::looks_like_iterator)] pub type Iterator; /// The next method always has to return an object with appropriate @@ -1011,6 +1036,26 @@ extern "C" { pub fn next(this: &Iterator) -> Result; } +impl Iterator { + fn looks_like_iterator(it: &JsValue) -> bool { + #[wasm_bindgen] + extern "C" { + type MaybeIterator; + + #[wasm_bindgen(method, getter)] + fn next(this: &MaybeIterator) -> JsValue; + } + + if !it.is_object() { + return false; + } + + let it = it.unchecked_ref::(); + + it.next().is_function() + } +} + /// An iterator over the JS `Symbol.iterator` iteration protocol. /// /// Use the `IntoIterator for &js_sys::Iterator` implementation to create this. @@ -1099,34 +1144,20 @@ impl IterState { /// Create an iterator over `val` using the JS iteration protocol and /// `Symbol.iterator`. pub fn try_iter(val: &JsValue) -> Result, JsValue> { - #[wasm_bindgen] - extern "C" { - type MaybeIterator; - - #[wasm_bindgen(method, getter)] - fn next(this: &MaybeIterator) -> JsValue; - } - let iter_sym = Symbol::iterator(); let iter_fn = Reflect::get(val, iter_sym.as_ref())?; - if !iter_fn.is_function() { - return Ok(None); - } - let iter_fn: Function = iter_fn.unchecked_into(); - let it = iter_fn.call0(val)?; - if !it.is_object() { - return Ok(None); - } + let iter_fn: Function = match iter_fn.dyn_into() { + Ok(iter_fn) => iter_fn, + Err(_) => return Ok(None), + }; - let next = it.unchecked_ref::().next(); + let it: Iterator = match iter_fn.call0(val)?.dyn_into() { + Ok(it) => it, + Err(_) => return Ok(None), + }; - Ok(if next.is_function() { - let it: Iterator = it.unchecked_into(); - Some(it.into_iter()) - } else { - None - }) + Ok(Some(it.into_iter())) } // IteratorNext @@ -1135,7 +1166,8 @@ extern "C" { /// The result of calling `next()` on a JS iterator. /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) - #[derive(Clone, Debug)] + #[wasm_bindgen(extends = Object)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type IteratorNext; /// Has the value `true` if the iterator is past the end of the iterated @@ -1157,8 +1189,8 @@ extern "C" { // Math #[wasm_bindgen] extern "C" { - #[derive(Clone, Debug)] #[wasm_bindgen(extends = Object)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Math; /// The Math.abs() function returns the absolute value of a number, that is @@ -1405,8 +1437,8 @@ extern "C" { // Number. #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_f64().is_some())] + #[derive(Clone)] pub type Number; /// The Number.isFinite() method determines whether the passed value is a finite number. @@ -1441,6 +1473,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) #[wasm_bindgen(constructor)] + #[deprecated(note = "recommended to use `Number::from` instead")] pub fn new(value: &JsValue) -> Number; /// The Number.parseInt() method parses a string argument and returns an @@ -1500,11 +1533,43 @@ extern "C" { pub fn value_of(this: &Number) -> f64; } +macro_rules! number_from { + ($($x:ident)*) => ($( + impl From<$x> for Number { + #[inline] + fn from(x: $x) -> Number { + Number::unchecked_from_js(JsValue::from(x)) + } + } + + impl PartialEq<$x> for Number { + #[inline] + fn eq(&self, other: &$x) -> bool { + self.value_of() == f64::from(*other) + } + } + )*) +} +number_from!(i8 u8 i16 u16 i32 u32 f32 f64); + +impl From for f64 { + #[inline] + fn from(n: Number) -> f64 { + n.value_of() + } +} + +impl fmt::Debug for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value_of().fmt(f) + } +} + // Date. #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Date; /// The getDate() method returns the day of the month for the @@ -2091,13 +2156,22 @@ impl Object { /// `None`. pub fn try_from(val: &JsValue) -> Option<&Object> { if val.is_object() { - Some(unsafe { mem::transmute(val) }) + Some(val.unchecked_ref()) } else { None } } } +impl PartialEq for Object { + #[inline] + fn eq(&self, other: &Object) -> bool { + Object::is(self.as_ref(), other.as_ref()) + } +} + +impl Eq for Object {} + // Proxy #[wasm_bindgen] extern "C" { @@ -2128,7 +2202,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError) #[wasm_bindgen(extends = Error, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type RangeError; /// The RangeError object indicates an error when a value is not in the set @@ -2147,7 +2221,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError) #[wasm_bindgen(extends = Error, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type ReferenceError; /// The ReferenceError object represents an error when a non-existent @@ -2161,8 +2235,8 @@ extern "C" { // Reflect #[wasm_bindgen] extern "C" { - #[derive(Clone, Debug)] #[wasm_bindgen(extends = Object)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Reflect; /// The static `Reflect.apply()` method calls a target function with @@ -2321,7 +2395,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type RegExp; /// The exec() method executes a search for a match in a specified @@ -2498,7 +2572,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Set; /// The `add()` method appends a new element with a specified value to the @@ -2588,7 +2662,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError) #[wasm_bindgen(extends = Error, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type SyntaxError; /// A SyntaxError is thrown when the JavaScript engine encounters tokens or @@ -2608,7 +2682,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError) #[wasm_bindgen(extends = Error, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type TypeError; /// The TypeError object represents an error when a value is not of the @@ -2627,7 +2701,7 @@ extern "C" { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError) #[wasm_bindgen(extends = Error, extends = Object, js_name = URIError)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type UriError; /// The URIError object represents an error when a global URI handling @@ -2642,7 +2716,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type WeakMap; /// The [`WeakMap`] object is a collection of key/value pairs in which the @@ -2686,7 +2760,7 @@ extern "C" { #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type WeakSet; /// The `WeakSet` object lets you store weakly held objects in a collection. @@ -2773,7 +2847,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/CompileError) #[wasm_bindgen(extends = Error, js_namespace = WebAssembly)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type CompileError; /// The `WebAssembly.CompileError()` constructor creates a new @@ -2795,7 +2869,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) #[wasm_bindgen(extends = Object, js_namespace = WebAssembly)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Instance; /// The `WebAssembly.Instance()` constructor function can be called to @@ -2826,7 +2900,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError) #[wasm_bindgen(extends = Error, js_namespace = WebAssembly)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type LinkError; /// The `WebAssembly.LinkError()` constructor creates a new WebAssembly @@ -2847,7 +2921,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/RuntimeError) #[wasm_bindgen(extends = Error, js_namespace = WebAssembly)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type RuntimeError; /// The `WebAssembly.RuntimeError()` constructor creates a new WebAssembly @@ -2868,7 +2942,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module) #[wasm_bindgen(js_namespace = WebAssembly, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Module; /// A `WebAssembly.Module` object contains stateless WebAssembly code @@ -2910,7 +2984,7 @@ pub mod WebAssembly { /// /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) #[wasm_bindgen(js_namespace = WebAssembly, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Table; /// The `WebAssembly.Table()` constructor creates a new `Table` object @@ -2956,7 +3030,7 @@ pub mod WebAssembly { extern "C" { /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) #[wasm_bindgen(js_namespace = WebAssembly, extends = Object)] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type Memory; /// The `WebAssembly.Memory()` constructor creates a new `Memory` object @@ -2997,8 +3071,8 @@ extern "C" { /// Notation (JSON)](https://json.org/) and converting values to JSON. It /// can't be called or constructed, and aside from its two method /// properties, it has no interesting functionality of its own. - #[derive(Clone, Debug)] #[wasm_bindgen(extends = Object)] + #[derive(Clone, Debug, PartialEq, Eq)] pub type JSON; /// The `JSON.parse()` method parses a JSON string, constructing the @@ -3056,8 +3130,8 @@ extern "C" { // JsString #[wasm_bindgen] extern "C" { - #[wasm_bindgen(js_name = String, extends = Object)] - #[derive(Clone)] + #[wasm_bindgen(js_name = String, extends = Object, is_type_of = JsValue::is_string)] + #[derive(Clone, PartialEq, Eq)] pub type JsString; /// The length property of a String object indicates the length of a string, @@ -3516,11 +3590,38 @@ impl JsString { /// If this JS value is not an instance of a string then this returns /// `None`. pub fn try_from(val: &JsValue) -> Option<&JsString> { - if val.is_string() { - Some(unsafe { mem::transmute(val) }) - } else { - None - } + val.dyn_ref() + } + + /// Returns whether this string is a valid UTF-16 string. + /// + /// This is useful for learning whether `String::from(..)` will return a + /// lossless representation of the JS string. If this string contains + /// unpaired surrogates then `String::from` will succeed but it will be a + /// lossy representation of the JS string because unpaired surrogates will + /// become replacement characters. + /// + /// If this function returns `false` then to get a lossless representation + /// of the string you'll need to manually use the `iter` method (or the + /// `char_code_at` accessor) to view the raw character codes. + /// + /// For more information, see the documentation on [JS strings vs Rust + /// strings][docs] + /// + /// [docs]: https://rustwasm.github.io/docs/wasm-bindgen/reference/types/str.html + pub fn is_valid_utf16(&self) -> bool { + std::char::decode_utf16(self.iter()).all(|i| i.is_ok()) + } + + /// Returns an iterator over the `u16` character codes that make up this JS + /// string. + /// + /// This method will call `char_code_at` for each code in this JS string, + /// returning an iterator of the codes in sequence. + pub fn iter<'a>( + &'a self, + ) -> impl ExactSizeIterator + DoubleEndedIterator + 'a { + (0..self.length()).map(move |i| self.char_code_at(i) as u16) } } @@ -3550,9 +3651,7 @@ impl<'a> PartialEq<&'a String> for JsString { impl<'a> From<&'a str> for JsString { fn from(s: &'a str) -> Self { - JsString { - obj: JsValue::from_str(s), - } + JsString::unchecked_from_js(JsValue::from_str(s)) } } @@ -3583,6 +3682,7 @@ impl fmt::Debug for JsString { // Symbol #[wasm_bindgen] extern "C" { + #[wasm_bindgen(is_type_of = JsValue::is_symbol)] #[derive(Clone, Debug)] pub type Symbol; diff --git a/crates/js-sys/tests/wasm/JsString.rs b/crates/js-sys/tests/wasm/JsString.rs index bb4a6ac0..c7f229f1 100644 --- a/crates/js-sys/tests/wasm/JsString.rs +++ b/crates/js-sys/tests/wasm/JsString.rs @@ -541,3 +541,15 @@ fn raw() { ); assert!(JsString::raw_0(&JsValue::null().unchecked_into()).is_err()); } + +#[wasm_bindgen_test] +fn is_valid_utf16() { + assert!(JsString::from("a").is_valid_utf16()); + assert!(JsString::from("").is_valid_utf16()); + assert!(JsString::from("🥑").is_valid_utf16()); + assert!(JsString::from("Why hello there this, 🥑, is 🥑 and is 🥑").is_valid_utf16()); + + assert!(JsString::from_char_code1(0x00).is_valid_utf16()); + assert!(!JsString::from_char_code1(0xd800).is_valid_utf16()); + assert!(!JsString::from_char_code1(0xdc00).is_valid_utf16()); +} diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml index 72b5185c..83ce0c10 100644 --- a/crates/macro-support/Cargo.toml +++ b/crates/macro-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-macro-support" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support" @@ -20,5 +20,5 @@ strict-macro = [] syn = { version = '0.15.0', features = ['visit'] } quote = '0.6' proc-macro2 = "0.4.9" -wasm-bindgen-backend = { path = "../backend", version = "=0.2.40" } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.40" } +wasm-bindgen-backend = { path = "../backend", version = "=0.2.42" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.42" } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 49146eb2..330ebbf4 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -45,6 +45,7 @@ macro_rules! attrgen { (readonly, Readonly(Span)), (js_name, JsName(Span, String, Span)), (js_class, JsClass(Span, String, Span)), + (is_type_of, IsTypeOf(Span, syn::Expr)), (extends, Extends(Span, syn::Path)), (vendor_prefix, VendorPrefix(Span, Ident)), (variadic, Variadic(Span)), @@ -241,6 +242,11 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); }); + (@parser $variant:ident(Span, syn::Expr)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + (@parser $variant:ident(Span, String, Span)) => ({ input.parse::()?; let (val, span) = match input.parse::() { @@ -523,6 +529,7 @@ impl ConvertToAst for syn::ForeignItemType { .js_name() .map(|s| s.0) .map_or_else(|| self.ident.to_string(), |s| s.to_string()); + let is_type_of = attrs.is_type_of().cloned(); let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); let mut extends = Vec::new(); let mut vendor_prefixes = Vec::new(); @@ -545,6 +552,7 @@ impl ConvertToAst for syn::ForeignItemType { attrs: self.attrs, doc_comment: None, instanceof_shim: shim, + is_type_of, rust_name: self.ident, js_name, extends, @@ -904,7 +912,7 @@ fn prepare_for_impl_recursion( pound_token: Default::default(), style: syn::AttrStyle::Outer, bracket_token: Default::default(), - path: syn::Ident::new("__wasm_bindgen_class_marker", Span::call_site()).into(), + path: syn::parse_quote! { wasm_bindgen::prelude::__wasm_bindgen_class_marker }, tts: quote::quote! { (#class = #js_class) }.into(), }, ); diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index 1400256a..7a150e7f 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-macro" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro" @@ -20,5 +20,5 @@ xxx_debug_only_print_generated_code = [] strict-macro = ["wasm-bindgen-macro-support/strict-macro"] [dependencies] -wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.40" } +wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.42" } quote = "0.6" diff --git a/crates/macro/ui-tests/pub-not-copy.rs b/crates/macro/ui-tests/pub-not-copy.rs new file mode 100644 index 00000000..4c7cf0b3 --- /dev/null +++ b/crates/macro/ui-tests/pub-not-copy.rs @@ -0,0 +1,10 @@ +#![crate_type = "rlib"] + +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct A { + pub field: String, +} diff --git a/crates/macro/ui-tests/pub-not-copy.stderr b/crates/macro/ui-tests/pub-not-copy.stderr new file mode 100644 index 00000000..95b597c4 --- /dev/null +++ b/crates/macro/ui-tests/pub-not-copy.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `std::string::String: std::marker::Copy` is not satisfied + --> $DIR/pub-not-copy.rs:9:16 + | +9 | pub field: String, + | ^^^^^^ the trait `std::marker::Copy` is not implemented for `std::string::String` + | +note: required by `__wbg_get_a_field::assert_copy` + --> $DIR/pub-not-copy.rs:7:1 + | +7 | #[wasm_bindgen] + | ^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 9eb60b65..d99424bb 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-shared" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared" diff --git a/crates/test-macro/Cargo.toml b/crates/test-macro/Cargo.toml index 09135e50..fefdd391 100644 --- a/crates/test-macro/Cargo.toml +++ b/crates/test-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test-macro" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] description = "Internal testing macro for wasm-bindgen" license = "MIT/Apache-2.0" diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 3eb2c901..b60c5715 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" license = "MIT/Apache-2.0" @@ -10,11 +10,11 @@ edition = "2018" [dependencies] console_error_panic_hook = '0.1' futures = "0.1" -js-sys = { path = '../js-sys', version = '0.3.17' } +js-sys = { path = '../js-sys', version = '0.3.19' } scoped-tls = "1.0" -wasm-bindgen = { path = '../..', version = '0.2.40' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.17' } -wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.40' } +wasm-bindgen = { path = '../..', version = '0.2.42' } +wasm-bindgen-futures = { path = '../futures', version = '0.3.19' } +wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.42' } [lib] test = false diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index c2b9a410..ab0dd139 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-threads-xform" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/threads-xform" diff --git a/crates/typescript-tests/run.sh b/crates/typescript-tests/run.sh index d6b03398..95a868a5 100755 --- a/crates/typescript-tests/run.sh +++ b/crates/typescript-tests/run.sh @@ -11,6 +11,13 @@ cargo run -p wasm-bindgen-cli --bin wasm-bindgen -- \ --out-dir pkg \ --typescript +mkdir pkg/web +cargo run -p wasm-bindgen-cli --bin wasm-bindgen -- \ + ../../target/wasm32-unknown-unknown/debug/typescript_tests.wasm \ + --out-dir pkg/web \ + --target web \ + --typescript + if [ ! -d node_modules ]; then npm install fi diff --git a/crates/typescript-tests/src/web/init.ts b/crates/typescript-tests/src/web/init.ts new file mode 100644 index 00000000..623efe72 --- /dev/null +++ b/crates/typescript-tests/src/web/init.ts @@ -0,0 +1,3 @@ +import * as wbg from '../../pkg/web/typescript_tests'; + +const init: Promise = wbg.init('.'); \ No newline at end of file diff --git a/crates/typescript-tests/tsconfig.json b/crates/typescript-tests/tsconfig.json index 90892979..3032c028 100644 --- a/crates/typescript-tests/tsconfig.json +++ b/crates/typescript-tests/tsconfig.json @@ -9,6 +9,6 @@ "baseUrl": "." }, "include": [ - "src/*.ts" + "src/**/*.ts" ] } diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index 76b8d898..54d08aca 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-wasm-interpreter" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-interpreter" diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 72718f06..557f5381 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "web-sys" -version = "0.3.17" +version = "0.3.19" authors = ["The wasm-bindgen Developers"] readme = "./README.md" homepage = "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html" @@ -22,17 +22,17 @@ test = false [build-dependencies] env_logger = "0.6.0" failure = "0.1.2" -wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.40" } +wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.42" } sourcefile = "0.1" [dependencies] -wasm-bindgen = { path = "../..", version = "0.2.40" } -js-sys = { path = '../js-sys', version = '0.3.17' } +wasm-bindgen = { path = "../..", version = "0.2.42" } +js-sys = { path = '../js-sys', version = '0.3.19' } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] futures = "0.1" -wasm-bindgen-test = { path = '../test', version = '0.2.40' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.17' } +wasm-bindgen-test = { path = '../test', version = '0.2.42' } +wasm-bindgen-futures = { path = '../futures', version = '0.3.19' } # This list is generated by passing `__WASM_BINDGEN_DUMP_FEATURES=foo` when # compiling this crate which dumps the total list of features to a file called diff --git a/crates/webidl/Cargo.toml b/crates/webidl/Cargo.toml index 91ed2f0b..5f841c3d 100644 --- a/crates/webidl/Cargo.toml +++ b/crates/webidl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-webidl" -version = "0.2.40" +version = "0.2.42" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" categories = ["wasm"] @@ -19,5 +19,5 @@ log = "0.4.1" proc-macro2 = "0.4.8" quote = '0.6' syn = { version = '0.15', features = ['full'] } -wasm-bindgen-backend = { version = "=0.2.40", path = "../backend" } +wasm-bindgen-backend = { version = "=0.2.42", path = "../backend" } weedle = "0.8" diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index cdd86a58..08a85397 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -514,6 +514,7 @@ impl<'src> FirstPassRecord<'src> { attrs, doc_comment: None, instanceof_shim: format!("__widl_instanceof_{}", name), + is_type_of: None, extends: Vec::new(), vendor_prefixes: Vec::new(), }; diff --git a/examples/add/Cargo.toml b/examples/add/Cargo.toml index f1b3dff9..f435ba79 100644 --- a/examples/add/Cargo.toml +++ b/examples/add/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/canvas/Cargo.toml b/examples/canvas/Cargo.toml index 5a4dc531..113355a1 100644 --- a/examples/canvas/Cargo.toml +++ b/examples/canvas/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.17" -wasm-bindgen = "0.2.40" +js-sys = "0.3.19" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/char/Cargo.toml b/examples/char/Cargo.toml index 2f708ec2..16e67ee7 100644 --- a/examples/char/Cargo.toml +++ b/examples/char/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/closures/Cargo.toml b/examples/closures/Cargo.toml index 8ce7cb01..d68043aa 100644 --- a/examples/closures/Cargo.toml +++ b/examples/closures/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" -js-sys = "0.3.17" +wasm-bindgen = "0.2.42" +js-sys = "0.3.19" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/console_log/Cargo.toml b/examples/console_log/Cargo.toml index b4696e5c..d0645982 100644 --- a/examples/console_log/Cargo.toml +++ b/examples/console_log/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" -web-sys = { version = "0.3.17", features = ['console'] } +wasm-bindgen = "0.2.42" +web-sys = { version = "0.3.19", features = ['console'] } diff --git a/examples/dom/Cargo.toml b/examples/dom/Cargo.toml index 6b584800..a9f57cd3 100644 --- a/examples/dom/Cargo.toml +++ b/examples/dom/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/duck-typed-interfaces/Cargo.toml b/examples/duck-typed-interfaces/Cargo.toml index 82863a93..ac9352f4 100644 --- a/examples/duck-typed-interfaces/Cargo.toml +++ b/examples/duck-typed-interfaces/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/fetch/Cargo.toml b/examples/fetch/Cargo.toml index eee40154..b71c02b5 100644 --- a/examples/fetch/Cargo.toml +++ b/examples/fetch/Cargo.toml @@ -9,9 +9,9 @@ crate-type = ["cdylib"] [dependencies] futures = "0.1.20" -wasm-bindgen = { version = "0.2.40", features = ["serde-serialize"] } -js-sys = "0.3.17" -wasm-bindgen-futures = "0.3.17" +wasm-bindgen = { version = "0.2.42", features = ["serde-serialize"] } +js-sys = "0.3.19" +wasm-bindgen-futures = "0.3.19" serde = { version = "1.0.80", features = ["derive"] } serde_derive = "^1.0.59" diff --git a/examples/guide-supported-types-examples/Cargo.toml b/examples/guide-supported-types-examples/Cargo.toml index 4426b45b..cd4f79d9 100644 --- a/examples/guide-supported-types-examples/Cargo.toml +++ b/examples/guide-supported-types-examples/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 32dfc572..e0de5b6f 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/import_js/crate/Cargo.toml b/examples/import_js/crate/Cargo.toml index 4cc846e0..033270d1 100644 --- a/examples/import_js/crate/Cargo.toml +++ b/examples/import_js/crate/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/julia_set/Cargo.toml b/examples/julia_set/Cargo.toml index a532ba89..e6cc0955 100644 --- a/examples/julia_set/Cargo.toml +++ b/examples/julia_set/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/paint/Cargo.toml b/examples/paint/Cargo.toml index 61b425b0..c0c3b8b9 100644 --- a/examples/paint/Cargo.toml +++ b/examples/paint/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.17" -wasm-bindgen = "0.2.40" +js-sys = "0.3.19" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/performance/Cargo.toml b/examples/performance/Cargo.toml index d933122f..144f1837 100644 --- a/examples/performance/Cargo.toml +++ b/examples/performance/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" humantime = "1" [dependencies.web-sys] diff --git a/examples/raytrace-parallel/Cargo.toml b/examples/raytrace-parallel/Cargo.toml index 26a42fe4..481ef4bc 100644 --- a/examples/raytrace-parallel/Cargo.toml +++ b/examples/raytrace-parallel/Cargo.toml @@ -10,10 +10,10 @@ crate-type = ["cdylib"] [dependencies] console_error_panic_hook = "0.1" futures = "0.1" -js-sys = "0.3.17" +js-sys = "0.3.19" raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' } -wasm-bindgen = { version = "0.2.40", features = ['serde-serialize'] } -wasm-bindgen-futures = "0.3.17" +wasm-bindgen = { version = "0.2.42", features = ['serde-serialize'] } +wasm-bindgen-futures = "0.3.19" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/request-animation-frame/Cargo.toml b/examples/request-animation-frame/Cargo.toml index 5cd41996..515266b0 100644 --- a/examples/request-animation-frame/Cargo.toml +++ b/examples/request-animation-frame/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml index 32ff302e..32e8c566 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/todomvc/Cargo.toml @@ -11,8 +11,8 @@ crate-type = ["cdylib"] askama = "0.7.2" [dependencies] -js-sys = "0.3.17" -wasm-bindgen = "0.2.40" +js-sys = "0.3.19" +wasm-bindgen = "0.2.42" askama = "0.7.2" console_error_panic_hook = "0.1.5" diff --git a/examples/wasm-in-wasm/Cargo.toml b/examples/wasm-in-wasm/Cargo.toml index 817b3e90..88b2e0d6 100644 --- a/examples/wasm-in-wasm/Cargo.toml +++ b/examples/wasm-in-wasm/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" -js-sys = "0.3.17" +wasm-bindgen = "0.2.42" +js-sys = "0.3.19" diff --git a/examples/wasm2js/Cargo.toml b/examples/wasm2js/Cargo.toml index 5c100132..8cb6d32d 100644 --- a/examples/wasm2js/Cargo.toml +++ b/examples/wasm2js/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" diff --git a/examples/webaudio/Cargo.toml b/examples/webaudio/Cargo.toml index 9e6a03b6..9eb94ba7 100644 --- a/examples/webaudio/Cargo.toml +++ b/examples/webaudio/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/webgl/Cargo.toml b/examples/webgl/Cargo.toml index a0031931..de49b258 100644 --- a/examples/webgl/Cargo.toml +++ b/examples/webgl/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.17" -wasm-bindgen = "0.2.40" +js-sys = "0.3.19" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/without-a-bundler/Cargo.toml b/examples/without-a-bundler/Cargo.toml index 0196551f..3d009239 100644 --- a/examples/without-a-bundler/Cargo.toml +++ b/examples/without-a-bundler/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.40" +wasm-bindgen = "0.2.42" [dependencies.web-sys] version = "0.3.4" diff --git a/guide/src/contributing/index.md b/guide/src/contributing/index.md index dbd469ea..00a195ac 100644 --- a/guide/src/contributing/index.md +++ b/guide/src/contributing/index.md @@ -20,3 +20,7 @@ development. You may want to browse the [unpublished guide documentation] for as that is when WebAssembly support was introduced. [Install Node]. [Install Node]: https://nodejs.org/en/ + +## Code Formatting + +Although formatting rules are not mandatory, it is encouraged to run `cargo run` (`rustfmt`) with its default rules within a PR to maintain a more organized code base. If necessary, a PR with a single commit that formats the entire project is also welcome. \ No newline at end of file diff --git a/guide/src/reference/types/str.md b/guide/src/reference/types/str.md index 999bbc18..5de5f166 100644 --- a/guide/src/reference/types/str.md +++ b/guide/src/reference/types/str.md @@ -20,3 +20,30 @@ with handles to JavaScript string values, use the `js_sys::JsString` type. ```js {{#include ../../../../examples/guide-supported-types-examples/str.js}} ``` + +## UTF-16 vs UTF-8 + +Strings in JavaScript are encoded as UTF-16, but with one major exception: they +can contain unpaired surrogates. For some Unicode characters UTF-16 uses two +16-byte values. These are called "surrogate pairs" because they always come in +pairs. In JavaScript, it is possible for these surrogate pairs to be missing the +other half, creating an "unpaired surrogate". + +When passing a string from JavaScript to Rust, it uses the `TextEncoder` API to +convert from UTF-16 to UTF-8. This is normally perfectly fine... unless there +are unpaired surrogates. In that case it will replace the unpaired surrogates +with U+FFFD (�, the replacement character). That means the string in Rust is +now different from the string in JavaScript! + +If you want to guarantee that the Rust string is the same as the JavaScript +string, you should instead use `js_sys::JsString` (which keeps the string in +JavaScript and doesn't copy it into Rust). + +If you want to access the raw value of a JS string, you can use `JsString::iter`, +which returns an `Iterator`. This perfectly preserves everything +(including unpaired surrogates), but it does not do any encoding (so you +have to do that yourself!). + +If you simply want to ignore strings which contain unpaired surrogates, you can +use `JsString::is_valid_utf16` to test whether the string contains unpaired +surrogates or not. diff --git a/guide/src/reference/types/string.md b/guide/src/reference/types/string.md index 568e20b6..3b846704 100644 --- a/guide/src/reference/types/string.md +++ b/guide/src/reference/types/string.md @@ -8,6 +8,9 @@ Copies the string's contents back and forth between the JavaScript garbage-collected heap and the Wasm linear memory with `TextDecoder` and `TextEncoder` +> **Note**: Be sure to check out the [documentation for `str`](str.html) to +> learn about some caveats when working with strings between JS and Rust. + ## Example Rust Usage ```rust diff --git a/src/cast.rs b/src/cast.rs index fa690e3d..f8f0694f 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -16,28 +16,35 @@ pub trait JsCast where Self: AsRef + Into, { - /// Test whether this JS value is an instance of the type `T`. + /// Test whether this JS value has a type `T`. /// - /// This method performs a dynamic check (at runtime) using the JS - /// `instanceof` operator. This method returns `self instanceof T`. - fn is_instance_of(&self) -> bool + /// This method will dynamically check to see if this JS object can be + /// casted to the JS object of type `T`. Usually this uses the `instanceof` + /// operator. This also works with primitive types like + /// booleans/strings/numbers as well as cross-realm object like `Array` + /// which can originate from other iframes. + /// + /// In general this is intended to be a more robust version of + /// `is_instance_of`, but if you want strictly the `instanceof` operator + /// it's recommended to use that instead. + fn has_type(&self) -> bool where T: JsCast, { - T::instanceof(self.as_ref()) + T::is_type_of(self.as_ref()) } /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `Err(self)` if `self.is_instance_of::()` + /// This method will return `Err(self)` if `self.has_type::()` /// returns `false`, and otherwise it will return `Ok(T)` manufactured with - /// an unchecked cast (verified correct via the `instanceof` operation). + /// an unchecked cast (verified correct via the `has_type` operation). fn dyn_into(self) -> Result where T: JsCast, { - if self.is_instance_of::() { + if self.has_type::() { Ok(self.unchecked_into()) } else { Err(self) @@ -47,14 +54,14 @@ where /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `None` if `self.is_instance_of::()` + /// This method will return `None` if `self.has_type::()` /// returns `false`, and otherwise it will return `Some(&T)` manufactured - /// with an unchecked cast (verified correct via the `instanceof` operation). + /// with an unchecked cast (verified correct via the `has_type` operation). fn dyn_ref(&self) -> Option<&T> where T: JsCast, { - if self.is_instance_of::() { + if self.has_type::() { Some(self.unchecked_ref()) } else { None @@ -93,13 +100,43 @@ where T::unchecked_from_js_ref(self.as_ref()) } + /// Test whether this JS value is an instance of the type `T`. + /// + /// This method performs a dynamic check (at runtime) using the JS + /// `instanceof` operator. This method returns `self instanceof T`. + /// + /// Note that `instanceof` does not always work with primitive values or + /// across different realms (e.g. iframes). If you're not sure whether you + /// specifically need only `instanceof` it's recommended to use `has_type` + /// instead. + fn is_instance_of(&self) -> bool + where + T: JsCast, + { + T::instanceof(self.as_ref()) + } + /// Performs a dynamic `instanceof` check to see whether the `JsValue` /// provided is an instance of this type. /// /// This is intended to be an internal implementation detail, you likely - /// won't need to call this. + /// won't need to call this. It's generally called through the + /// `is_instance_of` method instead. fn instanceof(val: &JsValue) -> bool; + /// Performs a dynamic check to see whether the `JsValue` provided + /// is a value of this type. + /// + /// Unlike `instanceof`, this can be specialised to use a custom check by + /// adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the + /// type import declaration. + /// + /// Other than that, this is intended to be an internal implementation + /// detail of `has_type` and you likely won't need to call this. + fn is_type_of(val: &JsValue) -> bool { + Self::instanceof(val) + } + /// Performs a zero-cost unchecked conversion from a `JsValue` into an /// instance of `Self` /// diff --git a/src/closure.rs b/src/closure.rs index 885b5975..5067c799 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -519,7 +519,7 @@ where /// This trait is not stable and it's not recommended to use this in bounds or /// implement yourself. #[doc(hidden)] -pub unsafe trait WasmClosure: 'static { +pub unsafe trait WasmClosure { fn describe(); } @@ -541,7 +541,7 @@ macro_rules! doit { ($( ($($var:ident)*) )*) => ($( - unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R + unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R + 'static where $($var: FromWasmAbi + 'static,)* R: ReturnWasmAbi + 'static, { @@ -587,7 +587,7 @@ macro_rules! doit { } } - unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R + unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R + 'static where $($var: FromWasmAbi + 'static,)* R: ReturnWasmAbi + 'static, { @@ -696,3 +696,148 @@ doit! { (A B C D E F) (A B C D E F G) } + +// Copy the above impls down here for where there's only one argument and it's a +// reference. We could add more impls for more kinds of references, but it +// becomes a combinatorial explosion quickly. Let's see how far we can get with +// just this one! Maybe someone else can figure out voodoo so we don't have to +// duplicate. + +unsafe impl WasmClosure for Fn(&A) -> R + where A: RefFromWasmAbi, + R: ReturnWasmAbi + 'static, +{ + fn describe() { + #[allow(non_snake_case)] + unsafe extern "C" fn invoke( + a: usize, + b: usize, + arg: ::Abi, + ) -> ::Abi { + if a == 0 { + throw_str("closure invoked recursively or destroyed already"); + } + // Make sure all stack variables are converted before we + // convert `ret` as it may throw (for `Result`, for + // example) + let ret = { + let f: *const Fn(&A) -> R = + FatPtr { fields: (a, b) }.ptr; + let mut _stack = GlobalStack::new(); + let arg = ::ref_from_abi(arg, &mut _stack); + (*f)(&*arg) + }; + ret.return_abi(&mut GlobalStack::new()) + } + + inform(invoke:: as u32); + + unsafe extern fn destroy( + a: usize, + b: usize, + ) { + debug_assert!(a != 0, "should never destroy a Fn whose pointer is 0"); + drop(Box::from_raw(FatPtr:: R> { + fields: (a, b) + }.ptr)); + } + inform(destroy:: as u32); + + <&Self>::describe(); + } +} + +unsafe impl WasmClosure for FnMut(&A) -> R + where A: RefFromWasmAbi, + R: ReturnWasmAbi + 'static, +{ + fn describe() { + #[allow(non_snake_case)] + unsafe extern "C" fn invoke( + a: usize, + b: usize, + arg: ::Abi, + ) -> ::Abi { + if a == 0 { + throw_str("closure invoked recursively or destroyed already"); + } + // Make sure all stack variables are converted before we + // convert `ret` as it may throw (for `Result`, for + // example) + let ret = { + let f: *const FnMut(&A) -> R = + FatPtr { fields: (a, b) }.ptr; + let f = f as *mut FnMut(&A) -> R; + let mut _stack = GlobalStack::new(); + let arg = ::ref_from_abi(arg, &mut _stack); + (*f)(&*arg) + }; + ret.return_abi(&mut GlobalStack::new()) + } + + inform(invoke:: as u32); + + unsafe extern fn destroy( + a: usize, + b: usize, + ) { + debug_assert!(a != 0, "should never destroy a FnMut whose pointer is 0"); + drop(Box::from_raw(FatPtr:: R> { + fields: (a, b) + }.ptr)); + } + inform(destroy:: as u32); + + <&mut Self>::describe(); + } +} + +#[allow(non_snake_case)] +impl WasmClosureFnOnce<(&A,), R> for T + where T: 'static + FnOnce(&A) -> R, + A: RefFromWasmAbi + 'static, + R: ReturnWasmAbi + 'static +{ + type FnMut = FnMut(&A) -> R; + + fn into_fn_mut(self) -> Box { + let mut me = Some(self); + Box::new(move |arg| { + let me = me.take().expect_throw("FnOnce called more than once"); + me(arg) + }) + } + + fn into_js_function(self) -> JsValue { + use std::rc::Rc; + use crate::__rt::WasmRefCell; + + let mut me = Some(self); + + let rc1 = Rc::new(WasmRefCell::new(None)); + let rc2 = rc1.clone(); + + let closure = Closure::wrap(Box::new(move |arg: &A| { + // Invoke ourself and get the result. + let me = me.take().expect_throw("FnOnce called more than once"); + let result = me(arg); + + // And then drop the `Rc` holding this function's `Closure` + // alive. + debug_assert_eq!(Rc::strong_count(&rc2), 1); + let option_closure = rc2.borrow_mut().take(); + debug_assert!(option_closure.is_some()); + drop(option_closure); + + result + }) as Box R>); + + let js_val = closure.as_ref().clone(); + + *rc1.borrow_mut() = Some(closure); + debug_assert_eq!(Rc::strong_count(&rc1), 2); + drop(rc1); + + js_val + } +} diff --git a/src/convert/closures.rs b/src/convert/closures.rs index 652e11a7..66174924 100644 --- a/src/convert/closures.rs +++ b/src/convert/closures.rs @@ -2,6 +2,7 @@ use core::mem; use crate::convert::slices::WasmSlice; use crate::convert::{FromWasmAbi, GlobalStack, IntoWasmAbi, ReturnWasmAbi, Stack}; +use crate::convert::RefFromWasmAbi; use crate::describe::{inform, WasmDescribe, FUNCTION}; use crate::throw_str; @@ -117,3 +118,97 @@ stack_closures! { (6 invoke6 invoke6_mut A B C D E F) (7 invoke7 invoke7_mut A B C D E F G) } + +impl<'a, 'b, A, R> IntoWasmAbi for &'a (Fn(&A) -> R + 'b) + where A: RefFromWasmAbi, + R: ReturnWasmAbi +{ + type Abi = WasmSlice; + + fn into_abi(self, _extra: &mut Stack) -> WasmSlice { + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + WasmSlice { ptr: a as u32, len: b as u32 } + } + } +} + +#[allow(non_snake_case)] +unsafe extern "C" fn invoke1_ref( + a: usize, + b: usize, + arg: ::Abi, +) -> ::Abi { + if a == 0 { + throw_str("closure invoked recursively or destroyed already"); + } + // Scope all local variables before we call `return_abi` to + // ensure they're all destroyed as `return_abi` may throw + let ret = { + let f: &Fn(&A) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + let arg = ::ref_from_abi(arg, &mut _stack); + f(&*arg) + }; + ret.return_abi(&mut GlobalStack::new()) +} + +impl<'a, A, R> WasmDescribe for Fn(&A) -> R + 'a + where A: RefFromWasmAbi, + R: ReturnWasmAbi, +{ + fn describe() { + inform(FUNCTION); + inform(invoke1_ref:: as u32); + inform(1); + <&A as WasmDescribe>::describe(); + ::describe(); + } +} + +impl<'a, 'b, A, R> IntoWasmAbi for &'a mut (FnMut(&A) -> R + 'b) + where A: RefFromWasmAbi, + R: ReturnWasmAbi +{ + type Abi = WasmSlice; + + fn into_abi(self, _extra: &mut Stack) -> WasmSlice { + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + WasmSlice { ptr: a as u32, len: b as u32 } + } + } +} + +#[allow(non_snake_case)] +unsafe extern "C" fn invoke1_mut_ref( + a: usize, + b: usize, + arg: ::Abi, +) -> ::Abi { + if a == 0 { + throw_str("closure invoked recursively or destroyed already"); + } + // Scope all local variables before we call `return_abi` to + // ensure they're all destroyed as `return_abi` may throw + let ret = { + let f: &mut FnMut(&A) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + let arg = ::ref_from_abi(arg, &mut _stack); + f(&*arg) + }; + ret.return_abi(&mut GlobalStack::new()) +} + +impl<'a, A, R> WasmDescribe for FnMut(&A) -> R + 'a + where A: RefFromWasmAbi, + R: ReturnWasmAbi +{ + fn describe() { + inform(FUNCTION); + inform(invoke1_mut_ref:: as u32); + inform(1); + <&A as WasmDescribe>::describe(); + ::describe(); + } +} diff --git a/src/convert/slices.rs b/src/convert/slices.rs index d99fbbbf..a3cb6ec9 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -123,7 +123,7 @@ macro_rules! vectors { } vectors! { - u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 + u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } if_std! { diff --git a/src/lib.rs b/src/lib.rs index 0cd5034b..5aad3f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,6 +260,16 @@ impl JsValue { /// /// If this JS value is not an instance of a string or if it's not valid /// utf-8 then this returns `None`. + /// + /// # UTF-16 vs UTF-8 + /// + /// JavaScript strings in general are encoded as UTF-16, but Rust strings + /// are encoded as UTF-8. This can cause the Rust string to look a bit + /// different than the JS string sometimes. For more details see the + /// [documentation about the `str` type][caveats] which contains a few + /// caveats about the encodings. + /// + /// [caveats]: https://rustwasm.github.io/docs/wasm-bindgen/reference/types/str.html #[cfg(feature = "std")] pub fn as_string(&self) -> Option { unsafe { @@ -507,6 +517,7 @@ externs! { fn __wbindgen_memory() -> u32; fn __wbindgen_module() -> u32; + fn __wbindgen_function_table() -> u32; } } @@ -726,6 +737,12 @@ pub fn memory() -> JsValue { unsafe { JsValue::_new(__wbindgen_memory()) } } +/// Returns a handle to this wasm instance's `WebAssembly.Table` which is the +/// indirect function table used by Rust +pub fn function_table() -> JsValue { + unsafe { JsValue::_new(__wbindgen_function_table()) } +} + #[doc(hidden)] pub mod __rt { use core::cell::{Cell, UnsafeCell}; diff --git a/tests/wasm/api.js b/tests/wasm/api.js index 4c2dec0e..be59f28d 100644 --- a/tests/wasm/api.js +++ b/tests/wasm/api.js @@ -55,3 +55,9 @@ exports.debug_values = () => ([ () => (null), new Set(), ]); + +exports.assert_function_table = (x, i) => { + const rawWasm = require('wasm-bindgen-test_bg.js'); + assert.ok(x instanceof WebAssembly.Table); + assert.strictEqual(x.get(i), rawWasm.function_table_lookup); +}; diff --git a/tests/wasm/api.rs b/tests/wasm/api.rs index 92b7b21d..84b626e6 100644 --- a/tests/wasm/api.rs +++ b/tests/wasm/api.rs @@ -9,6 +9,7 @@ extern "C" { fn js_eq_works(); fn assert_null(v: JsValue); fn debug_values() -> JsValue; + fn assert_function_table(a: JsValue, b: usize); } #[wasm_bindgen_test] @@ -171,3 +172,14 @@ fn debug_output() { assert_eq!(format!("{:?}", test.unwrap()), expected); } } + +#[wasm_bindgen_test] +fn function_table_is() { + assert_function_table( + wasm_bindgen::function_table(), + function_table_lookup as usize, + ); +} + +#[no_mangle] +pub extern "C" fn function_table_lookup() {} diff --git a/tests/wasm/classes.rs b/tests/wasm/classes.rs index 3e76b95a..660d7063 100644 --- a/tests/wasm/classes.rs +++ b/tests/wasm/classes.rs @@ -461,3 +461,21 @@ pub fn option_class_assert_none(x: Option) { pub fn option_class_assert_some(x: Option) { assert_eq!(x.unwrap().0, 3); } + +mod works_in_module { + use wasm_bindgen::prelude::wasm_bindgen; + + #[wasm_bindgen] + pub struct WorksInModule(u32); + + #[wasm_bindgen] + impl WorksInModule { + #[wasm_bindgen(constructor)] + pub fn new() -> WorksInModule { + WorksInModule(1) + } + + pub fn foo(&self) { + } + } +} diff --git a/tests/wasm/closures.js b/tests/wasm/closures.js index 03d5d1f0..7acaa383 100644 --- a/tests/wasm/closures.js +++ b/tests/wasm/closures.js @@ -113,3 +113,9 @@ exports.calling_it_throws = a => { }; exports.call_val = f => f(); + +exports.pass_reference_first_arg_twice = (a, b, c) => { + b(a); + c(a); + a.free(); +}; diff --git a/tests/wasm/closures.rs b/tests/wasm/closures.rs index 4ce047f0..1e5af49e 100755 --- a/tests/wasm/closures.rs +++ b/tests/wasm/closures.rs @@ -90,6 +90,18 @@ extern "C" { #[wasm_bindgen(js_name = calling_it_throws)] fn call_val_throws(f: &JsValue) -> bool; + + fn pass_reference_first_arg_twice( + a: RefFirstArgument, + b: &Closure, + c: &Closure, + ); + #[wasm_bindgen(js_name = pass_reference_first_arg_twice)] + fn pass_reference_first_arg_twice2( + a: RefFirstArgument, + b: &mut FnMut(&RefFirstArgument), + c: &mut FnMut(&RefFirstArgument), + ); } #[wasm_bindgen_test] @@ -439,3 +451,74 @@ fn test_closure_returner() { Ok(o) } } + +#[wasm_bindgen] +pub struct RefFirstArgument { + contents: u32, +} + +#[wasm_bindgen_test] +fn reference_as_first_argument_builds_at_all() { + #[wasm_bindgen] + extern "C" { + fn ref_first_arg1(a: &Fn(&JsValue)); + fn ref_first_arg2(a: &mut FnMut(&JsValue)); + fn ref_first_arg3(a: &Closure); + fn ref_first_arg4(a: &Closure); + fn ref_first_custom1(a: &Fn(&RefFirstArgument)); + fn ref_first_custom2(a: &mut FnMut(&RefFirstArgument)); + fn ref_first_custom3(a: &Closure); + fn ref_first_custom4(a: &Closure); + } + + Closure::wrap(Box::new(|_: &JsValue| ()) as Box); + Closure::wrap(Box::new(|_: &JsValue| ()) as Box); + Closure::once(|_: &JsValue| ()); + Closure::once_into_js(|_: &JsValue| ()); + Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box); + Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box); + Closure::once(|_: &RefFirstArgument| ()); + Closure::once_into_js(|_: &RefFirstArgument| ()); +} + +#[wasm_bindgen_test] +fn reference_as_first_argument_works() { + let a = Rc::new(Cell::new(0)); + let b = { + let a = a.clone(); + Closure::once(move |x: &RefFirstArgument| { + assert_eq!(a.get(), 0); + assert_eq!(x.contents, 3); + a.set(a.get() + 1); + }) + }; + let c = { + let a = a.clone(); + Closure::once(move |x: &RefFirstArgument| { + assert_eq!(a.get(), 1); + assert_eq!(x.contents, 3); + a.set(a.get() + 1); + }) + }; + pass_reference_first_arg_twice(RefFirstArgument { contents: 3 }, &b, &c); + assert_eq!(a.get(), 2); +} + +#[wasm_bindgen_test] +fn reference_as_first_argument_works2() { + let a = Cell::new(0); + pass_reference_first_arg_twice2( + RefFirstArgument { contents: 3 }, + &mut |x: &RefFirstArgument| { + assert_eq!(a.get(), 0); + assert_eq!(x.contents, 3); + a.set(a.get() + 1); + }, + &mut |x: &RefFirstArgument| { + assert_eq!(a.get(), 1); + assert_eq!(x.contents, 3); + a.set(a.get() + 1); + }, + ); + assert_eq!(a.get(), 2); +} diff --git a/tests/wasm/slice.js b/tests/wasm/slice.js index 7ec7ee8f..373e3859 100644 --- a/tests/wasm/slice.js +++ b/tests/wasm/slice.js @@ -24,10 +24,12 @@ exports.js_export = () => { i32[0] = 1; i32[1] = 2; assert.deepStrictEqual(wasm.export_i32(i32), i32); + assert.deepStrictEqual(wasm.export_isize(i32), i32); const u32 = new Uint32Array(2); u32[0] = 1; u32[1] = 2; assert.deepStrictEqual(wasm.export_u32(u32), u32); + assert.deepStrictEqual(wasm.export_usize(u32), u32); const f32 = new Float32Array(2); f32[0] = 1; @@ -73,6 +75,7 @@ exports.import_js_i32 = a => { assert.strictEqual(a[1], 2); return a; }; +exports.import_js_isize = exports.import_js_i32; exports.import_js_u32 = a => { assert.strictEqual(a.length, 2); @@ -80,6 +83,7 @@ exports.import_js_u32 = a => { assert.strictEqual(a[1], 2); return a; }; +exports.import_js_usize = exports.import_js_u32; exports.import_js_f32 = a => { assert.strictEqual(a.length, 2); @@ -118,10 +122,12 @@ exports.js_import = () => { i32[0] = 1; i32[1] = 2; assert.deepStrictEqual(wasm.import_rust_i32(i32), i32); + assert.deepStrictEqual(wasm.import_rust_isize(i32), i32); const u32 = new Uint32Array(2); u32[0] = 1; u32[1] = 2; assert.deepStrictEqual(wasm.import_rust_u32(u32), u32); + assert.deepStrictEqual(wasm.import_rust_usize(u32), u32); const f32 = new Float32Array(2); f32[0] = 1; @@ -140,6 +146,8 @@ exports.js_pass_array = () => { wasm.pass_array_rust_u16([1, 2]); wasm.pass_array_rust_i32([1, 2]); wasm.pass_array_rust_u32([1, 2]); + wasm.pass_array_rust_isize([1, 2]); + wasm.pass_array_rust_usize([1, 2]); wasm.pass_array_rust_f32([1, 2]); wasm.pass_array_rust_f64([1, 2]); }; @@ -158,6 +166,8 @@ exports.import_mut_js_i16 = import_mut_foo; exports.import_mut_js_u16 = import_mut_foo; exports.import_mut_js_i32 = import_mut_foo; exports.import_mut_js_u32 = import_mut_foo; +exports.import_mut_js_isize = import_mut_foo; +exports.import_mut_js_usize = import_mut_foo; exports.import_mut_js_f32 = import_mut_foo; exports.import_mut_js_f64 = import_mut_foo; @@ -182,6 +192,8 @@ exports.js_export_mut = () => { export_mut_run(new Uint16Array(3), wasm.export_mut_u16); export_mut_run(new Int32Array(3), wasm.export_mut_i32); export_mut_run(new Uint32Array(3), wasm.export_mut_u32); + export_mut_run(new Int32Array(3), wasm.export_mut_isize); + export_mut_run(new Uint32Array(3), wasm.export_mut_usize); export_mut_run(new Float32Array(3), wasm.export_mut_f32); export_mut_run(new Float64Array(3), wasm.export_mut_f64); }; diff --git a/tests/wasm/slice.rs b/tests/wasm/slice.rs index e14ed187..8e299c0f 100644 --- a/tests/wasm/slice.rs +++ b/tests/wasm/slice.rs @@ -40,6 +40,8 @@ export_macro! { (u16, export_u16) (i32, export_i32) (u32, export_u32) + (isize, export_isize) + (usize, export_usize) (f32, export_f32) (f64, export_f64) } @@ -73,6 +75,8 @@ import_macro! { (import_rust_u16, import_js_u16, u16) (import_rust_i32, import_js_i32, i32) (import_rust_u32, import_js_u32, u32) + (import_rust_isize, import_js_isize, isize) + (import_rust_usize, import_js_usize, usize) (import_rust_f32, import_js_f32, f32) (import_rust_f64, import_js_f64, f64) } @@ -100,6 +104,8 @@ pass_array_marco! { (pass_array_rust_u16, u16) (pass_array_rust_i32, i32) (pass_array_rust_u32, u32) + (pass_array_rust_isize, isize) + (pass_array_rust_usize, usize) (pass_array_rust_f32, f32) (pass_array_rust_f64, f64) } @@ -169,6 +175,8 @@ export_mut_macro! { (u16, export_mut_u16) (i32, export_mut_i32) (u32, export_mut_u32) + (isize, export_mut_isize) + (usize, export_mut_usize) (f32, export_mut_f32) (f64, export_mut_f64) }