mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-30 22:25:35 +03:00
Merge pull request #870 from alexcrichton/no-constructor-token
Remove the need for a `ConstructorToken`
This commit is contained in:
commit
9a1fa5a81b
@ -34,11 +34,9 @@ pub struct Export {
|
|||||||
pub class: Option<Ident>,
|
pub class: Option<Ident>,
|
||||||
/// The type of `self` (either `self`, `&self`, or `&mut self`)
|
/// The type of `self` (either `self`, `&self`, or `&mut self`)
|
||||||
pub method_self: Option<MethodSelf>,
|
pub method_self: Option<MethodSelf>,
|
||||||
/// The name of the constructor function (e.g. new).
|
/// Whether or not this export is flagged as a constructor, returning an
|
||||||
///
|
/// instance of the `impl` type
|
||||||
/// This allows javascript to expose an `Object` interface, where calling
|
pub is_constructor: bool,
|
||||||
/// the constructor maps correctly to rust.
|
|
||||||
pub constructor: Option<String>,
|
|
||||||
/// The rust function
|
/// The rust function
|
||||||
pub function: Function,
|
pub function: Function,
|
||||||
/// Comments extracted from the rust source.
|
/// Comments extracted from the rust source.
|
||||||
@ -326,7 +324,7 @@ impl Export {
|
|||||||
class: self.class.as_ref().map(|s| s.to_string()),
|
class: self.class.as_ref().map(|s| s.to_string()),
|
||||||
method,
|
method,
|
||||||
consumed,
|
consumed,
|
||||||
constructor: self.constructor.clone(),
|
is_constructor: self.is_constructor,
|
||||||
function: self.function.shared(),
|
function: self.function.shared(),
|
||||||
comments: self.comments.clone(),
|
comments: self.comments.clone(),
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,14 @@ pub struct Js2Rust<'a, 'b: 'a> {
|
|||||||
/// Name of the JS shim/function that we're generating, primarily for
|
/// Name of the JS shim/function that we're generating, primarily for
|
||||||
/// TypeScript right now.
|
/// TypeScript right now.
|
||||||
js_name: String,
|
js_name: String,
|
||||||
|
|
||||||
|
/// whether or not this generated function body will act like a constructor,
|
||||||
|
/// meaning it doesn't actually return something but rather assigns to
|
||||||
|
/// `this`
|
||||||
|
///
|
||||||
|
/// The string value here is the class that this should be a constructor
|
||||||
|
/// for.
|
||||||
|
constructor: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Js2Rust<'a, 'b> {
|
impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||||
@ -51,6 +59,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
arg_idx: 0,
|
arg_idx: 0,
|
||||||
ret_ty: String::new(),
|
ret_ty: String::new(),
|
||||||
ret_expr: String::new(),
|
ret_expr: String::new(),
|
||||||
|
constructor: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +73,11 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constructor(&mut self, class: Option<&str>) -> &mut Self {
|
||||||
|
self.constructor = class.map(|s| s.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Flag this shim as a method call into Rust, so the first Rust argument
|
/// Flag this shim as a method call into Rust, so the first Rust argument
|
||||||
/// passed should be `this.ptr`.
|
/// passed should be `this.ptr`.
|
||||||
pub fn method(&mut self, method: bool, consumed: bool) -> &mut Self {
|
pub fn method(&mut self, method: bool, consumed: bool) -> &mut Self {
|
||||||
@ -393,6 +407,32 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
||||||
|
if let Some(name) = ty.rust_struct() {
|
||||||
|
match &self.constructor {
|
||||||
|
Some(class) if class == name => {
|
||||||
|
self.ret_expr = format!("this.ptr = RET;");
|
||||||
|
if self.cx.config.weak_refs {
|
||||||
|
self.ret_expr.push_str(&format!("\
|
||||||
|
addCleanup(this, this.ptr, free{});
|
||||||
|
", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(class) => {
|
||||||
|
bail!("constructor for `{}` cannot return `{}`", class, name)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.ret_ty = name.to_string();
|
||||||
|
self.cx.require_class_wrap(name);
|
||||||
|
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.constructor.is_some() {
|
||||||
|
bail!("constructor functions must return a Rust structure")
|
||||||
|
}
|
||||||
|
|
||||||
if let Descriptor::Unit = ty {
|
if let Descriptor::Unit = ty {
|
||||||
self.ret_ty = "void".to_string();
|
self.ret_ty = "void".to_string();
|
||||||
self.ret_expr = format!("return RET;");
|
self.ret_expr = format!("return RET;");
|
||||||
@ -551,7 +591,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
|
|
||||||
if let Some(name) = ty.rust_struct() {
|
if let Some(name) = ty.rust_struct() {
|
||||||
self.ret_ty = name.to_string();
|
self.ret_ty = name.to_string();
|
||||||
self.ret_expr = format!("return {name}.__construct(RET);", name = name);
|
self.cx.require_class_wrap(name);
|
||||||
|
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,8 @@ pub struct ExportedClass {
|
|||||||
comments: String,
|
comments: String,
|
||||||
contents: String,
|
contents: String,
|
||||||
typescript: String,
|
typescript: String,
|
||||||
constructor: Option<String>,
|
has_constructor: bool,
|
||||||
|
wrap_needed: bool,
|
||||||
fields: Vec<ClassField>,
|
fields: Vec<ClassField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,11 +533,7 @@ impl<'a> Context<'a> {
|
|||||||
// `drop()` the `WeakRef` (cancel finalization) whenever it is
|
// `drop()` the `WeakRef` (cancel finalization) whenever it is
|
||||||
// finalized.
|
// finalized.
|
||||||
self.expose_cleanup_groups();
|
self.expose_cleanup_groups();
|
||||||
let mk = format!("
|
let mk = format!("addCleanup(this, this.ptr, free{});", name);
|
||||||
const cleanup_ptr = this.ptr;
|
|
||||||
const ref = CLEANUPS.makeRef(this, () => free{}(cleanup_ptr));
|
|
||||||
CLEANUPS_MAP.set(this.ptr, ref);
|
|
||||||
", name);
|
|
||||||
let free = "
|
let free = "
|
||||||
CLEANUPS_MAP.get(ptr).drop();
|
CLEANUPS_MAP.get(ptr).drop();
|
||||||
CLEANUPS_MAP.delete(ptr);
|
CLEANUPS_MAP.delete(ptr);
|
||||||
@ -546,73 +543,28 @@ impl<'a> Context<'a> {
|
|||||||
(String::new(), "")
|
(String::new(), "")
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.config.debug || class.constructor.is_some() {
|
if self.config.debug && !class.has_constructor {
|
||||||
self.expose_constructor_token();
|
dst.push_str(
|
||||||
|
|
||||||
dst.push_str(&format!(
|
|
||||||
"
|
"
|
||||||
static __construct(ptr) {{
|
constructor() {
|
||||||
return new {}(new ConstructorToken(ptr));
|
throw new Error('cannot invoke `new` directly');
|
||||||
}}
|
}
|
||||||
|
|
||||||
constructor(...args) {{
|
|
||||||
if (args.length === 1 && args[0] instanceof ConstructorToken) {{
|
|
||||||
this.ptr = args[0].ptr;
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
",
|
|
||||||
name
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(ref constructor) = class.constructor {
|
|
||||||
ts_dst.push_str(&format!("constructor(...args: any[]);\n"));
|
|
||||||
|
|
||||||
dst.push_str(&format!(
|
|
||||||
"
|
|
||||||
// This invocation of new will call this constructor with a ConstructorToken
|
|
||||||
let instance = {class}.{constructor}(...args);
|
|
||||||
this.ptr = instance.ptr;
|
|
||||||
{mkweakref}
|
|
||||||
",
|
|
||||||
class = name,
|
|
||||||
constructor = constructor,
|
|
||||||
mkweakref = mkweakref,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
dst.push_str(
|
|
||||||
"throw new Error('you cannot invoke `new` directly without having a \
|
|
||||||
method annotated a constructor');\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dst.push_str("}");
|
|
||||||
} else {
|
|
||||||
dst.push_str(&format!(
|
|
||||||
"
|
"
|
||||||
static __construct(ptr) {{
|
);
|
||||||
return new {}(ptr);
|
|
||||||
}}
|
|
||||||
|
|
||||||
constructor(ptr) {{
|
|
||||||
this.ptr = ptr;
|
|
||||||
{}
|
|
||||||
}}
|
|
||||||
",
|
|
||||||
name,
|
|
||||||
mkweakref,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut wrap_needed = class.wrap_needed;
|
||||||
let new_name = shared::new_function(&name);
|
let new_name = shared::new_function(&name);
|
||||||
if self.wasm_import_needed(&new_name) {
|
if self.wasm_import_needed(&new_name) {
|
||||||
self.expose_add_heap_object();
|
self.expose_add_heap_object();
|
||||||
|
wrap_needed = true;
|
||||||
|
|
||||||
self.export(
|
self.export(
|
||||||
&new_name,
|
&new_name,
|
||||||
&format!(
|
&format!(
|
||||||
"
|
"
|
||||||
function(ptr) {{
|
function(ptr) {{
|
||||||
return addHeapObject({}.__construct(ptr));
|
return addHeapObject({}.__wrap(ptr));
|
||||||
}}
|
}}
|
||||||
",
|
",
|
||||||
name
|
name
|
||||||
@ -621,6 +573,21 @@ impl<'a> Context<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wrap_needed {
|
||||||
|
dst.push_str(&format!(
|
||||||
|
"
|
||||||
|
static __wrap(ptr) {{
|
||||||
|
const obj = Object.create({}.prototype);
|
||||||
|
obj.ptr = ptr;
|
||||||
|
{}
|
||||||
|
return obj;
|
||||||
|
}}
|
||||||
|
",
|
||||||
|
name,
|
||||||
|
mkweakref.replace("this", "obj"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
for field in class.fields.iter() {
|
for field in class.fields.iter() {
|
||||||
let wasm_getter = shared::struct_field_get(name, &field.name);
|
let wasm_getter = shared::struct_field_get(name, &field.name);
|
||||||
let wasm_setter = shared::struct_field_set(name, &field.name);
|
let wasm_setter = shared::struct_field_set(name, &field.name);
|
||||||
@ -1165,22 +1132,6 @@ impl<'a> Context<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expose_constructor_token(&mut self) {
|
|
||||||
if !self.exposed_globals.insert("ConstructorToken") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.global(
|
|
||||||
"
|
|
||||||
class ConstructorToken {
|
|
||||||
constructor(ptr) {
|
|
||||||
this.ptr = ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expose_get_string_from_wasm(&mut self) {
|
fn expose_get_string_from_wasm(&mut self) {
|
||||||
if !self.exposed_globals.insert("get_string_from_wasm") {
|
if !self.exposed_globals.insert("get_string_from_wasm") {
|
||||||
return;
|
return;
|
||||||
@ -1717,6 +1668,11 @@ impl<'a> Context<'a> {
|
|||||||
"
|
"
|
||||||
const CLEANUPS = new WeakRefGroup(x => x.holdings());
|
const CLEANUPS = new WeakRefGroup(x => x.holdings());
|
||||||
const CLEANUPS_MAP = new Map();
|
const CLEANUPS_MAP = new Map();
|
||||||
|
|
||||||
|
function addCleanup(obj, ptr, free) {
|
||||||
|
const ref = CLEANUPS.makeRef(obj, () => free(ptr));
|
||||||
|
CLEANUPS_MAP.set(ptr, ref);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1785,6 +1741,13 @@ impl<'a> Context<'a> {
|
|||||||
self.memory_init = Some(mem.limits().clone());
|
self.memory_init = Some(mem.limits().clone());
|
||||||
"memory"
|
"memory"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn require_class_wrap(&mut self, class: &str) {
|
||||||
|
self.exported_classes
|
||||||
|
.entry(class.to_string())
|
||||||
|
.or_insert_with(ExportedClass::default)
|
||||||
|
.wrap_needed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> SubContext<'a, 'b> {
|
impl<'a, 'b> SubContext<'a, 'b> {
|
||||||
@ -1857,8 +1820,14 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
|
let function_name = if export.is_constructor {
|
||||||
|
"constructor"
|
||||||
|
} else {
|
||||||
|
&export.function.name
|
||||||
|
};
|
||||||
|
let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx)
|
||||||
.method(export.method, export.consumed)
|
.method(export.method, export.consumed)
|
||||||
|
.constructor(if export.is_constructor { Some(class_name) } else { None })
|
||||||
.process(descriptor.unwrap_function())?
|
.process(descriptor.unwrap_function())?
|
||||||
.finish("", &format!("wasm.{}", wasm_name));
|
.finish("", &format!("wasm.{}", wasm_name));
|
||||||
|
|
||||||
@ -1870,25 +1839,19 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
|||||||
class
|
class
|
||||||
.contents
|
.contents
|
||||||
.push_str(&format_doc_comments(&export.comments, Some(js_doc)));
|
.push_str(&format_doc_comments(&export.comments, Some(js_doc)));
|
||||||
if !export.method {
|
|
||||||
|
if export.is_constructor {
|
||||||
|
if class.has_constructor {
|
||||||
|
bail!("found duplicate constructor `{}`",
|
||||||
|
export.function.name);
|
||||||
|
}
|
||||||
|
class.has_constructor = true;
|
||||||
|
} else if !export.method {
|
||||||
class.contents.push_str("static ");
|
class.contents.push_str("static ");
|
||||||
class.typescript.push_str("static ");
|
class.typescript.push_str("static ");
|
||||||
}
|
}
|
||||||
|
|
||||||
let constructors: Vec<String> = self
|
class.contents.push_str(function_name);
|
||||||
.program
|
|
||||||
.exports
|
|
||||||
.iter()
|
|
||||||
.filter(|x| x.class == Some(class_name.to_string()))
|
|
||||||
.filter_map(|x| x.constructor.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
class.constructor = match constructors.len() {
|
|
||||||
0 => None,
|
|
||||||
1 => Some(constructors[0].clone()),
|
|
||||||
x @ _ => bail!("there must be only one constructor, not {}", x),
|
|
||||||
};
|
|
||||||
class.contents.push_str(&export.function.name);
|
|
||||||
class.contents.push_str(&js);
|
class.contents.push_str(&js);
|
||||||
class.contents.push_str("\n");
|
class.contents.push_str("\n");
|
||||||
class.typescript.push_str(&ts);
|
class.typescript.push_str(&ts);
|
||||||
|
@ -232,7 +232,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
if arg.is_by_ref() {
|
if arg.is_by_ref() {
|
||||||
bail!("cannot invoke JS functions with custom ref types yet")
|
bail!("cannot invoke JS functions with custom ref types yet")
|
||||||
}
|
}
|
||||||
let assign = format!("let c{0} = {1}.__construct({0});", abi, class);
|
self.cx.require_class_wrap(class);
|
||||||
|
let assign = format!("let c{0} = {1}.__wrap({0});", abi, class);
|
||||||
self.prelude(&assign);
|
self.prelude(&assign);
|
||||||
self.js_arguments.push(format!("c{}", abi));
|
self.js_arguments.push(format!("c{}", abi));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -29,7 +29,7 @@ pub fn spawn(
|
|||||||
// native ESM support for wasm modules.
|
// native ESM support for wasm modules.
|
||||||
await wasm.booted;
|
await wasm.booted;
|
||||||
|
|
||||||
const cx = Context.new();
|
const cx = new Context();
|
||||||
window.console_log_redirect = __wbgtest_console_log;
|
window.console_log_redirect = __wbgtest_console_log;
|
||||||
window.console_error_redirect = __wbgtest_console_error;
|
window.console_error_redirect = __wbgtest_console_error;
|
||||||
|
|
||||||
|
@ -733,7 +733,7 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
|
|||||||
program.exports.push(ast::Export {
|
program.exports.push(ast::Export {
|
||||||
class: None,
|
class: None,
|
||||||
method_self: None,
|
method_self: None,
|
||||||
constructor: None,
|
is_constructor: false,
|
||||||
comments,
|
comments,
|
||||||
rust_name: f.ident.clone(),
|
rust_name: f.ident.clone(),
|
||||||
function: f.convert(opts)?,
|
function: f.convert(opts)?,
|
||||||
@ -856,12 +856,6 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
|||||||
let opts = BindgenAttrs::find(&mut method.attrs)?;
|
let opts = BindgenAttrs::find(&mut method.attrs)?;
|
||||||
let comments = extract_doc_comments(&method.attrs);
|
let comments = extract_doc_comments(&method.attrs);
|
||||||
let is_constructor = opts.constructor();
|
let is_constructor = opts.constructor();
|
||||||
let constructor = if is_constructor {
|
|
||||||
Some(method.sig.ident.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let (function, method_self) = function_from_decl(
|
let (function, method_self) = function_from_decl(
|
||||||
&method.sig.ident,
|
&method.sig.ident,
|
||||||
&opts,
|
&opts,
|
||||||
@ -875,7 +869,7 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
|
|||||||
program.exports.push(ast::Export {
|
program.exports.push(ast::Export {
|
||||||
class: Some(class.clone()),
|
class: Some(class.clone()),
|
||||||
method_self,
|
method_self,
|
||||||
constructor,
|
is_constructor,
|
||||||
function,
|
function,
|
||||||
comments,
|
comments,
|
||||||
rust_name: method.sig.ident.clone(),
|
rust_name: method.sig.ident.clone(),
|
||||||
|
@ -97,7 +97,7 @@ pub struct Export {
|
|||||||
pub class: Option<String>,
|
pub class: Option<String>,
|
||||||
pub method: bool,
|
pub method: bool,
|
||||||
pub consumed: bool,
|
pub consumed: bool,
|
||||||
pub constructor: Option<String>,
|
pub is_constructor: bool,
|
||||||
pub function: Function,
|
pub function: Function,
|
||||||
pub comments: Vec<String>,
|
pub comments: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const wasm = require('wasm-bindgen-test.js');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
exports.js_simple = () => {
|
exports.js_simple = () => {
|
||||||
const r = wasm.ClassesSimple.new();
|
const r = new wasm.ClassesSimple();
|
||||||
assert.strictEqual(r.add(0), 0);
|
assert.strictEqual(r.add(0), 0);
|
||||||
assert.strictEqual(r.add(1), 1);
|
assert.strictEqual(r.add(1), 1);
|
||||||
assert.strictEqual(r.add(1), 2);
|
assert.strictEqual(r.add(1), 2);
|
||||||
@ -70,7 +70,8 @@ exports.js_constructors = () => {
|
|||||||
assert.strictEqual(foo.get_number(), 1);
|
assert.strictEqual(foo.get_number(), 1);
|
||||||
foo.free();
|
foo.free();
|
||||||
|
|
||||||
const foo2 = wasm.ConstructorsFoo.new(2);
|
assert.strictEqual(wasm.ConstructorsBar.new, undefined);
|
||||||
|
const foo2 = new wasm.ConstructorsFoo(2);
|
||||||
assert.strictEqual(foo2.get_number(), 2);
|
assert.strictEqual(foo2.get_number(), 2);
|
||||||
foo2.free();
|
foo2.free();
|
||||||
|
|
||||||
@ -78,7 +79,8 @@ exports.js_constructors = () => {
|
|||||||
assert.strictEqual(bar.get_sum(), 7);
|
assert.strictEqual(bar.get_sum(), 7);
|
||||||
bar.free();
|
bar.free();
|
||||||
|
|
||||||
const bar2 = wasm.ConstructorsBar.other_name(5, 6);
|
assert.strictEqual(wasm.ConstructorsBar.other_name, undefined);
|
||||||
|
const bar2 = new wasm.ConstructorsBar(5, 6);
|
||||||
assert.strictEqual(bar2.get_sum(), 11);
|
assert.strictEqual(bar2.get_sum(), 11);
|
||||||
bar2.free();
|
bar2.free();
|
||||||
|
|
||||||
@ -121,12 +123,12 @@ exports.js_readonly_fields = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.js_double_consume = () => {
|
exports.js_double_consume = () => {
|
||||||
const r = wasm.DoubleConsume.new();
|
const r = new wasm.DoubleConsume();
|
||||||
assert.throws(() => r.consume(r), /Attempt to use a moved value/);
|
assert.throws(() => r.consume(r), /Attempt to use a moved value/);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.js_js_rename = () => {
|
exports.js_js_rename = () => {
|
||||||
wasm.JsRename.new().bar();
|
(new wasm.JsRename()).bar();
|
||||||
wasm.classes_foo();
|
wasm.classes_foo();
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user