From 2e821f01e9c8f84b2f8abbbc68ed0ccef238f186 Mon Sep 17 00:00:00 2001 From: Oliver T Date: Thu, 17 Aug 2023 15:50:21 -0400 Subject: [PATCH 1/4] Remove class wrap for constructors in Rust exports After #1594 constructors of Rust exported structs started using class wrapping when generating JS shims. Wrapping erases prototype information from the object instance in JS and as a result it is not possible to override methods (via inheritance) of the generated class. Additionally, some checks to ensure constructors always return an instance of `Self` were lost. This PR fixes the above two issues by embedding the kind of export into the `CallExport` instruction and monitoring for constructor exports calls during the export adapter creation. Once a constructor export call is detected and validated a new instruction `SelfFromI32` is pushed into the stack that avoids class wrapping. Fixes #3213 Signed-off-by: Oliver T --- crates/cli-support/src/externref.rs | 4 +- crates/cli-support/src/js/binding.rs | 10 ++- crates/cli-support/src/js/mod.rs | 2 +- crates/cli-support/src/multivalue.rs | 2 +- crates/cli-support/src/wit/mod.rs | 52 ++++++++++------ crates/cli-support/src/wit/nonstandard.rs | 2 +- crates/cli-support/src/wit/standard.rs | 6 +- crates/cli/tests/reference/builder.d.ts | 11 ++++ crates/cli/tests/reference/builder.js | 61 +++++++++++++++++++ crates/cli/tests/reference/builder.rs | 11 ++++ crates/cli/tests/reference/builder.wat | 10 +++ crates/cli/tests/reference/constructor.d.ts | 10 +++ crates/cli/tests/reference/constructor.js | 53 ++++++++++++++++ crates/cli/tests/reference/constructor.rs | 13 ++++ crates/cli/tests/reference/constructor.wat | 10 +++ crates/cli/tests/wasm-bindgen/main.rs | 24 ++++++++ .../attributes/on-rust-exports/constructor.md | 38 ++++++++++++ 17 files changed, 293 insertions(+), 26 deletions(-) create mode 100644 crates/cli/tests/reference/builder.d.ts create mode 100644 crates/cli/tests/reference/builder.js create mode 100644 crates/cli/tests/reference/builder.rs create mode 100644 crates/cli/tests/reference/builder.wat create mode 100644 crates/cli/tests/reference/constructor.d.ts create mode 100644 crates/cli/tests/reference/constructor.js create mode 100644 crates/cli/tests/reference/constructor.rs create mode 100644 crates/cli/tests/reference/constructor.wat diff --git a/crates/cli-support/src/externref.rs b/crates/cli-support/src/externref.rs index 2baa66286e9..d147b9545d2 100644 --- a/crates/cli-support/src/externref.rs +++ b/crates/cli-support/src/externref.rs @@ -137,7 +137,7 @@ fn find_call_export(instrs: &[InstructionData]) -> Option { .iter() .enumerate() .filter_map(|(i, instr)| match instr.instr { - Instruction::CallExport(e) => Some(Export::Export(e)), + Instruction::CallExport(e, _) => Some(Export::Export(e)), Instruction::CallTableElement(e) => Some(Export::TableElement { idx: e, call_idx: i, @@ -267,7 +267,7 @@ fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec break, + Instruction::CallExport(_, _) | Instruction::CallTableElement(_) => break, Instruction::I32FromExternrefOwned => { args.pop(); args.push(Some(true)); diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 83a04f92fb6..16b8342cf35 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -545,7 +545,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> } Instruction::CallCore(_) - | Instruction::CallExport(_) + | Instruction::CallExport(_, _) | Instruction::CallAdapter(_) | Instruction::CallTableElement(_) | Instruction::DeferFree { .. } => { @@ -986,6 +986,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> js.push(format!("String.fromCodePoint({})", val)); } + Instruction::SelfFromI32 => { + let val = js.pop(); + js.prelude(&format!("this.__wbg_ptr = {} >>> 0;", val)); + js.push(format!("this")); + } + Instruction::RustFromI32 { class } => { js.cx.require_class_wrap(class); let val = js.pop(); @@ -1203,7 +1209,7 @@ impl Invocation { defer: true, }, - CallExport(e) => match module.exports.get(*e).item { + CallExport(e, _) => match module.exports.get(*e).item { walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, _ => panic!("can only call exported function"), }, diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 56847262544..dc638c67795 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2749,7 +2749,7 @@ impl<'a> Context<'a> { call = Some(id); } } - Instruction::CallExport(_) + Instruction::CallExport(_, _) | Instruction::CallTableElement(_) | Instruction::CallCore(_) => return Ok(false), _ => {} diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs index 61a20878e63..185fd6ee1e1 100644 --- a/crates/cli-support/src/multivalue.rs +++ b/crates/cli-support/src/multivalue.rs @@ -81,7 +81,7 @@ fn extract_xform<'a>( .iter_mut() .find_map(|i| match &mut i.instr { Instruction::CallCore(f) => Some(Slot::Id(f)), - Instruction::CallExport(e) => Some(Slot::Export(*e)), + Instruction::CallExport(e, _) => Some(Slot::Export(*e)), Instruction::CallTableElement(index) => Some(Slot::TableElement(*index)), _ => None, }) diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 9d7b18e84c0..90870d4bf10 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -515,7 +515,7 @@ impl<'a> Context<'a> { None => AuxExportKind::Function(export.function.name.to_string()), }; - let id = self.export_adapter(export_id, descriptor)?; + let id = self.export_adapter(export_id, descriptor, &kind)?; self.aux.export_map.insert( id, AuxExport { @@ -870,7 +870,13 @@ impl<'a> Context<'a> { ret: descriptor.clone(), inner_ret: Some(descriptor.clone()), }; - let getter_id = self.export_adapter(getter_id, getter_descriptor)?; + let kind = AuxExportKind::Method { + class: struct_.name.to_string(), + name: field.name.to_string(), + receiver: AuxReceiverKind::Borrowed, + kind: AuxExportedMethodKind::Getter, + }; + let getter_id = self.export_adapter(getter_id, getter_descriptor, &kind)?; self.aux.export_map.insert( getter_id, AuxExport { @@ -878,12 +884,7 @@ impl<'a> Context<'a> { arg_names: None, asyncness: false, comments: concatenate_comments(&field.comments), - kind: AuxExportKind::Method { - class: struct_.name.to_string(), - name: field.name.to_string(), - receiver: AuxReceiverKind::Borrowed, - kind: AuxExportedMethodKind::Getter, - }, + kind, generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, @@ -902,7 +903,13 @@ impl<'a> Context<'a> { ret: Descriptor::Unit, inner_ret: None, }; - let setter_id = self.export_adapter(setter_id, setter_descriptor)?; + let kind = AuxExportKind::Method { + class: struct_.name.to_string(), + name: field.name.to_string(), + receiver: AuxReceiverKind::Borrowed, + kind: AuxExportedMethodKind::Setter, + }; + let setter_id = self.export_adapter(setter_id, setter_descriptor, &kind)?; self.aux.export_map.insert( setter_id, AuxExport { @@ -910,12 +917,7 @@ impl<'a> Context<'a> { arg_names: None, asyncness: false, comments: concatenate_comments(&field.comments), - kind: AuxExportKind::Method { - class: struct_.name.to_string(), - name: field.name.to_string(), - receiver: AuxReceiverKind::Borrowed, - kind: AuxExportedMethodKind::Setter, - }, + kind, generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, @@ -1196,11 +1198,12 @@ impl<'a> Context<'a> { &mut self, export: ExportId, signature: Function, + kind: &AuxExportKind, ) -> Result { let export = self.module.exports.get(export); let name = export.name.clone(); // Do the actual heavy lifting elsewhere to generate the `binding`. - let call = Instruction::CallExport(export.id()); + let call = Instruction::CallExport(export.id(), kind.clone()); let id = self.register_export_adapter(call, signature)?; self.adapters.exports.push((name, id)); Ok(id) @@ -1275,7 +1278,22 @@ impl<'a> Context<'a> { }; let mut ret = args.cx.instruction_builder(true); - ret.outgoing(&signature.ret)?; + if let Instruction::CallExport(_, AuxExportKind::Constructor(ref class)) = call { + if let Descriptor::RustStruct(ref name) = signature.ret { + if class != name { + bail!("constructor for `{}` cannot return `{}`", class, name); + } + ret.instruction( + &[AdapterType::I32], + Instruction::SelfFromI32, + &[AdapterType::Struct(class.clone())], + ); + } else { + bail!("constructor for `{}` does not return `Self`", class); + } + } else { + ret.outgoing(&signature.ret)?; + } let uses_retptr = ret.input.len() > 1; // Our instruction stream starts out with the return pointer as the first diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index d05761bfda0..90d448fbe7e 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -101,7 +101,7 @@ pub struct AuxExport { /// sort of webidl import to customize behavior or something like that. In any /// case this doesn't feel quite right in terms of privilege separation, so /// we'll want to work on this. For now though it works. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AuxExportKind { /// A free function that's just listed on the exported module Function(String), diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 5a771d59675..300d626b8fa 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -1,5 +1,5 @@ use crate::descriptor::VectorKind; -use crate::wit::{AuxImport, WasmBindgenAux}; +use crate::wit::{AuxImport, WasmBindgenAux, AuxExportKind}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use walrus::{FunctionId, ImportId, TypedCustomSectionId}; @@ -102,7 +102,7 @@ pub enum Instruction { /// call-adapter instruction CallAdapter(AdapterId), /// Call an exported function in the core module - CallExport(walrus::ExportId), + CallExport(walrus::ExportId, AuxExportKind), /// Call an element in the function table of the core module CallTableElement(u32), @@ -249,6 +249,8 @@ pub enum Instruction { }, /// pops `i32`, pushes string from that `char` StringFromChar, + /// pops `i32`, pushed an externref for rust class without wrapping + SelfFromI32, /// pops `i32`, pushes an externref for the wrapped rust class RustFromI32 { class: String, diff --git a/crates/cli/tests/reference/builder.d.ts b/crates/cli/tests/reference/builder.d.ts new file mode 100644 index 00000000000..ede348c9632 --- /dev/null +++ b/crates/cli/tests/reference/builder.d.ts @@ -0,0 +1,11 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +*/ +export class ClassBuilder { + free(): void; +/** +* @returns {ClassBuilder} +*/ + static builder(): ClassBuilder; +} diff --git a/crates/cli/tests/reference/builder.js b/crates/cli/tests/reference/builder.js new file mode 100644 index 00000000000..3402667dd0d --- /dev/null +++ b/crates/cli/tests/reference/builder.js @@ -0,0 +1,61 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} +/** +*/ +export class ClassBuilder { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(ClassBuilder.prototype); + obj.__wbg_ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_classbuilder_free(ptr); + } + /** + * @returns {ClassBuilder} + */ + static builder() { + const ret = wasm.classbuilder_builder(); + return ClassBuilder.__wrap(ret); + } +} + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/builder.rs b/crates/cli/tests/reference/builder.rs new file mode 100644 index 00000000000..fe699d79bf1 --- /dev/null +++ b/crates/cli/tests/reference/builder.rs @@ -0,0 +1,11 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct ClassBuilder(String); + +#[wasm_bindgen] +impl ClassBuilder { + pub fn builder() -> Self { + ClassBuilder(String::from("Test")) + } +} diff --git a/crates/cli/tests/reference/builder.wat b/crates/cli/tests/reference/builder.wat new file mode 100644 index 00000000000..c6761d480fd --- /dev/null +++ b/crates/cli/tests/reference/builder.wat @@ -0,0 +1,10 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (func $classbuilder_builder (;0;) (type 0) (result i32)) + (func $__wbg_classbuilder_free (;1;) (type 1) (param i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "__wbg_classbuilder_free" (func $__wbg_classbuilder_free)) + (export "classbuilder_builder" (func $classbuilder_builder)) +) diff --git a/crates/cli/tests/reference/constructor.d.ts b/crates/cli/tests/reference/constructor.d.ts new file mode 100644 index 00000000000..f35e8c72fa1 --- /dev/null +++ b/crates/cli/tests/reference/constructor.d.ts @@ -0,0 +1,10 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +*/ +export class ClassConstructor { + free(): void; +/** +*/ + constructor(); +} diff --git a/crates/cli/tests/reference/constructor.js b/crates/cli/tests/reference/constructor.js new file mode 100644 index 00000000000..1973256e15c --- /dev/null +++ b/crates/cli/tests/reference/constructor.js @@ -0,0 +1,53 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} +/** +*/ +export class ClassConstructor { + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_classconstructor_free(ptr); + } + /** + */ + constructor() { + const ret = wasm.classconstructor_new(); + this.__wbg_ptr = ret >>> 0; + return this; + } +} + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/constructor.rs b/crates/cli/tests/reference/constructor.rs new file mode 100644 index 00000000000..8c29147d75b --- /dev/null +++ b/crates/cli/tests/reference/constructor.rs @@ -0,0 +1,13 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct ClassConstructor(()); + +#[wasm_bindgen] +impl ClassConstructor { + + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + ClassConstructor(()) + } +} diff --git a/crates/cli/tests/reference/constructor.wat b/crates/cli/tests/reference/constructor.wat new file mode 100644 index 00000000000..a5596470321 --- /dev/null +++ b/crates/cli/tests/reference/constructor.wat @@ -0,0 +1,10 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (func $__wbg_classconstructor_free (;0;) (type 1) (param i32)) + (func $classconstructor_new (;1;) (type 0) (result i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "__wbg_classconstructor_free" (func $__wbg_classconstructor_free)) + (export "classconstructor_new" (func $classconstructor_new)) +) diff --git a/crates/cli/tests/wasm-bindgen/main.rs b/crates/cli/tests/wasm-bindgen/main.rs index 055fad5ed1f..7ebcb9dce32 100644 --- a/crates/cli/tests/wasm-bindgen/main.rs +++ b/crates/cli/tests/wasm-bindgen/main.rs @@ -431,3 +431,27 @@ fn function_table_preserved() { .wasm_bindgen(""); cmd.assert().success(); } + +#[test] +fn constructor_without_self_does_not_work() { + let (mut cmd, _out_dir) = Project::new("constructor_without_self_does_not_work") + .file( + "src/lib.rs", + r#" + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + pub struct Foo(()); + + #[wasm_bindgen] + impl Foo { + #[wasm_bindgen(constructor)] + pub fn new() -> i32 { + 0 + } + } + "#, + ) + .wasm_bindgen("--target web"); + cmd.assert().failure(); +} diff --git a/guide/src/reference/attributes/on-rust-exports/constructor.md b/guide/src/reference/attributes/on-rust-exports/constructor.md index 1d6fcf4f625..483c00b07be 100644 --- a/guide/src/reference/attributes/on-rust-exports/constructor.md +++ b/guide/src/reference/attributes/on-rust-exports/constructor.md @@ -32,3 +32,41 @@ import { Foo } from './my_module'; const f = new Foo(); console.log(f.get_contents()); ``` + +## Caveats + +Starting from v0.2.48 there is a bug in the `wasm-bindgen-cli-support` crate which breaks inheritance of exported Rust structs from JavaScript side (see [#3213](https://github.com/rustwasm/wasm-bindgen/issues/3213)). If you want to inherit a Rust struct such as: + +```rust +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct Parent { + msg: String, +} + +#[wasm_bindgen] +impl Parent { + #[wasm_bindgen(constructor)] + fn new() -> Self { + Parent { + msg: String::from("Hello from Parent!"), + } + } +} +``` + +You will need to reset the prototype of `this` back to the `Child` class prototype after calling the `Parent`'s constructor via `super`. + +```js +import { Parent } from './my_module'; + +class Child extends Parent { + constructor() { + super(); + Object.setPrototypeOf(this, Child.prototype); + } +} +``` + +This is no longer required as of v0.2.88. From aa28337a7ab623a3cf7d0725462f1659bed63f22 Mon Sep 17 00:00:00 2001 From: Oliver T Date: Thu, 17 Aug 2023 16:55:27 -0400 Subject: [PATCH 2/4] Rust fmt --- crates/cli-support/src/wit/standard.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 300d626b8fa..06564701628 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -1,5 +1,5 @@ use crate::descriptor::VectorKind; -use crate::wit::{AuxImport, WasmBindgenAux, AuxExportKind}; +use crate::wit::{AuxExportKind, AuxImport, WasmBindgenAux}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use walrus::{FunctionId, ImportId, TypedCustomSectionId}; From e0e21fcb9f7e43009fed1e88d828e1956911d976 Mon Sep 17 00:00:00 2001 From: Oliver T Date: Thu, 17 Aug 2023 17:11:06 -0400 Subject: [PATCH 3/4] remove restrictive failure condition --- crates/cli-support/src/wit/mod.rs | 2 +- crates/cli/tests/wasm-bindgen/main.rs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 90870d4bf10..e6b7ed21ec2 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -1289,7 +1289,7 @@ impl<'a> Context<'a> { &[AdapterType::Struct(class.clone())], ); } else { - bail!("constructor for `{}` does not return `Self`", class); + ret.outgoing(&signature.ret)?; } } else { ret.outgoing(&signature.ret)?; diff --git a/crates/cli/tests/wasm-bindgen/main.rs b/crates/cli/tests/wasm-bindgen/main.rs index 7ebcb9dce32..81b31cc933e 100644 --- a/crates/cli/tests/wasm-bindgen/main.rs +++ b/crates/cli/tests/wasm-bindgen/main.rs @@ -433,8 +433,8 @@ fn function_table_preserved() { } #[test] -fn constructor_without_self_does_not_work() { - let (mut cmd, _out_dir) = Project::new("constructor_without_self_does_not_work") +fn constructor_cannot_return_foreign_struct() { + let (mut cmd, _out_dir) = Project::new("constructor_cannot_return_foreign_struct") .file( "src/lib.rs", r#" @@ -443,11 +443,14 @@ fn constructor_without_self_does_not_work() { #[wasm_bindgen] pub struct Foo(()); + #[wasm_bindgen] + pub struct Bar(()); + #[wasm_bindgen] impl Foo { #[wasm_bindgen(constructor)] - pub fn new() -> i32 { - 0 + pub fn new() -> Bar { + Bar(()) } } "#, From 6358b355e397df2e4c6e396d7f7984b37b42b301 Mon Sep 17 00:00:00 2001 From: Oliver T Date: Thu, 17 Aug 2023 17:36:46 -0400 Subject: [PATCH 4/4] clippy & changelog entry Signed-off-by: Oliver T --- CHANGELOG.md | 8 ++++++++ crates/cli-support/src/js/binding.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3185009da14..17f5487e7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,10 @@ bindgen placeholder. [#3233](https://github.com/rustwasm/wasm-bindgen/pull/3233) +* Changed inheritance behavior in generated JS bindings, it is now possible to + override methods from generated JS classes using inheritance. + [#3561](https://github.com/rustwasm/wasm-bindgen/pull/3561) + ### Fixed * Fixed bindings and comments for `Atomics.wait`. @@ -53,6 +57,10 @@ * Fixed `wasm_bindgen_test` macro to handle raw identifiers in test names. [#3541](https://github.com/rustwasm/wasm-bindgen/pull/3541) +* Fixed bug where constructors of exported Rust structs are allowed to return + other exported Rust structs (i.e. not `Self`). + [#3561](https://github.com/rustwasm/wasm-bindgen/pull/3561) + ## [0.2.87](https://github.com/rustwasm/wasm-bindgen/compare/0.2.86...0.2.87) Released 2023-06-12. diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 16b8342cf35..13cc6f06d75 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -989,7 +989,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Instruction::SelfFromI32 => { let val = js.pop(); js.prelude(&format!("this.__wbg_ptr = {} >>> 0;", val)); - js.push(format!("this")); + js.push("this".to_string()); } Instruction::RustFromI32 { class } => {