From f831711f5d2d9f4e56d691a7e5d3aa37066fb164 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Feb 2019 09:08:37 -0800 Subject: [PATCH] Support `Option` in arguments/returns Add all the necessary support in a few locations and we should be good to go! Closes #1252 --- crates/backend/src/codegen.rs | 11 ++++ crates/cli-support/src/js/js2rust.rs | 84 ++++++++++++++++------------ crates/cli-support/src/js/rust2js.rs | 25 +++++++++ tests/wasm/classes.js | 19 +++++++ tests/wasm/classes.rs | 41 ++++++++++++++ 5 files changed, 145 insertions(+), 35 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 92d8cad49b1..7b0eef94c75 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -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); diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index af964b1231a..4dc4e8b24c0 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -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 @@ -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); @@ -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 @@ -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, + )); + } } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 284ce5bcfc4..a9a84d02714 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -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 @@ -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 diff --git a/tests/wasm/classes.js b/tests/wasm/classes.js index 1c3ef4ccab7..2e9512c812b 100644 --- a/tests/wasm/classes.js +++ b/tests/wasm/classes.js @@ -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); +}; diff --git a/tests/wasm/classes.rs b/tests/wasm/classes.rs index 4d1999928b5..024848b0c9b 100644 --- a/tests/wasm/classes.rs +++ b/tests/wasm/classes.rs @@ -23,6 +23,13 @@ extern "C" { fn js_access_fields(); fn js_renamed_export(); fn js_conditional_bindings(); + + fn js_assert_none(a: Option); + fn js_assert_some(a: Option); + fn js_return_none1() -> Option; + fn js_return_none2() -> Option; + fn js_return_some(a: OptionClass) -> Option; + fn js_test_option_classes(); } #[wasm_bindgen_test] @@ -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 { + None +} + +#[wasm_bindgen] +pub fn option_class_some() -> Option { + Some(OptionClass(3)) +} + +#[wasm_bindgen] +pub fn option_class_assert_none(x: Option) { + assert!(x.is_none()); +} + +#[wasm_bindgen] +pub fn option_class_assert_some(x: Option) { + assert_eq!(x.unwrap().0, 3); +}