Skip to content

Commit cbeb301

Browse files
authored
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
1 parent 6eef5f7 commit cbeb301

File tree

20 files changed

+1214
-725
lines changed

20 files changed

+1214
-725
lines changed

crates/backend/src/codegen.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ impl ToTokens for ast::ImportType {
513513
}
514514
}
515515

516+
impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name {
517+
fn none() -> Self::Abi { 0 }
518+
}
519+
516520
impl ::wasm_bindgen::convert::FromWasmAbi for #name {
517521
type Abi = <::wasm_bindgen::JsValue as
518522
::wasm_bindgen::convert::FromWasmAbi>::Abi;
@@ -527,6 +531,10 @@ impl ToTokens for ast::ImportType {
527531
}
528532
}
529533

534+
impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name {
535+
fn is_none(abi: &Self::Abi) -> bool { *abi == 0 }
536+
}
537+
530538
impl<'a> ::wasm_bindgen::convert::IntoWasmAbi for &'a #name {
531539
type Abi = <&'a ::wasm_bindgen::JsValue as
532540
::wasm_bindgen::convert::IntoWasmAbi>::Abi;

crates/cli-support/src/descriptor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ tys! {
3333
ENUM
3434
RUST_STRUCT
3535
CHAR
36+
OPTIONAL
3637
}
3738

3839
#[derive(Debug)]
@@ -59,6 +60,7 @@ pub enum Descriptor {
5960
Enum,
6061
RustStruct(String),
6162
Char,
63+
Option(Box<Descriptor>),
6264
}
6365

6466
#[derive(Debug)]
@@ -115,6 +117,7 @@ impl Descriptor {
115117
REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data))),
116118
SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data))),
117119
VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data))),
120+
OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data))),
118121
STRING => Descriptor::String,
119122
ANYREF => Descriptor::Anyref,
120123
ENUM => Descriptor::Enum,

crates/cli-support/src/js/js2rust.rs

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,31 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
122122
let i = self.arg_idx;
123123
let name = self.abi_arg();
124124

125+
let (arg, optional) = match arg {
126+
Descriptor::Option(t) => (&**t, true),
127+
_ => (arg, false),
128+
};
129+
125130
if let Some(kind) = arg.vector_kind() {
126131
self.js_arguments
127132
.push((name.clone(), kind.js_ty().to_string()));
128133

129134
let func = self.cx.pass_to_wasm_function(kind)?;
135+
let val = if optional {
136+
self.cx.expose_is_like_none();
137+
format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name)
138+
} else {
139+
format!("{}({})", func, name)
140+
};
130141
self.prelude(&format!(
131-
"\
132-
const [ptr{i}, len{i}] = {func}({arg});\n\
133-
",
142+
"const [ptr{i}, len{i}] = {val};",
134143
i = i,
135-
func = func,
136-
arg = name
144+
val = val,
137145
));
138146
if arg.is_by_ref() {
147+
if optional {
148+
bail!("optional slices aren't currently supported");
149+
}
139150
if arg.is_mut_ref() {
140151
let get = self.cx.memview_function(kind);
141152
self.finally(&format!(
@@ -165,6 +176,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
165176
return Ok(self);
166177
}
167178

179+
if arg.is_anyref() {
180+
self.js_arguments.push((name.clone(), "any".to_string()));
181+
self.cx.expose_add_heap_object();
182+
if optional {
183+
self.cx.expose_is_like_none();
184+
self.rust_arguments.push(format!(
185+
"isLikeNone({0}) ? 0 : addHeapObject({0})",
186+
name,
187+
));
188+
} else {
189+
self.rust_arguments.push(format!("addHeapObject({})", name));
190+
}
191+
return Ok(self);
192+
}
193+
194+
if optional {
195+
bail!("unsupported optional argument to rust function {:?}", arg);
196+
}
197+
168198
if let Some(s) = arg.rust_struct() {
169199
self.js_arguments.push((name.clone(), s.to_string()));
170200

@@ -262,11 +292,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
262292
self.js_arguments.push((name.clone(), "string".to_string()));
263293
self.rust_arguments.push(format!("{}.codePointAt(0)", name))
264294
}
265-
Descriptor::Anyref => {
266-
self.js_arguments.push((name.clone(), "any".to_string()));
267-
self.cx.expose_add_heap_object();
268-
self.rust_arguments.push(format!("addHeapObject({})", name));
269-
}
270295
_ => bail!("unsupported argument to rust function {:?}", arg),
271296
}
272297
Ok(self)
@@ -282,16 +307,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
282307
}
283308
};
284309

285-
if ty.is_ref_anyref() {
286-
self.ret_ty = "any".to_string();
287-
self.cx.expose_get_object();
288-
self.ret_expr = format!("return getObject(RET);");
289-
return Ok(self);
290-
}
291-
292-
if ty.is_by_ref() {
293-
bail!("cannot return references from Rust to JS yet")
294-
}
310+
let (ty, optional) = match ty {
311+
Descriptor::Option(t) => (&**t, true),
312+
_ => (ty, false),
313+
};
295314

296315
if let Some(ty) = ty.vector_kind() {
297316
self.ret_ty = ty.js_ty().to_string();
@@ -307,16 +326,42 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
307326
const mem = getUint32Memory();\n\
308327
const ptr = mem[retptr / 4];\n\
309328
const len = mem[retptr / 4 + 1];\n\
329+
{guard}
310330
const realRet = {}(ptr, len).slice();\n\
311331
wasm.__wbindgen_free(ptr, len * {});\n\
312332
return realRet;\n\
313333
",
314334
f,
315-
ty.size()
335+
ty.size(),
336+
guard = if optional { "if (ptr === 0) return;" } else { "" },
316337
);
317338
return Ok(self);
318339
}
319340

