mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-16 22:41:24 +00:00
Generate accessors for public struct fields
Automatically infer public struct fields as "JS wants to access this" and generate appropriate getters/setters for the field. At this time the field is required to implement `Copy`, but we will probably want to relax that in the future to at least encompass `JsValue` and maybe other `Clone` values as well. Closes #121
This commit is contained in:
@ -11,7 +11,7 @@ pub struct Js2Rust<'a, 'b: 'a> {
|
||||
rust_arguments: Vec<String>,
|
||||
|
||||
/// Arguments and their types to the JS shim.
|
||||
js_arguments: Vec<(String, String)>,
|
||||
pub js_arguments: Vec<(String, String)>,
|
||||
|
||||
/// Conversions that happen before we invoke the wasm function, such as
|
||||
/// converting a string to a ptr/length pair.
|
||||
@ -100,7 +100,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self
|
||||
}
|
||||
|
||||
fn argument(&mut self, arg: &Descriptor) {
|
||||
pub fn argument(&mut self, arg: &Descriptor) -> &mut Self {
|
||||
let i = self.arg_idx;
|
||||
self.arg_idx += 1;
|
||||
let name = format!("arg{}", i);
|
||||
@ -122,7 +122,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.cx.require_internal_export("__wbindgen_free");
|
||||
}
|
||||
self.rust_arguments.push(format!("ptr{}", i));
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if let Some(s) = arg.rust_struct() {
|
||||
@ -144,7 +144,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
", i = i, arg = name));
|
||||
self.rust_arguments.push(format!("ptr{}", i));
|
||||
}
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if arg.is_number() {
|
||||
@ -156,7 +156,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
}
|
||||
|
||||
self.rust_arguments.push(name);
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if arg.is_ref_anyref() {
|
||||
@ -164,7 +164,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.finally("stack.pop();");
|
||||
self.rust_arguments.push(format!("addBorrowedObject({})", name));
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
match *arg {
|
||||
@ -187,15 +187,16 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
panic!("unsupported argument to rust function {:?}", arg)
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn ret(&mut self, ret: &Option<Descriptor>) {
|
||||
pub fn ret(&mut self, ret: &Option<Descriptor>) -> &mut Self {
|
||||
let ty = match *ret {
|
||||
Some(ref t) => t,
|
||||
None => {
|
||||
self.ret_ty = "void".to_string();
|
||||
self.ret_expr = format!("return RET;");
|
||||
return
|
||||
return self
|
||||
}
|
||||
};
|
||||
|
||||
@ -203,7 +204,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_get_object();
|
||||
self.ret_expr = format!("return getObject(RET);");
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if ty.is_by_ref() {
|
||||
@ -222,19 +223,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
wasm.__wbindgen_free(ret, len * {});\n\
|
||||
return realRet;\n\
|
||||
", f, ty.size());
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if let Some(name) = ty.rust_struct() {
|
||||
self.ret_ty = name.to_string();
|
||||
self.ret_expr = format!("return {name}.__construct(RET);", name = name);
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
if ty.is_number() {
|
||||
self.ret_ty = "number".to_string();
|
||||
self.ret_expr = format!("return RET;");
|
||||
return
|
||||
return self
|
||||
}
|
||||
|
||||
match *ty {
|
||||
@ -249,6 +250,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
}
|
||||
_ => panic!("unsupported return from Rust to JS {:?}", ty),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate the actual function.
|
||||
|
@ -35,6 +35,11 @@ pub struct ExportedClass {
|
||||
pub contents: String,
|
||||
pub typescript: String,
|
||||
pub constructor: Option<String>,
|
||||
pub fields: Vec<ClassField>,
|
||||
}
|
||||
|
||||
pub struct ClassField {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct SubContext<'a, 'b: 'a> {
|
||||
@ -341,78 +346,111 @@ impl<'a> Context<'a> {
|
||||
fn write_classes(&mut self) {
|
||||
let classes = mem::replace(&mut self.exported_classes, Default::default());
|
||||
for (class, exports) in classes {
|
||||
let mut dst = format!("class {} {{\n", class);
|
||||
let mut ts_dst = format!("export {}", dst);
|
||||
self.write_class(&class, &exports);
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.debug || exports.constructor.is_some() {
|
||||
self.expose_constructor_token();
|
||||
fn write_class(&mut self, name: &str, class: &ExportedClass) {
|
||||
let mut dst = format!("class {} {{\n", name);
|
||||
let mut ts_dst = format!("export {}", dst);
|
||||
|
||||
dst.push_str(&format!("
|
||||
static __construct(ptr) {{
|
||||
return new {}(new ConstructorToken(ptr));
|
||||
}}
|
||||
|
||||
constructor(...args) {{
|
||||
if (args.length === 1 && args[0] instanceof ConstructorToken) {{
|
||||
this.ptr = args[0].ptr;
|
||||
return;
|
||||
}}
|
||||
", class));
|
||||
|
||||
if let Some(constructor) = exports.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;
|
||||
", class = class, constructor = constructor));
|
||||
} else {
|
||||
dst.push_str("throw new Error('you cannot invoke `new` directly without having a \
|
||||
method annotated a constructor');");
|
||||
}
|
||||
|
||||
dst.push_str("}");
|
||||
} else {
|
||||
dst.push_str(&format!("
|
||||
static __construct(ptr) {{
|
||||
return new {}(ptr);
|
||||
}}
|
||||
|
||||
constructor(ptr) {{
|
||||
this.ptr = ptr;
|
||||
}}
|
||||
", class));
|
||||
}
|
||||
|
||||
let new_name = shared::new_function(&class);
|
||||
if self.wasm_import_needed(&new_name) {
|
||||
self.expose_add_heap_object();
|
||||
|
||||
self.export(&new_name, &format!("
|
||||
function(ptr) {{
|
||||
return addHeapObject({}.__construct(ptr));
|
||||
}}
|
||||
", class));
|
||||
}
|
||||
if self.config.debug || class.constructor.is_some() {
|
||||
self.expose_constructor_token();
|
||||
|
||||
dst.push_str(&format!("
|
||||
free() {{
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
wasm.{}(ptr);
|
||||
static __construct(ptr) {{
|
||||
return new {}(new ConstructorToken(ptr));
|
||||
}}
|
||||
", shared::free_function(&class)));
|
||||
ts_dst.push_str("free(): void;\n");
|
||||
|
||||
dst.push_str(&exports.contents);
|
||||
ts_dst.push_str(&exports.typescript);
|
||||
dst.push_str("}\n");
|
||||
ts_dst.push_str("}\n");
|
||||
constructor(...args) {{
|
||||
if (args.length === 1 && args[0] instanceof ConstructorToken) {{
|
||||
this.ptr = args[0].ptr;
|
||||
return;
|
||||
}}
|
||||
", name));
|
||||
|
||||
self.export(&class, &dst);
|
||||
self.typescript.push_str(&ts_dst);
|
||||
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;
|
||||
", class = name, constructor = constructor));
|
||||
} else {
|
||||
dst.push_str("throw new Error('you cannot invoke `new` directly without having a \
|
||||
method annotated a constructor');");
|
||||
}
|
||||
|
||||
dst.push_str("}");
|
||||
} else {
|
||||
dst.push_str(&format!("
|
||||
static __construct(ptr) {{
|
||||
return new {}(ptr);
|
||||
}}
|
||||
|
||||
constructor(ptr) {{
|
||||
this.ptr = ptr;
|
||||
}}
|
||||
", name));
|
||||
}
|
||||
|
||||
let new_name = shared::new_function(&name);
|
||||
if self.wasm_import_needed(&new_name) {
|
||||
self.expose_add_heap_object();
|
||||
|
||||
self.export(&new_name, &format!("
|
||||
function(ptr) {{
|
||||
return addHeapObject({}.__construct(ptr));
|
||||
}}
|
||||
", name));
|
||||
}
|
||||
|
||||
for field in class.fields.iter() {
|
||||
let wasm_getter = shared::struct_field_get(name, &field.name);
|
||||
let wasm_setter = shared::struct_field_set(name, &field.name);
|
||||
let descriptor = self.describe(&wasm_getter);
|
||||
|
||||
let set = {
|
||||
let mut cx = Js2Rust::new(&field.name, self);
|
||||
cx.method(true)
|
||||
.argument(&descriptor)
|
||||
.ret(&None);
|
||||
ts_dst.push_str(&format!("{}: {}\n",
|
||||
field.name,
|
||||
&cx.js_arguments[0].1));
|
||||
cx.finish("", &format!("wasm.{}", wasm_setter)).0
|
||||
};
|
||||
let (get, _ts) = Js2Rust::new(&field.name, self)
|
||||
.method(true)
|
||||
.ret(&Some(descriptor))
|
||||
.finish("", &format!("wasm.{}", wasm_getter));
|
||||
|
||||
dst.push_str("get ");
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&get);
|
||||
dst.push_str("\n");
|
||||
dst.push_str("set ");
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&set);
|
||||
}
|
||||
|
||||
dst.push_str(&format!("
|
||||
free() {{
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
wasm.{}(ptr);
|
||||
}}
|
||||
", shared::free_function(&name)));
|
||||
ts_dst.push_str("free(): void;\n");
|
||||
|
||||
dst.push_str(&class.contents);
|
||||
ts_dst.push_str(&class.typescript);
|
||||
dst.push_str("}\n");
|
||||
ts_dst.push_str("}\n");
|
||||
|
||||
self.export(&name, &dst);
|
||||
self.typescript.push_str(&ts_dst);
|
||||
}
|
||||
|
||||
fn export_table(&mut self) {
|
||||
@ -1268,8 +1306,14 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
}
|
||||
for s in self.program.structs.iter() {
|
||||
self.cx.exported_classes
|
||||
.entry(s.clone())
|
||||
.or_insert_with(Default::default);
|
||||
.entry(s.name.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.fields
|
||||
.extend(s.fields.iter().map(|s| {
|
||||
ClassField {
|
||||
name: s.name.clone(),
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user