Skip to content

Commit 350fb8c

Browse files
committed
Add support for optional slice types
1 parent e99c6b7 commit 350fb8c

File tree

10 files changed

+262
-40
lines changed

10 files changed

+262
-40
lines changed

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: 42 additions & 16 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,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
165176
return Ok(self);
166177
}
167178

179+
if optional {
180+
bail!("unsupported optional argument to rust function {:?}", arg);
181+
}
182+
168183
if let Some(s) = arg.rust_struct() {
169184
self.js_arguments.push((name.clone(), s.to_string()));
170185

@@ -282,16 +297,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
282297
}
283298
};
284299

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-
}
300+
let (ty, optional) = match ty {
301+
Descriptor::Option(t) => (&**t, true),
302+
_ => (ty, false),
303+
};
295304

296305
if let Some(ty) = ty.vector_kind() {
297306
self.ret_ty = ty.js_ty().to_string();
@@ -307,16 +316,33 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
307316
const mem = getUint32Memory();\n\
308317
const ptr = mem[retptr / 4];\n\
309318
const len = mem[retptr / 4 + 1];\n\
319+
{guard}
310320
const realRet = {}(ptr, len).slice();\n\
311321
wasm.__wbindgen_free(ptr, len * {});\n\
312322
return realRet;\n\
313323
",
314324
f,
315-
ty.size()
325+
ty.size(),
326+
guard = if optional { "if (ptr === 0) return;" } else { "" },
316327
);
317328
return Ok(self);
318329
}
319330

