mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-15 22:11:23 +00:00
Add support for optional slice types (#507)
* 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
This commit is contained in:
@ -33,6 +33,7 @@ tys! {
|
||||
ENUM
|
||||
RUST_STRUCT
|
||||
CHAR
|
||||
OPTIONAL
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -59,6 +60,7 @@ pub enum Descriptor {
|
||||
Enum,
|
||||
RustStruct(String),
|
||||
Char,
|
||||
Option(Box<Descriptor>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -115,6 +117,7 @@ impl Descriptor {
|
||||
REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data))),
|
||||
SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data))),
|
||||
VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data))),
|
||||
OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data))),
|
||||
STRING => Descriptor::String,
|
||||
ANYREF => Descriptor::Anyref,
|
||||
ENUM => Descriptor::Enum,
|
||||
|
@ -122,20 +122,31 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
let i = self.arg_idx;
|
||||
let name = self.abi_arg();
|
||||
|
||||
let (arg, optional) = match arg {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (arg, false),
|
||||
};
|
||||
|
||||
if let Some(kind) = arg.vector_kind() {
|
||||
self.js_arguments
|
||||
.push((name.clone(), kind.js_ty().to_string()));
|
||||
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
let val = if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name)
|
||||
} else {
|
||||
format!("{}({})", func, name)
|
||||
};
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
const [ptr{i}, len{i}] = {func}({arg});\n\
|
||||
",
|
||||
"const [ptr{i}, len{i}] = {val};",
|
||||
i = i,
|
||||
func = func,
|
||||
arg = name
|
||||
val = val,
|
||||
));
|
||||
if arg.is_by_ref() {
|
||||
if optional {
|
||||
bail!("optional slices aren't currently supported");
|
||||
}
|
||||
if arg.is_mut_ref() {
|
||||
let get = self.cx.memview_function(kind);
|
||||
self.finally(&format!(
|
||||
@ -165,6 +176,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if arg.is_anyref() {
|
||||
self.js_arguments.push((name.clone(), "any".to_string()));
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.rust_arguments.push(format!(
|
||||
"isLikeNone({0}) ? 0 : addHeapObject({0})",
|
||||
name,
|
||||
));
|
||||
} else {
|
||||
self.rust_arguments.push(format!("addHeapObject({})", name));
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if optional {
|
||||
bail!("unsupported optional argument to rust function {:?}", arg);
|
||||
}
|
||||
|
||||
if let Some(s) = arg.rust_struct() {
|
||||
self.js_arguments.push((name.clone(), s.to_string()));
|
||||
|
||||
@ -262,11 +292,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.js_arguments.push((name.clone(), "string".to_string()));
|
||||
self.rust_arguments.push(format!("{}.codePointAt(0)", name))
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
self.js_arguments.push((name.clone(), "any".to_string()));
|
||||
self.cx.expose_add_heap_object();
|
||||
self.rust_arguments.push(format!("addHeapObject({})", name));
|
||||
}
|
||||
_ => bail!("unsupported argument to rust function {:?}", arg),
|
||||
}
|
||||
Ok(self)
|
||||
@ -282,16 +307,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
}
|
||||
};
|
||||
|
||||
if ty.is_ref_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_get_object();
|
||||
self.ret_expr = format!("return getObject(RET);");
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if ty.is_by_ref() {
|
||||
bail!("cannot return references from Rust to JS yet")
|
||||
}
|
||||
let (ty, optional) = match ty {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (ty, false),
|
||||
};
|
||||
|
||||
if let Some(ty) = ty.vector_kind() {
|
||||
self.ret_ty = ty.js_ty().to_string();
|
||||
@ -307,16 +326,42 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
const mem = getUint32Memory();\n\
|
||||
const ptr = mem[retptr / 4];\n\
|
||||
const len = mem[retptr / 4 + 1];\n\
|
||||
{guard}
|
||||
const realRet = {}(ptr, len).slice();\n\
|
||||
wasm.__wbindgen_free(ptr, len * {});\n\
|
||||
return realRet;\n\
|
||||
",
|
||||
f,
|
||||
ty.size()
|
||||
ty.size(),
|
||||
guard = if optional { "if (ptr === 0) return;" } else { "" },
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// No need to worry about `optional` here, the abi representation means
|
||||
// that `takeObject` will naturally pluck out `undefined`.
|
||||
if ty.is_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_take_object();
|
||||
self.ret_expr = format!("return takeObject(RET);");
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if optional {
|
||||
bail!("unsupported optional argument to rust function {:?}", ty);
|
||||
}
|
||||
|
||||
if ty.is_ref_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_get_object();
|
||||
self.ret_expr = format!("return getObject(RET);");
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if ty.is_by_ref() {
|
||||
bail!("cannot return references from Rust to JS yet")
|
||||
}
|
||||
|
||||
if let Some(name) = ty.rust_struct() {
|
||||
self.ret_ty = name.to_string();
|
||||
self.ret_expr = format!("return {name}.__construct(RET);", name = name);
|
||||
@ -360,11 +405,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.ret_ty = "string".to_string();
|
||||
self.ret_expr = format!("return String.fromCodePoint(RET);")
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_take_object();
|
||||
self.ret_expr = format!("return takeObject(RET);");
|
||||
}
|
||||
_ => bail!("unsupported return from Rust to JS {:?}", ty),
|
||||
}
|
||||
Ok(self)
|
||||
|
@ -53,6 +53,8 @@ pub struct SubContext<'a, 'b: 'a> {
|
||||
pub cx: &'a mut Context<'b>,
|
||||
}
|
||||
|
||||
const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
|
||||
let contents = contents.trim();
|
||||
@ -183,28 +185,6 @@ impl<'a> Context<'a> {
|
||||
))
|
||||
})?;
|
||||
|
||||
self.bind("__wbindgen_undefined_new", &|me| {
|
||||
me.expose_add_heap_object();
|
||||
Ok(String::from(
|
||||
"
|
||||
function() {
|
||||
return addHeapObject(undefined);
|
||||
}
|
||||
",
|
||||
))
|
||||
})?;
|
||||
|
||||
self.bind("__wbindgen_null_new", &|me| {
|
||||
me.expose_add_heap_object();
|
||||
Ok(String::from(
|
||||
"
|
||||
function() {
|
||||
return addHeapObject(null);
|
||||
}
|
||||
",
|
||||
))
|
||||
})?;
|
||||
|
||||
self.bind("__wbindgen_is_null", &|me| {
|
||||
me.expose_get_object();
|
||||
Ok(String::from(
|
||||
@ -227,17 +207,6 @@ impl<'a> Context<'a> {
|
||||
))
|
||||
})?;
|
||||
|
||||
self.bind("__wbindgen_boolean_new", &|me| {
|
||||
me.expose_add_heap_object();
|
||||
Ok(String::from(
|
||||
"
|
||||
function(v) {
|
||||
return addHeapObject(v === 1);
|
||||
}
|
||||
",
|
||||
))
|
||||
})?;
|
||||
|
||||
self.bind("__wbindgen_boolean_get", &|me| {
|
||||
me.expose_get_object();
|
||||
Ok(String::from(
|
||||
@ -782,14 +751,16 @@ impl<'a> Context<'a> {
|
||||
"
|
||||
function dropRef(idx) {{
|
||||
{}
|
||||
let obj = slab[idx >> 1];
|
||||
idx = idx >> 1;
|
||||
if (idx < {}) return;
|
||||
let obj = slab[idx];
|
||||
{}
|
||||
// If we hit 0 then free up our space in the slab
|
||||
slab[idx >> 1] = slab_next;
|
||||
slab_next = idx >> 1;
|
||||
slab[idx] = slab_next;
|
||||
slab_next = idx;
|
||||
}}
|
||||
",
|
||||
validate_owned, dec_ref
|
||||
validate_owned, INITIAL_SLAB_VALUES.len(), dec_ref
|
||||
));
|
||||
}
|
||||
|
||||
@ -820,12 +791,9 @@ impl<'a> Context<'a> {
|
||||
if !self.exposed_globals.insert("slab") {
|
||||
return;
|
||||
}
|
||||
let initial_values = [
|
||||
"{ obj: null }",
|
||||
"{ obj: undefined }",
|
||||
"{ obj: true }",
|
||||
"{ obj: false }",
|
||||
];
|
||||
let initial_values = INITIAL_SLAB_VALUES.iter()
|
||||
.map(|s| format!("{{ obj: {} }}", s))
|
||||
.collect::<Vec<_>>();
|
||||
self.global(&format!("const slab = [{}];", initial_values.join(", ")));
|
||||
if self.config.debug {
|
||||
self.export(
|
||||
@ -1575,6 +1543,17 @@ impl<'a> Context<'a> {
|
||||
name
|
||||
}
|
||||
|
||||
fn expose_is_like_none(&mut self) {
|
||||
if !self.exposed_globals.insert("is_like_none") {
|
||||
return
|
||||
}
|
||||
self.global("
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn gc(&mut self) -> Result<(), Error> {
|
||||
let module = mem::replace(self.module, Module::default());
|
||||
let wasm_bytes = parity_wasm::serialize(module)?;
|
||||
|
@ -82,25 +82,36 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
let abi = self.shim_argument();
|
||||
|
||||
let (arg, optional) = match arg {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (arg, false),
|
||||
};
|
||||
|
||||
if let Some(ty) = arg.vector_kind() {
|
||||
let abi2 = self.shim_argument();
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty);
|
||||
self.prelude(&format!(
|
||||
"let v{0} = {func}({0}, {1});",
|
||||
"let v{0} = {prefix}{func}({0}, {1});",
|
||||
abi,
|
||||
abi2,
|
||||
func = f
|
||||
func = f,
|
||||
prefix = if optional { format!("{} == 0 ? undefined : ", abi) } else { String::new() },
|
||||
));
|
||||
|
||||
if !arg.is_by_ref() {
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
v{0} = v{0}.slice();\n\
|
||||
wasm.__wbindgen_free({0}, {1} * {size});\
|
||||
{start}
|
||||
v{0} = v{0}.slice();
|
||||
wasm.__wbindgen_free({0}, {1} * {size});
|
||||
{end}\
|
||||
",
|
||||
abi,
|
||||
abi2,
|
||||
size = ty.size()
|
||||
size = ty.size(),
|
||||
start = if optional { format!("if ({} !== 0) {{", abi) } else { String::new() },
|
||||
end = if optional { "}" } else { "" },
|
||||
|
||||
));
|
||||
self.cx.require_internal_export("__wbindgen_free")?;
|
||||
}
|
||||
@ -108,6 +119,18 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// No need to special case `optional` here because `takeObject` will
|
||||
// naturally work.
|
||||
if arg.is_anyref() {
|
||||
self.cx.expose_take_object();
|
||||
self.js_arguments.push(format!("takeObject({})", abi));
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if optional {
|
||||
bail!("unsupported optional argument {:?}", arg);
|
||||
}
|
||||
|
||||
if let Some(signed) = arg.get_64bit() {
|
||||
let f = if signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
@ -233,10 +256,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
ref d if d.is_number() => abi,
|
||||
Descriptor::Boolean => format!("{} !== 0", abi),
|
||||
Descriptor::Char => format!("String.fromCodePoint({})", abi),
|
||||
Descriptor::Anyref => {
|
||||
self.cx.expose_take_object();
|
||||
format!("takeObject({})", abi)
|
||||
}
|
||||
ref d if d.is_ref_anyref() => {
|
||||
self.cx.expose_get_object();
|
||||
format!("getObject({})", abi)
|
||||
@ -258,6 +277,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let (ty, optional) = match ty {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (ty, false),
|
||||
};
|
||||
if ty.is_by_ref() {
|
||||
bail!("cannot return a reference from JS to Rust")
|
||||
}
|
||||
@ -265,17 +288,43 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
let f = self.cx.pass_to_wasm_function(ty)?;
|
||||
self.cx.expose_uint32_memory();
|
||||
self.shim_arguments.insert(0, "ret".to_string());
|
||||
let mut prelude = String::new();
|
||||
let expr = if optional {
|
||||
prelude.push_str("const val = JS;");
|
||||
self.cx.expose_is_like_none();
|
||||
format!("isLikeNone(val) ? [0, 0] : {}(val)", f)
|
||||
} else {
|
||||
format!("{}(JS)", f)
|
||||
};
|
||||
self.ret_expr = format!(
|
||||
"\
|
||||
const [retptr, retlen] = {}(JS);\n\
|
||||
{}
|
||||
const [retptr, retlen] = {};
|
||||
const mem = getUint32Memory();
|
||||
mem[ret / 4] = retptr;
|
||||
mem[ret / 4 + 1] = retlen;
|
||||
",
|
||||
f
|
||||
prelude,
|
||||
expr
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if ty.is_anyref() {
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.ret_expr = "
|
||||
const val = JS;
|
||||
return isLikeNone(val) ? 0 : addHeapObject(val);
|
||||
".to_string();
|
||||
} else {
|
||||
self.ret_expr = "return addHeapObject(JS);".to_string()
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
if optional {
|
||||
bail!("unsupported optional return type {:?}", ty);
|
||||
}
|
||||
if ty.is_number() {
|
||||
self.ret_expr = "return JS;".to_string();
|
||||
return Ok(());
|
||||
@ -324,10 +373,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
self.ret_expr = match *ty {
|
||||
Descriptor::Boolean => "return JS ? 1 : 0;".to_string(),
|
||||
Descriptor::Char => "return JS.codePointAt(0);".to_string(),
|
||||
Descriptor::Anyref => {
|
||||
self.cx.expose_add_heap_object();
|
||||
"return addHeapObject(JS);".to_string()
|
||||
}
|
||||
_ => bail!("unimplemented return from JS to Rust: {:?}", ty),
|
||||
};
|
||||
Ok(())
|
||||
|
Reference in New Issue
Block a user