Alex Crichton 935f71afec
Switch from failure to anyhow (#1851)
This commit switches all of `wasm-bindgen` from the `failure` crate to
`anyhow`. The `anyhow` crate should serve all the purposes that we
previously used `failure` for but has a few advantages:

* It's based on the standard `Error` trait rather than a custom `Fail`
  trait, improving ecosystem compatibility.
* We don't need a `#[derive(Fail)]`, which means that's less code to
  compile for `wasm-bindgen`. This notably helps the compile time of
  `web-sys` itself.
* Using `Result<()>` in `fn main` with `anyhow::Error` produces
  human-readable output, so we can use that natively.
2019-11-04 11:35:28 -06:00

452 lines
17 KiB
Rust

//! Implementation of translating a `NonstandardOutgoing` expression to an
//! actual JS shim and code snippet which ensures that bindings behave as we'd
//! expect.
use crate::descriptor::VectorKind;
use crate::js::binding::JsBuilder;
use crate::js::Context;
use crate::webidl::NonstandardOutgoing;
use anyhow::{bail, Error};
use wasm_webidl_bindings::ast;
pub struct Outgoing<'a, 'b> {
cx: &'a mut Context<'b>,
js: &'a mut JsBuilder,
}
impl<'a, 'b> Outgoing<'a, 'b> {
pub fn new(cx: &'a mut Context<'b>, js: &'a mut JsBuilder) -> Outgoing<'a, 'b> {
Outgoing { cx, js }
}
pub fn process(&mut self, outgoing: &NonstandardOutgoing) -> Result<String, Error> {
let before = self.js.typescript_len();
let ret = self.nonstandard(outgoing)?;
assert_eq!(before + 1, self.js.typescript_len());
Ok(ret)
}
fn nonstandard(&mut self, outgoing: &NonstandardOutgoing) -> Result<String, Error> {
match outgoing {
NonstandardOutgoing::Standard(expr) => self.standard(expr),
// Converts the wasm argument, a single code unit, to a string.
NonstandardOutgoing::Char { idx } => {
self.js.typescript_required("string");
Ok(format!("String.fromCodePoint({})", self.arg(*idx)))
}
// Just need to wrap up the pointer we get from Rust into a JS type
// and then we can pass that along
NonstandardOutgoing::RustType { class, idx } => {
self.js.typescript_required(class);
self.cx.require_class_wrap(class);
Ok(format!("{}.__wrap({})", class, self.arg(*idx)))
}
// Just a small wrapper around `getObject`
NonstandardOutgoing::BorrowedAnyref { idx } => {
self.js.typescript_required("any");
self.cx.expose_get_object();
Ok(format!("getObject({})", self.arg(*idx)))
}
// given the low/high bits we get from Rust, store them into a
// temporary 64-bit conversion array and then load the BigInt out of
// it.
NonstandardOutgoing::Number64 {
lo_idx,
hi_idx,
signed,
} => {
self.js.typescript_required("BigInt");
let f = if *signed {
self.cx.expose_int64_cvt_shim()
} else {
self.cx.expose_uint64_cvt_shim()
};
let i = self.js.tmp();
self.js.prelude(&format!(
"\
u32CvtShim[0] = {low};
u32CvtShim[1] = {high};
const n{i} = {f}[0];
",
low = self.arg(*lo_idx),
high = self.arg(*hi_idx),
f = f,
i = i,
));
Ok(format!("n{}", i))
}
// Similar to `View` below, except using 64-bit types which don't
// fit into webidl scalar types right now.
NonstandardOutgoing::View64 {
offset,
length,
signed,
} => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
let kind = if *signed {
VectorKind::I64
} else {
VectorKind::U64
};
self.js.typescript_required(kind.js_ty());
let f = self.cx.expose_get_vector_from_wasm(kind)?;
Ok(format!("{}({}, {})", f, ptr, len))
}
// Similar to `View` below, except using anyref types which have
// fancy conversion functions on our end.
NonstandardOutgoing::ViewAnyref { offset, length } => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
self.js.typescript_required(VectorKind::Anyref.js_ty());
let f = self.cx.expose_get_vector_from_wasm(VectorKind::Anyref)?;
Ok(format!("{}({}, {})", f, ptr, len))
}
// Similar to `View` below, except we free the memory in JS right
// now.
//
// TODO: we should free the memory in Rust to allow using standard
// webidl bindings.
NonstandardOutgoing::Vector {
offset,
length,
kind,
} => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
self.js.typescript_required(kind.js_ty());
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
let i = self.js.tmp();
self.js
.prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len));
self.prelude_free_vector(*offset, *length, *kind)?;
Ok(format!("v{}", i))
}
NonstandardOutgoing::CachedString {
offset,
length,
owned,
optional,
} => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
let tmp = self.js.tmp();
if *optional {
self.js.typescript_optional("string");
} else {
self.js.typescript_required("string");
}
self.cx.expose_get_cached_string_from_wasm()?;
self.js.prelude(&format!(
"const v{} = getCachedStringFromWasm({}, {});",
tmp, ptr, len
));
if *owned {
self.prelude_free_cached_string(&ptr, &len)?;
}
Ok(format!("v{}", tmp))
}
NonstandardOutgoing::StackClosure {
a,
b,
binding_idx,
nargs,
mutable,
} => {
self.js.typescript_optional("any");
let i = self.js.tmp();
self.js.prelude(&format!(
"const state{} = {{a: {}, b: {}}};",
i,
self.arg(*a),
self.arg(*b),
));
let args = (0..*nargs)
.map(|i| format!("arg{}", i))
.collect::<Vec<_>>()
.join(", ");
if *mutable {
// Mutable closures need protection against being called
// recursively, so ensure that we clear out one of the
// internal pointers while it's being invoked.
self.js.prelude(&format!(
"const cb{i} = ({args}) => {{
const a = state{i}.a;
state{i}.a = 0;
try {{
return __wbg_elem_binding{idx}(a, state{i}.b, {args});
}} finally {{
state{i}.a = a;
}}
}};",
i = i,
args = args,
idx = binding_idx,
));
} else {
self.js.prelude(&format!(
"const cb{i} = ({args}) => __wbg_elem_binding{idx}(state{i}.a, state{i}.b, {args});",
i = i,
args = args,
idx = binding_idx,
));
}
// Make sure to null out our internal pointers when we return
// back to Rust to ensure that any lingering references to the
// closure will fail immediately due to null pointers passed in
// to Rust.
self.js.finally(&format!("state{}.a = state{0}.b = 0;", i));
Ok(format!("cb{}", i))
}
NonstandardOutgoing::OptionBool { idx } => {
self.js.typescript_optional("boolean");
Ok(format!(
"{0} === 0xFFFFFF ? undefined : {0} !== 0",
self.arg(*idx)
))
}
NonstandardOutgoing::OptionChar { idx } => {
self.js.typescript_optional("string");
Ok(format!(
"{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})",
self.arg(*idx)
))
}
NonstandardOutgoing::OptionIntegerEnum { idx, hole } => {
self.js.typescript_optional("number");
Ok(format!(
"{0} === {1} ? undefined : {0}",
self.arg(*idx),
hole
))
}
NonstandardOutgoing::OptionRustType { class, idx } => {
self.cx.require_class_wrap(class);
self.js.typescript_optional(class);
Ok(format!(
"{0} === 0 ? undefined : {1}.__wrap({0})",
self.arg(*idx),
class,
))
}
NonstandardOutgoing::OptionU32Sentinel { idx } => {
self.js.typescript_optional("number");
Ok(format!(
"{0} === 0xFFFFFF ? undefined : {0}",
self.arg(*idx)
))
}
NonstandardOutgoing::OptionNative {
signed,
present,
val,
} => {
self.js.typescript_optional("number");
Ok(format!(
"{} === 0 ? undefined : {}{}",
self.arg(*present),
self.arg(*val),
if *signed { "" } else { " >>> 0" },
))
}
NonstandardOutgoing::OptionInt64 {
present,
_ignored,
lo,
hi,
signed,
} => {
self.js.typescript_optional("BigInt");
let f = if *signed {
self.cx.expose_int64_cvt_shim()
} else {
self.cx.expose_uint64_cvt_shim()
};
let i = self.js.tmp();
self.js.prelude(&format!(
"
u32CvtShim[0] = {low};
u32CvtShim[1] = {high};
const n{i} = {present} === 0 ? undefined : {f}[0];
",
present = self.arg(*present),
low = self.arg(*lo),
high = self.arg(*hi),
f = f,
i = i,
));
Ok(format!("n{}", i))
}
NonstandardOutgoing::OptionSlice {
kind,
offset,
length,
} => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
self.js.typescript_optional(kind.js_ty());
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
Ok(format!(
"{ptr} === 0 ? undefined : {f}({ptr}, {len})",
ptr = ptr,
len = len,
f = f
))
}
NonstandardOutgoing::OptionVector {
offset,
length,
kind,
} => {
let ptr = self.arg(*offset);
let len = self.arg(*length);
self.js.typescript_optional(kind.js_ty());
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
let i = self.js.tmp();
self.js.prelude(&format!("let v{};", i));
self.js.prelude(&format!("if ({} !== 0) {{", ptr));
self.js
.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len));
self.prelude_free_vector(*offset, *length, *kind)?;
self.js.prelude("}");
Ok(format!("v{}", i))
}
}
}
/// Evaluates the `standard` binding expression, returning the JS expression
/// needed to evaluate the binding.
fn standard(&mut self, standard: &ast::OutgoingBindingExpression) -> Result<String, Error> {
match standard {
ast::OutgoingBindingExpression::As(expr) => match expr.ty {
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => {
self.js.typescript_required("any");
if self.cx.config.anyref {
Ok(self.arg(expr.idx))
} else {
self.cx.expose_take_object();
Ok(format!("takeObject({})", self.arg(expr.idx)))
}
}
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => {
self.js.typescript_required("boolean");
Ok(format!("{} !== 0", self.arg(expr.idx)))
}
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLong) => {
self.js.typescript_required("number");
Ok(format!("{} >>> 0", self.arg(expr.idx)))
}
_ => {
self.js.typescript_required("number");
Ok(self.arg(expr.idx))
}
},
ast::OutgoingBindingExpression::View(view) => {
// TODO: deduplicate with same match statement in incoming
// bindings
let scalar = match view.ty {
ast::WebidlTypeRef::Scalar(s) => s,
ast::WebidlTypeRef::Id(_) => {
bail!("unsupported type passed to `view` in webidl binding")
}
};
let kind = match scalar {
ast::WebidlScalarType::Int8Array => VectorKind::I8,
ast::WebidlScalarType::Uint8Array => VectorKind::U8,
ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8,
ast::WebidlScalarType::Int16Array => VectorKind::I16,
ast::WebidlScalarType::Uint16Array => VectorKind::U16,
ast::WebidlScalarType::Int32Array => VectorKind::I32,
ast::WebidlScalarType::Uint32Array => VectorKind::U32,
ast::WebidlScalarType::Float32Array => VectorKind::F32,
ast::WebidlScalarType::Float64Array => VectorKind::F64,
_ => bail!("unsupported type passed to `view`: {:?}", scalar),
};
self.js.typescript_required(kind.js_ty());
let ptr = self.arg(view.offset);
let len = self.arg(view.length);
let f = self.cx.expose_get_vector_from_wasm(kind)?;
Ok(format!("{}({}, {})", f, ptr, len))
}
ast::OutgoingBindingExpression::Utf8Str(expr) => {
assert_eq!(expr.ty, ast::WebidlScalarType::DomString.into());
self.js.typescript_required("string");
let ptr = self.arg(expr.offset);
let len = self.arg(expr.length);
self.cx.expose_get_string_from_wasm()?;
Ok(format!("getStringFromWasm({}, {})", ptr, len))
}
ast::OutgoingBindingExpression::Utf8CStr(_) => {
bail!("unsupported `utf8-cstr` found in outgoing webidl bindings");
}
ast::OutgoingBindingExpression::I32ToEnum(_) => {
bail!("unsupported `i32-to-enum` found in outgoing webidl bindings");
}
ast::OutgoingBindingExpression::Copy(_) => {
bail!("unsupported `copy` found in outgoing webidl bindings");
}
ast::OutgoingBindingExpression::Dict(_) => {
bail!("unsupported `dict` found in outgoing webidl bindings");
}
ast::OutgoingBindingExpression::BindExport(_) => {
bail!("unsupported `bind-export` found in outgoing webidl bindings");
}
}
}
fn arg(&self, idx: u32) -> String {
self.js.arg(idx).to_string()
}
fn prelude_free_vector(
&mut self,
offset: u32,
length: u32,
kind: VectorKind,
) -> Result<(), Error> {
self.js.prelude(&format!(
"wasm.__wbindgen_free({0}, {1} * {size});",
self.arg(offset),
self.arg(length),
size = kind.size(),
));
self.cx.require_internal_export("__wbindgen_free")
}
fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> {
self.js.prelude(&format!(
"if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len}); }}",
ptr = ptr,
len = len,
));
self.cx.require_internal_export("__wbindgen_free")
}
}