mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-27 12:06:14 +03:00
cbeb301371
* Shard the `convert.rs` module into sub-modules Hopefully this'll make the organization a little nicer over time! * Start adding support for optional types This commit starts adding support for optional types to wasm-bindgen as arguments/return values to functions. The strategy here is to add two new traits, `OptionIntoWasmAbi` and `OptionFromWasmAbi`. These two traits are used as a blanket impl to implement `IntoWasmAbi` and `FromWasmAbi` for `Option<T>`. Some consequences of this design: * It should be possible to ensure `Option<SomeForeignType>` implements to/from wasm traits. This is because the option-based traits can be implemented for foreign types. * A specialized implementation is possible for all types, so there's no need for `Option<T>` to introduce unnecessary overhead. * Two new traits is a bit unforutnate but I can't currently think of an alternative design that works for the above two constraints, although it doesn't mean one doesn't exist! * The error messages for "can't use this type here" is actually halfway decent because it says these new traits need to be implemented, which provides a good place to document and talk about what's going on here! * Nested references like `Option<&T>` can't implement `FromWasmAbi`. This means that you can't define a function in Rust which takes `Option<&str>`. It may be possible to do this one day but it'll likely require more trait trickery than I'm capable of right now. * Add support for optional slices This commit adds support for optional slice types, things like strings and arrays. The null representation of these has a pointer value of 0, which should never happen in normal Rust. Otherwise the various plumbing is done throughout the tooling to enable these types in all locations. * Fix `takeObject` on global sentinels These don't have a reference count as they're always expected to work, so avoid actually dropping a reference on them. * Remove some no longer needed bindings * Add support for optional anyref types This commit adds support for optional imported class types. Each type imported with `#[wasm_bindgen]` automatically implements the relevant traits and now supports `Option<Foo>` in various argument/return positions. * Fix building without the `std` feature * Actually fix the build... * Add support for optional types to WebIDL Closes #502
565 lines
15 KiB
Rust
565 lines
15 KiB
Rust
use super::project;
|
|
|
|
#[test]
|
|
fn add() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros)]
|
|
|
|
extern crate wasm_bindgen;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn add(a: u32, b: u32) -> u32 {
|
|
a + b
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn add3(a: u32) -> u32 {
|
|
a + 3
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn get2(_b: bool) -> u32 {
|
|
2
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn return_and_take_bool(a: bool, b: bool) -> bool {
|
|
a && b
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn raw_pointers_work(a: *mut u32, b: *const u8) -> *const u32 {
|
|
unsafe {
|
|
(*a) = (*b) as u32;
|
|
return a
|
|
}
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as assert from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
assert.strictEqual(wasm.add(1, 2), 3);
|
|
assert.strictEqual(wasm.add(2, 3), 5);
|
|
assert.strictEqual(wasm.add3(2), 5);
|
|
assert.strictEqual(wasm.get2(true), 2);
|
|
assert.strictEqual(wasm.return_and_take_bool(true, false), false);
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn add_headless() {
|
|
project()
|
|
.headless(true)
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros)]
|
|
extern crate wasm_bindgen;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn add(a: u32, b: u32) -> u32 {
|
|
a + b
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as assert from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
console.log("start `add_headless` test");
|
|
assert.strictEqual(wasm.add(1, 2), 3);
|
|
console.log("end `add_headless` test");
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn string_arguments() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros)]
|
|
|
|
extern crate wasm_bindgen;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn assert_foo_and_bar(a: &str, b: &str) {
|
|
assert_eq!(a, "foo2");
|
|
assert_eq!(b, "bar");
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn assert_foo(a: &str) {
|
|
assert_eq!(a, "foo");
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
wasm.assert_foo("foo");
|
|
wasm.assert_foo_and_bar("foo2", "bar");
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn return_a_string() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros)]
|
|
|
|
extern crate wasm_bindgen;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn clone(a: &str) -> String {
|
|
a.to_string()
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn concat(a: &str, b: &str, c: i8) -> String {
|
|
format!("{} {} {}", a, b, c)
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as assert from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
assert.strictEqual(wasm.clone("foo"), "foo");
|
|
assert.strictEqual(wasm.clone("another"), "another");
|
|
assert.strictEqual(wasm.concat("a", "b", 3), "a b 3");
|
|
assert.strictEqual(wasm.concat("c", "d", -2), "c d -2");
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn exceptions() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros)]
|
|
|
|
extern crate wasm_bindgen;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn foo(_a: u32) {}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn bar(_a: &str) {}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as assert from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
assert.throws(() => wasm.foo('a'), /expected a number argument/);
|
|
assert.throws(() => wasm.bar(3), /expected a string argument/);
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
// #[test]
|
|
// fn other_imports() {
|
|
// project()
|
|
// .file("src/lib.rs", r#"
|
|
// #![feature(use_extern_macros)]
|
|
//
|
|
// extern crate wasm_bindgen;
|
|
//
|
|
// use wasm_bindgen::prelude::*;
|
|
//
|
|
// extern {
|
|
// fn another_import(a: u32);
|
|
// }
|
|
//
|
|
// wasm_bindgen! {
|
|
// pub fn foo(a: u32) {
|
|
// unsafe { another_import(a); }
|
|
// }
|
|
// }
|
|
// "#)
|
|
// .file("test.js", r#"
|
|
// import * as assert from "assert";
|
|
// import * as wasm from "./out";
|
|
//
|
|
// let ARG: number | null = null;
|
|
//
|
|
// export function test() {
|
|
// wasm.foo(2);
|
|
// assert.strictEqual(ARG, 2);
|
|
// }
|
|
// "#)
|
|
// .test();
|
|
// }
|
|
|
|
#[test]
|
|
fn other_exports() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#[no_mangle]
|
|
pub extern fn foo(_a: u32) {
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as wasm from "./out_bg";
|
|
|
|
export function test() {
|
|
wasm.foo(2);
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn no_std() {
|
|
project()
|
|
.no_std(true)
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros, wasm_import_module)]
|
|
#![no_std]
|
|
#![allow(dead_code)]
|
|
|
|
extern crate wasm_bindgen;
|
|
extern crate std as _some_other_name;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen(module = "./foo")]
|
|
extern {
|
|
fn test(a: &str);
|
|
|
|
type Js;
|
|
#[wasm_bindgen(constructor)]
|
|
fn new() -> Js;
|
|
#[wasm_bindgen(method)]
|
|
fn init(this: &Js);
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn foo(_a: u32) {}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as wasm from "./out_bg";
|
|
|
|
export function test() {
|
|
// mostly just testing the project compiles here
|
|
wasm.foo(1);
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"foo.js",
|
|
r#"
|
|
export class Js {
|
|
init() {
|
|
}
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn no_std_class() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros, wasm_import_module)]
|
|
#![no_std]
|
|
#![allow(dead_code)]
|
|
|
|
extern crate wasm_bindgen;
|
|
extern crate std as _some_other_name;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
extern {
|
|
fn test(a: &str);
|
|
|
|
type Js;
|
|
#[wasm_bindgen(constructor)]
|
|
fn new() -> Js;
|
|
#[wasm_bindgen(method, structural)]
|
|
fn init(this: &Js);
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn foo(_a: u32) {}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct A {}
|
|
|
|
#[wasm_bindgen]
|
|
impl A {
|
|
pub fn foo(&self) {}
|
|
pub fn bar(&mut self) {}
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as wasm from "./out_bg";
|
|
|
|
export function test() {
|
|
// mostly just testing the project compiles here
|
|
wasm.foo(1);
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn jsvalue_typeof() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros, wasm_import_module)]
|
|
extern crate wasm_bindgen;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
pub fn is_object(val: &JsValue) -> bool {
|
|
val.is_object()
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn is_function(val: &JsValue) -> bool {
|
|
val.is_function()
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn is_string(val: &JsValue) -> bool {
|
|
val.is_string()
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"test.js",
|
|
r#"
|
|
import * as assert from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function test() {
|
|
assert.ok(wasm.is_object({}));
|
|
assert.ok(!wasm.is_object(42));
|
|
assert.ok(wasm.is_function(function() {}));
|
|
assert.ok(!wasm.is_function(42));
|
|
assert.ok(wasm.is_string("2b or !2b"));
|
|
assert.ok(!wasm.is_string(42));
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn binding_to_unimplemented_apis_doesnt_break_everything() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros, wasm_import_module)]
|
|
extern crate wasm_bindgen;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen]
|
|
extern {
|
|
#[derive(Clone)]
|
|
type Array;
|
|
|
|
#[wasm_bindgen(constructor)]
|
|
fn new() -> Array;
|
|
|
|
#[wasm_bindgen(method, catch)]
|
|
fn standardized_method_this_js_runtime_doesnt_implement_yet(this: &Array)
|
|
-> Result<(), JsValue>;
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn test() {
|
|
let array = Array::new();
|
|
let res = array.standardized_method_this_js_runtime_doesnt_implement_yet();
|
|
assert!(res.is_err());
|
|
}
|
|
"#,
|
|
)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn optional_slices() {
|
|
project()
|
|
.file(
|
|
"src/lib.rs",
|
|
r#"
|
|
#![feature(use_extern_macros, wasm_custom_section, wasm_import_module)]
|
|
extern crate wasm_bindgen;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[wasm_bindgen(module = "./foo")]
|
|
extern {
|
|
fn optional_str_none(a: Option<&str>);
|
|
fn optional_str_some(a: Option<&str>);
|
|
fn optional_slice_none(a: Option<&[u8]>);
|
|
fn optional_slice_some(a: Option<&[u8]>);
|
|
|
|
fn optional_string_none(a: Option<String>);
|
|
fn optional_string_some(a: Option<String>);
|
|
fn optional_string_some_empty(a: Option<String>);
|
|
|
|
fn return_string_none() -> Option<String>;
|
|
fn return_string_some() -> Option<String>;
|
|
|
|
fn run_rust_tests();
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn test() {
|
|
optional_str_none(None);
|
|
optional_str_some(Some("x"));
|
|
optional_slice_none(None);
|
|
optional_slice_some(Some(&[1, 2, 3]));
|
|
optional_string_none(None);
|
|
optional_string_some_empty(Some(String::new()));
|
|
optional_string_some(Some("abcd".to_string()));
|
|
|
|
assert_eq!(return_string_none(), None);
|
|
assert_eq!(return_string_some(), Some("foo".to_string()));
|
|
run_rust_tests();
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn take_optional_str_none(x: Option<String>) {
|
|
assert!(x.is_none())
|
|
}
|
|
#[wasm_bindgen]
|
|
pub fn take_optional_str_some(x: Option<String>) {
|
|
assert_eq!(x, Some(String::from("hello")));
|
|
}
|
|
#[wasm_bindgen]
|
|
pub fn return_optional_str_none() -> Option<String> {
|
|
None
|
|
}
|
|
#[wasm_bindgen]
|
|
pub fn return_optional_str_some() -> Option<String> {
|
|
Some("world".to_string())
|
|
}
|
|
"#,
|
|
)
|
|
.file(
|
|
"foo.js",
|
|
r#"
|
|
import { strictEqual } from "assert";
|
|
import * as wasm from "./out";
|
|
|
|
export function optional_str_none(x) {
|
|
strictEqual(x, undefined);
|
|
}
|
|
|
|
export function optional_str_some(x) {
|
|
strictEqual(x, 'x');
|
|
}
|
|
|
|
export function optional_slice_none(x) {
|
|
strictEqual(x, undefined);
|
|
}
|
|
|
|
export function optional_slice_some(x) {
|
|
strictEqual(x.length, 3);
|
|
strictEqual(x[0], 1);
|
|
strictEqual(x[1], 2);
|
|
strictEqual(x[2], 3);
|
|
}
|
|
|
|
export function optional_string_none(x) {
|
|
strictEqual(x, undefined);
|
|
}
|
|
|
|
export function optional_string_some(x) {
|
|
strictEqual(x, 'abcd');
|
|
}
|
|
|
|
export function optional_string_some_empty(x) {
|
|
strictEqual(x, '');
|
|
}
|
|
|
|
export function return_string_none() {}
|
|
export function return_string_some() {
|
|
return 'foo';
|
|
}
|
|
|
|
export function run_rust_tests() {
|
|
wasm.take_optional_str_none();
|
|
wasm.take_optional_str_none(null);
|
|
wasm.take_optional_str_none(undefined);
|
|
wasm.take_optional_str_some('hello');
|
|
strictEqual(wasm.return_optional_str_none(), undefined);
|
|
strictEqual(wasm.return_optional_str_some(), 'world');
|
|
}
|
|
"#
|
|
)
|
|
.test();
|
|
}
|