Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Option<RustStruct> in arguments/returns #1275

Merged
merged 1 commit into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,17 @@ impl ToTokens for ast::Struct {
(*js).borrow_mut()
}
}

impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name {
#[inline]
fn none() -> Self::Abi { 0 }
}

impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name {
#[inline]
fn is_none(abi: &Self::Abi) -> bool { *abi == 0 }
}

})
.to_tokens(tokens);

Expand Down
84 changes: 49 additions & 35 deletions crates/cli-support/src/js/js2rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,18 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
.push(format!("isLikeNone({0}) ? {1} : {0}", name, hole));
return Ok(self);
}
Descriptor::RustStruct(ref s) => {
self.js_arguments.push((name.clone(), format!("{} | undefined", s)));
self.prelude(&format!("let ptr{} = 0;", i));
self.prelude(&format!("if ({0} !== null && {0} !== undefined) {{", name));
self.assert_class(&name, s);
self.assert_not_moved(&name);
self.prelude(&format!("ptr{} = {}.ptr;", i, name));
self.prelude(&format!("{}.ptr = 0;", name));
self.prelude("}");
self.rust_arguments.push(format!("ptr{}", i));
return Ok(self);
}
_ => bail!(
"unsupported optional argument type for calling Rust function from JS: {:?}",
arg
Expand All @@ -322,44 +334,13 @@ impl<'a, 'b> Js2Rust<'a, 'b> {

if let Some(s) = arg.rust_struct() {
self.js_arguments.push((name.clone(), s.to_string()));

if self.cx.config.debug {
self.cx.expose_assert_class();
self.prelude(&format!(
"\
_assertClass({arg}, {struct_});\n\
",
arg = name,
struct_ = s
));
}

self.assert_class(&name, s);
self.assert_not_moved(&name);
if arg.is_by_ref() {
self.rust_arguments.push(format!("{}.ptr", name));
} else {
self.prelude(&format!(
"\
const ptr{i} = {arg}.ptr;\n\
",
i = i,
arg = name
));
if self.cx.config.debug {
self.prelude(&format!(
"\
if (ptr{i} === 0) {{
throw new Error('Attempt to use a moved value');
}}
",
i = i,
));
}
self.prelude(&format!(
"\
{arg}.ptr = 0;\n\
",
arg = name
));
self.prelude(&format!("const ptr{} = {}.ptr;", i, name));
self.prelude(&format!("{}.ptr = 0;", name));
self.rust_arguments.push(format!("ptr{}", i));
}
return Ok(self);
Expand Down Expand Up @@ -627,6 +608,17 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
);
return Ok(self);
}
Descriptor::RustStruct(ref name) => {
self.ret_ty = format!("{} | undefined", name);
self.cx.require_class_wrap(name);
self.ret_expr = format!("
const ptr = RET;
return ptr === 0 ? undefined : {}.__wrap(ptr);
",
name,
);
return Ok(self);
}
_ => bail!(
"unsupported optional return type for calling Rust function from JS: {:?}",
ty
Expand Down Expand Up @@ -764,4 +756,26 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
ts.push(';');
(js, ts, self.js_doc_comments())
}

fn assert_class(&mut self, arg: &str, class: &str) {
if !self.cx.config.debug {
return
}
self.cx.expose_assert_class();
self.prelude(&format!("_assertClass({}, {});", arg, class));
}

fn assert_not_moved(&mut self, arg: &str) {
if !self.cx.config.debug {
return
}
self.prelude(&format!(
"\
if ({0}.ptr === 0) {{
throw new Error('Attempt to use a moved value');
}}
",
arg,
));
}
}
25 changes: 25 additions & 0 deletions crates/cli-support/src/js/rust2js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
));
return Ok(());
}
Descriptor::RustStruct(ref class) => {
self.cx.require_class_wrap(class);
let assign = format!("let c{0} = {0} === 0 ? undefined : {1}.__wrap({0});", abi, class);
self.prelude(&assign);
self.js_arguments.push(format!("c{}", abi));
return Ok(());
}
_ => bail!(
"unsupported optional argument type for calling JS function from Rust: {:?}",
arg
Expand Down Expand Up @@ -456,6 +463,24 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
);
return Ok(());
}
Descriptor::RustStruct(ref class) => {
// Like below, assert the type
self.ret_expr = format!(
"\
const val = JS;
if (val === undefined || val === null)
return 0;
if (!(val instanceof {0})) {{
throw new Error('expected value of type {0}');
}}
const ret = val.ptr;
val.ptr = 0;
return ret;\
",
class
);
return Ok(());
}
_ => bail!(
"unsupported optional return type for calling JS function from Rust: {:?}",
ty
Expand Down
19 changes: 19 additions & 0 deletions tests/wasm/classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,22 @@ exports.js_conditional_bindings = () => {
const x = new wasm.ConditionalBindings();
x.free();
};

exports.js_assert_none = x => {
assert.strictEqual(x, undefined);
};
exports.js_assert_some = x => {
assert.ok(x instanceof wasm.OptionClass);
};
exports.js_return_none1 = () => null;
exports.js_return_none2 = () => undefined;
exports.js_return_some = x => x;

exports.js_test_option_classes = () => {
assert.strictEqual(wasm.option_class_none(), undefined);
wasm.option_class_assert_none(undefined);
wasm.option_class_assert_none(null);
const c = wasm.option_class_some();
assert.ok(c instanceof wasm.OptionClass);
wasm.option_class_assert_some(c);
};
41 changes: 41 additions & 0 deletions tests/wasm/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ extern "C" {
fn js_access_fields();
fn js_renamed_export();
fn js_conditional_bindings();

fn js_assert_none(a: Option<OptionClass>);
fn js_assert_some(a: Option<OptionClass>);
fn js_return_none1() -> Option<OptionClass>;
fn js_return_none2() -> Option<OptionClass>;
fn js_return_some(a: OptionClass) -> Option<OptionClass>;
fn js_test_option_classes();
}

#[wasm_bindgen_test]
Expand Down Expand Up @@ -414,7 +421,41 @@ impl ConditionalBindings {
ConditionalBindings {}
}
}

#[wasm_bindgen_test]
fn conditional_bindings() {
js_conditional_bindings();
}

#[wasm_bindgen]
pub struct OptionClass(u32);

#[wasm_bindgen_test]
fn option_class() {
js_assert_none(None);
js_assert_some(Some(OptionClass(1)));
assert!(js_return_none1().is_none());
assert!(js_return_none2().is_none());
assert_eq!(js_return_some(OptionClass(2)).unwrap().0, 2);
js_test_option_classes();
}

#[wasm_bindgen]
pub fn option_class_none() -> Option<OptionClass> {
None
}

#[wasm_bindgen]
pub fn option_class_some() -> Option<OptionClass> {
Some(OptionClass(3))
}

#[wasm_bindgen]
pub fn option_class_assert_none(x: Option<OptionClass>) {
assert!(x.is_none());
}

#[wasm_bindgen]
pub fn option_class_assert_some(x: Option<OptionClass>) {
assert_eq!(x.unwrap().0, 3);
}