331+
if optional {
332+
bail!("unsupported optional argument to rust function {:?}", ty);
333+
}
334+
335+
if ty.is_ref_anyref() {
336+
self.ret_ty = "any".to_string();
337+
self.cx.expose_get_object();
338+
self.ret_expr = format!("return getObject(RET);");
339+
return Ok(self);
340+
}
341+
342+
if ty.is_by_ref() {
343+
bail!("cannot return references from Rust to JS yet")
344+
}
345+
320346
if let Some(name) = ty.rust_struct() {
321347
self.ret_ty = name.to_string();
322348
self.ret_expr = format!("return {name}.__construct(RET);", name = name);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,17 @@ impl<'a> Context<'a> {
15751575
name
15761576
}
15771577

1578+
fn expose_is_like_none(&mut self) {
1579+
if !self.exposed_globals.insert("is_like_none") {
1580+
return
1581+
}
1582+
self.global("
1583+
function isLikeNone(x) {
1584+
return x === undefined || x === null;
1585+
}
1586+
");
1587+
}
1588+
15781589
fn gc(&mut self) -> Result<(), Error> {
15791590
let module = mem::replace(self.module, Module::default());
15801591
let wasm_bytes = parity_wasm::serialize(module)?;

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,32 +82,47 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
8282
fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> {
8383
let abi = self.shim_argument();
8484

85+
let (arg, optional) = match arg {
86+
Descriptor::Option(t) => (&**t, true),
87+
_ => (arg, false),
88+
};
89+
8590
if let Some(ty) = arg.vector_kind() {
8691
let abi2 = self.shim_argument();
8792
let f = self.cx.expose_get_vector_from_wasm(ty);
8893
self.prelude(&format!(
89-
"let v{0} = {func}({0}, {1});",
94+
"let v{0} = {prefix}{func}({0}, {1});",
9095
abi,
9196
abi2,
92-
func = f
97+
func = f,
98+
prefix = if optional { format!("{} == 0 ? undefined : ", abi) } else { String::new() },
9399
));
94100

95101
if !arg.is_by_ref() {
96102
self.prelude(&format!(
97103
"\
98-
v{0} = v{0}.slice();\n\
99-
wasm.__wbindgen_free({0}, {1} * {size});\
104+
{start}
105+
v{0} = v{0}.slice();
106+
wasm.__wbindgen_free({0}, {1} * {size});
107+
{end}\
100108
",
101109
abi,
102110
abi2,
103-
size = ty.size()
111+
size = ty.size(),
112+
start = if optional { format!("if ({} !== 0) {{", abi) } else { String::new() },
113+
end = if optional { "}" } else { "" },
114+
104115
));
105116
self.cx.require_internal_export("__wbindgen_free")?;
106117
}
107118
self.js_arguments.push(format!("v{}", abi));
108119
return Ok(());
109120
}
110121

122+
if optional {
123+
bail!("unsupported optional argument {:?}", arg);
124+
}
125+
111126
if let Some(signed) = arg.get_64bit() {
112127
let f = if signed {
113128
self.cx.expose_int64_cvt_shim()
@@ -258,24 +273,41 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
258273
return Ok(());
259274
}
260275
};
276+
let (ty, optional) = match ty {
277+
Descriptor::Option(t) => (&**t, true),
278+
_ => (ty, false),
279+
};
261280
if ty.is_by_ref() {
262281
bail!("cannot return a reference from JS to Rust")
263282
}
264283
if let Some(ty) = ty.vector_kind() {
265284
let f = self.cx.pass_to_wasm_function(ty)?;
266285
self.cx.expose_uint32_memory();
267286
self.shim_arguments.insert(0, "ret".to_string());
287+
let mut prelude = String::new();
288+
let expr = if optional {
289+
prelude.push_str("const val = JS;");
290+
self.cx.expose_is_like_none();
291+
format!("isLikeNone(val) ? [0, 0] : {}(val)", f)
292+
} else {
293+
format!("{}(JS)", f)
294+
};
268295
self.ret_expr = format!(
269296
"\
270-
const [retptr, retlen] = {}(JS);\n\
297+
{}
298+
const [retptr, retlen] = {};
271299
const mem = getUint32Memory();
272300
mem[ret / 4] = retptr;
273301
mem[ret / 4 + 1] = retlen;
274302
",
275-
f
303+
prelude,
304+
expr
276305
);
277306
return Ok(());
278307
}
308+
if optional {
309+
bail!("unsupported optional return type {:?}", ty);
310+
}
279311
if ty.is_number() {
280312
self.ret_expr = "return JS;".to_string();
281313
return Ok(());

guide/src/feature-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ are:
2828
* Borrowed exported structs (`&Foo` or `&mut Bar`)
2929
* The `JsValue` type and `&JsValue` (not mutable references)
3030
* Vectors and slices of supported integer types and of the `JsValue` type.
31+
* Optional vectors/slices
3132

3233
All of the above can also be returned except borrowed references. Passing
3334
`Vec<JsValue>` as an argument to a function is not currently supported. Strings are

guide/src/reference.md

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@
22

33
The table below provides an overview of all the types that wasm-bindgen can send/receive across the wasm ABI boundary.
44

5-
| Type | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value |
6-
|:---:|:---:|:---:|:---:|:---:|
7-
| `str` | No | Yes | No | Yes |
8-
| `char` | Yes | No | No | Yes |
9-
| `bool` | Yes | No | No | Yes |
10-
| `JsValue` | Yes | Yes | Yes | Yes |
11-
| `Box<[JsValue]>` | Yes | No | No | Yes |
12-
| `*const T` | Yes | No | No | Yes |
13-
| `*mut T` | Yes | No | No | Yes |
14-
| `u8` `i8` `u16` `i16` `u64` `i64` `isize` `size` | Yes | No | No | Yes |
15-
| `u32` `i32` `f32` `f64` | Yes | Yes | Yes | Yes |
16-
| `Box<[u8]>` `Box<[i8]>` `Box<[u16]>` `Box<[i16]>` `Box<[u32]>` `Box<[i32]>` `Box<[u64]>` `Box<[i64]>` | Yes | No | No | Yes |
17-
| `Box<[f32]>` `Box<[f64]>` | Yes | No | No | Yes |
18-
| `[u8]` `[i8]` `[u16]` `[i16]` `[u32]` `[i32]` `[u64]` `[i64]` | No | Yes | Yes | No |
19-
| `&[u8]` `&mut[u8]` `&[i8]` `&mut[i8]` `&[u16]` `&mut[u16]` `&[i16]` `&mut[i16]` `&[u32]` `&mut[u32]` `&[i32]` `&mut[i32]` `&[u64]` `&mut[u64]` `&[i64]` `&mut[i64]` | No | No | No | Yes |
20-
| `[f32]` `[f64]` | No | Yes | Yes | No |
21-
| `&[f32]` `&mut[f32]` `&[f64]` `&mut[f64]` | No | No | No | Yes |
5+
| Type | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option<T>` parameter | `Option<T>` return value |
6+
|:---:|:---:|:---:|:---:|:---:|:---:|
7+
| `str` | No | Yes | No | Yes | Yes | No |
8+
| `char` | Yes | No | No | Yes | No | No |
9+
| `bool` | Yes | No | No | Yes | No | No |
10+
| `JsValue` | Yes | Yes | Yes | Yes | No | No |
11+
| `Box<[JsValue]>` | Yes | No | No | Yes | Yes | yes |
12+
| `*const T` | Yes | No | No | Yes | No | No |
13+
| `*mut T` | Yes | No | No | Yes | No | No |
14+
| `u8` `i8` `u16` `i16` `u64` `i64` `isize` `size` | Yes | No | No | Yes | No | No |
15+
| `u32` `i32` `f32` `f64` | Yes | Yes | Yes | Yes | No | No |
16+
| `Box<[u8]>` `Box<[i8]>` `Box<[u16]>` `Box<[i16]>` `Box<[u32]>` `Box<[i32]>` `Box<[u64]>` `Box<[i64]>` `Box<[f32]>` `Box<[f64]`> | Yes | No | No | Yes | Yes | Yes |
17+
| `[u8]` `[i8]` `[u16]` `[i16]` `[u32]` `[i32]` `[u64]` `[i64]` `[f32]` `[f64]` | No | Yes | Yes | No | Yes | No |

src/convert.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,30 @@ stack_closures! {
608608
(A B C D E F)
609609
(A B C D E F G)
610610
}
611+
612+
impl<T> FromWasmAbi for Option<T> where T: FromWasmAbi<Abi = WasmSlice> {
613+
type Abi = WasmSlice;
614+
615+
unsafe fn from_abi(js: WasmSlice, extra: &mut Stack) -> Option<T> {
616+
if js.ptr == 0 {
617+
None
618+
} else {
619+
Some(T::from_abi(js, extra))
620+
}
621+
}
622+
}
623+
624+
impl<T> IntoWasmAbi for Option<T> where T: IntoWasmAbi<Abi = WasmSlice> {
625+
type Abi = WasmSlice;
626+
627+
fn into_abi(self, extra: &mut Stack) -> WasmSlice {
628+
match self {
629+
Some(slice) => {
630+
let ret = slice.into_abi(extra);
631+
debug_assert!(ret.ptr != 0);
632+
return ret
633+
}
634+
None => WasmSlice { ptr: 0, len: 0 },
635+
}
636+
}
637+
}

src/describe.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ tys! {
3838
ENUM
3939
RUST_STRUCT
4040
CHAR
41+
OPTIONAL
4142
}
4243

4344
pub fn inform(a: u32) {
@@ -195,3 +196,10 @@ doit! {
195196
(A B C D E F)
196197
(A B C D E F G)
197198
}
199+
200+
impl<T: WasmDescribe> WasmDescribe for Option<T> {
201+
fn describe() {
202+
inform(OPTIONAL);
203+
T::describe();
204+
}
205+
}

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,11 @@ pub mod __rt {
681681

682682
#[no_mangle]
683683
pub unsafe extern fn __wbindgen_free(ptr: *mut u8, size: usize) {
684+
// This happens for zero-length slices, and in that case `ptr` is
685+
// likely bogus so don't actually send this to the system allocator
686+
if size == 0 {
687+
return
688+
}
684689
let align = mem::align_of::<usize>();
685690
let layout = Layout::from_size_align_unchecked(size, align);
686691
System.dealloc(ptr, layout);

0 commit comments

Comments
 (0)