341+
// No need to worry about `optional` here, the abi representation means
342+
// that `takeObject` will naturally pluck out `undefined`.
343+
if ty.is_anyref() {
344+
self.ret_ty = "any".to_string();
345+
self.cx.expose_take_object();
346+
self.ret_expr = format!("return takeObject(RET);");
347+
return Ok(self);
348+
}
349+
350+
if optional {
351+
bail!("unsupported optional argument to rust function {:?}", ty);
352+
}
353+
354+
if ty.is_ref_anyref() {
355+
self.ret_ty = "any".to_string();
356+
self.cx.expose_get_object();
357+
self.ret_expr = format!("return getObject(RET);");
358+
return Ok(self);
359+
}
360+
361+
if ty.is_by_ref() {
362+
bail!("cannot return references from Rust to JS yet")
363+
}
364+
320365
if let Some(name) = ty.rust_struct() {
321366
self.ret_ty = name.to_string();
322367
self.ret_expr = format!("return {name}.__construct(RET);", name = name);
@@ -360,11 +405,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
360405
self.ret_ty = "string".to_string();
361406
self.ret_expr = format!("return String.fromCodePoint(RET);")
362407
}
363-
Descriptor::Anyref => {
364-
self.ret_ty = "any".to_string();
365-
self.cx.expose_take_object();
366-
self.ret_expr = format!("return takeObject(RET);");
367-
}
368408
_ => bail!("unsupported return from Rust to JS {:?}", ty),
369409
}
370410
Ok(self)

crates/cli-support/src/js/mod.rs

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub struct SubContext<'a, 'b: 'a> {
5353
pub cx: &'a mut Context<'b>,
5454
}
5555

56+
const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
57+
5658
impl<'a> Context<'a> {
5759
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
5860
let contents = contents.trim();
@@ -183,28 +185,6 @@ impl<'a> Context<'a> {
183185
))
184186
})?;
185187

186-
self.bind("__wbindgen_undefined_new", &|me| {
187-
me.expose_add_heap_object();
188-
Ok(String::from(
189-
"
190-
function() {
191-
return addHeapObject(undefined);
192-
}
193-
",
194-
))
195-
})?;
196-
197-
self.bind("__wbindgen_null_new", &|me| {
198-
me.expose_add_heap_object();
199-
Ok(String::from(
200-
"
201-
function() {
202-
return addHeapObject(null);
203-
}
204-
",
205-
))
206-
})?;
207-
208188
self.bind("__wbindgen_is_null", &|me| {
209189
me.expose_get_object();
210190
Ok(String::from(
@@ -227,17 +207,6 @@ impl<'a> Context<'a> {
227207
))
228208
})?;
229209

230-
self.bind("__wbindgen_boolean_new", &|me| {
231-
me.expose_add_heap_object();
232-
Ok(String::from(
233-
"
234-
function(v) {
235-
return addHeapObject(v === 1);
236-
}
237-
",
238-
))
239-
})?;
240-
241210
self.bind("__wbindgen_boolean_get", &|me| {
242211
me.expose_get_object();
243212
Ok(String::from(
@@ -782,14 +751,16 @@ impl<'a> Context<'a> {
782751
"
783752
function dropRef(idx) {{
784753
{}
785-
let obj = slab[idx >> 1];
754+
idx = idx >> 1;
755+
if (idx < {}) return;
756+
let obj = slab[idx];
786757
{}
787758
// If we hit 0 then free up our space in the slab
788-
slab[idx >> 1] = slab_next;
789-
slab_next = idx >> 1;
759+
slab[idx] = slab_next;
760+
slab_next = idx;
790761
}}
791762
",
792-
validate_owned, dec_ref
763+
validate_owned, INITIAL_SLAB_VALUES.len(), dec_ref
793764
));
794765
}
795766

@@ -820,12 +791,9 @@ impl<'a> Context<'a> {
820791
if !self.exposed_globals.insert("slab") {
821792
return;
822793
}
823-
let initial_values = [
824-
"{ obj: null }",
825-
"{ obj: undefined }",
826-
"{ obj: true }",
827-
"{ obj: false }",
828-
];
794+
let initial_values = INITIAL_SLAB_VALUES.iter()
795+
.map(|s| format!("{{ obj: {} }}", s))
796+
.collect::<Vec<_>>();
829797
self.global(&format!("const slab = [{}];", initial_values.join(", ")));
830798
if self.config.debug {
831799
self.export(
@@ -1575,6 +1543,17 @@ impl<'a> Context<'a> {
15751543
name
15761544
}
15771545

1546+
fn expose_is_like_none(&mut self) {
1547+
if !self.exposed_globals.insert("is_like_none") {
1548+
return
1549+
}
1550+
self.global("
1551+
function isLikeNone(x) {
1552+
return x === undefined || x === null;
1553+
}
1554+
");
1555+
}
1556+
15781557
fn gc(&mut self) -> Result<(), Error> {
15791558
let module = mem::replace(self.module, Module::default());
15801559
let wasm_bytes = parity_wasm::serialize(module)?;

0 commit comments

Comments
 (0)