Skip to content

Commit

Permalink
Merge pull request #1275 from alexcrichton/option-classes
Browse files Browse the repository at this point in the history
Support `Option<RustStruct>` in arguments/returns
  • Loading branch information
fitzgen committed Feb 19, 2019
2 parents e498d99 + f831711 commit 32b72a3
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 35 deletions.
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);
}

0 comments on commit 32b72a3

Please sign in to comment.