mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-26 06:51:07 +00:00
Particularly useful in our tests, where we don't have the regular console logging with post-facto object inspection, and instead need to provide all this info up front.
806 lines
28 KiB
Rust
806 lines
28 KiB
Rust
use crate::descriptor::{Descriptor, Function};
|
|
use crate::js::js2rust::ExportedShim;
|
|
use crate::js::{Context, ImportTarget, Js2Rust};
|
|
use failure::{bail, Error};
|
|
|
|
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
|
/// JS, then invoking an imported JS function.
|
|
pub struct Rust2Js<'a, 'b: 'a> {
|
|
pub cx: &'a mut Context<'b>,
|
|
|
|
/// Arguments of the JS shim that we're generating, aka the variables passed
|
|
/// from Rust which are only numbers.
|
|
shim_arguments: Vec<String>,
|
|
|
|
/// Arguments which are forwarded to the imported JS function
|
|
js_arguments: Vec<String>,
|
|
|
|
/// Conversions that happen before we invoke the wasm function, such as
|
|
/// converting a string to a ptr/length pair.
|
|
prelude: String,
|
|
|
|
/// "Destructors" or cleanup that must happen after the wasm function
|
|
/// finishes. This is scheduled in a `finally` block.
|
|
finally: String,
|
|
|
|
/// Next global index to write to when passing arguments via the single
|
|
/// global stack.
|
|
global_idx: usize,
|
|
|
|
/// Index of the next argument for unique name generation purposes.
|
|
arg_idx: usize,
|
|
|
|
/// Expression used to generate the return value. The string "JS" in this
|
|
/// expression is replaced with the actual JS invocation eventually.
|
|
ret_expr: String,
|
|
|
|
/// Whether or not we're catching JS exceptions
|
|
catch: bool,
|
|
catch_and_rethrow: bool,
|
|
|
|
/// Whether or not the last argument is a slice representing variadic arguments.
|
|
variadic: bool,
|
|
|
|
/// list of arguments that are anyref, and whether they're an owned anyref
|
|
/// or not.
|
|
pub anyref_args: Vec<(usize, bool)>,
|
|
pub ret_anyref: bool,
|
|
}
|
|
|
|
impl<'a, 'b> Rust2Js<'a, 'b> {
|
|
pub fn new(cx: &'a mut Context<'b>) -> Rust2Js<'a, 'b> {
|
|
Rust2Js {
|
|
cx,
|
|
shim_arguments: Vec::new(),
|
|
js_arguments: Vec::new(),
|
|
prelude: String::new(),
|
|
finally: String::new(),
|
|
global_idx: 0,
|
|
arg_idx: 0,
|
|
ret_expr: String::new(),
|
|
catch: false,
|
|
catch_and_rethrow: false,
|
|
variadic: false,
|
|
anyref_args: Vec::new(),
|
|
ret_anyref: false,
|
|
}
|
|
}
|
|
|
|
pub fn catch(&mut self, catch: bool) -> &mut Self {
|
|
self.catch = catch;
|
|
self
|
|
}
|
|
|
|
pub fn catch_and_rethrow(&mut self, catch_and_rethrow: bool) -> &mut Self {
|
|
self.catch_and_rethrow = catch_and_rethrow;
|
|
self
|
|
}
|
|
|
|
pub fn variadic(&mut self, variadic: bool) -> &mut Self {
|
|
self.variadic = variadic;
|
|
self
|
|
}
|
|
|
|
/// Generates all bindings necessary for the signature in `Function`,
|
|
/// creating necessary argument conversions and return value processing.
|
|
pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> {
|
|
for arg in function.arguments.iter() {
|
|
self.argument(arg)?;
|
|
}
|
|
self.ret(&function.ret)?;
|
|
Ok(self)
|
|
}
|
|
|
|
/// Get a generated name for an argument.
|
|
fn shim_argument(&mut self) -> String {
|
|
let s = format!("arg{}", self.arg_idx);
|
|
self.arg_idx += 1;
|
|
self.shim_arguments.push(s.clone());
|
|
s
|
|
}
|
|
|
|
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} = {prefix}{func}({0}, {1});",
|
|
abi,
|
|
abi2,
|
|
func = f,
|
|
prefix = if optional {
|
|
format!("{} == 0 ? undefined : ", abi)
|
|
} else {
|
|
String::new()
|
|
},
|
|
));
|
|
|
|
if !arg.is_by_ref() && !arg.is_clamped_by_ref() {
|
|
self.prelude(&format!(
|
|
"\
|
|
{start}
|
|
v{0} = v{0}.slice();
|
|
wasm.__wbindgen_free({0}, {1} * {size});
|
|
{end}\
|
|
",
|
|
abi,
|
|
abi2,
|
|
size = ty.size(),
|
|
start = if optional {
|
|
format!("if ({} !== 0) {{", abi)
|
|
} else {
|
|
String::new()
|
|
},
|
|
end = if optional { "}" } else { "" },
|
|
));
|
|
self.cx.require_internal_export("__wbindgen_free")?;
|
|
}
|
|
self.js_arguments.push(format!("v{}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
// No need to special case `optional` here because `takeObject` will
|
|
// naturally work.
|
|
if arg.is_anyref() {
|
|
let arg = self.cx.take_object(&abi);
|
|
self.js_arguments.push(arg);
|
|
self.anyref_args.push((self.arg_idx - 1, true));
|
|
return Ok(());
|
|
} else if arg.is_ref_anyref() {
|
|
let arg = self.cx.get_object(&abi);
|
|
self.js_arguments.push(arg);
|
|
self.anyref_args.push((self.arg_idx - 1, false));
|
|
return Ok(());
|
|
}
|
|
|
|
if optional {
|
|
if arg.is_wasm_native() {
|
|
let value = self.shim_argument();
|
|
self.js_arguments.push(format!(
|
|
"{present} === 0 ? undefined : {value}",
|
|
value = value,
|
|
present = abi,
|
|
));
|
|
return Ok(());
|
|
}
|
|
|
|
if arg.is_abi_as_u32() {
|
|
self.js_arguments
|
|
.push(format!("{0} === 0xFFFFFF ? undefined : {0}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(signed) = arg.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_cvt_shim()
|
|
} else {
|
|
self.cx.expose_uint64_cvt_shim()
|
|
};
|
|
self.shim_argument();
|
|
let low = self.shim_argument();
|
|
let high = self.shim_argument();
|
|
let name = format!("n{}", abi);
|
|
self.prelude(&format!(
|
|
"
|
|
u32CvtShim[0] = {present} === 0 ? 0 : {low};
|
|
u32CvtShim[1] = {present} === 0 ? 0 : {high};
|
|
const {name} = {present} === 0 ? undefined : {f}[0];
|
|
",
|
|
present = abi,
|
|
low = low,
|
|
high = high,
|
|
f = f,
|
|
name = name,
|
|
));
|
|
self.js_arguments.push(name);
|
|
return Ok(());
|
|
}
|
|
|
|
match *arg {
|
|
Descriptor::Boolean => {
|
|
self.js_arguments
|
|
.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", abi));
|
|
return Ok(());
|
|
}
|
|
Descriptor::Enum { hole } => {
|
|
self.js_arguments
|
|
.push(format!("{0} === {1} ? undefined : {0}", abi, hole));
|
|
return Ok(());
|
|
}
|
|
Descriptor::Char => {
|
|
self.js_arguments.push(format!(
|
|
"{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})",
|
|
abi
|
|
));
|
|
return Ok(());
|
|
}
|
|
Descriptor::RustStruct(ref class) => {
|
|
self.cx.require_class_wrap(class);
|
|
let assign = format!(
|
|
"let c{0} = {0} === 0 ? undefined : {1}.__wrap({0});",
|
|
abi, class
|
|
);
|
|
self.prelude(&assign);
|
|
self.js_arguments.push(format!("c{}", abi));
|
|
return Ok(());
|
|
}
|
|
_ => bail!(
|
|
"unsupported optional argument type for calling JS function from Rust: {:?}",
|
|
arg
|
|
),
|
|
};
|
|
}
|
|
|
|
if let Some(signed) = arg.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_cvt_shim()
|
|
} else {
|
|
self.cx.expose_uint64_cvt_shim()
|
|
};
|
|
let high = self.shim_argument();
|
|
let name = format!("n{}", abi);
|
|
self.prelude(&format!(
|
|
"\
|
|
u32CvtShim[0] = {low};
|
|
u32CvtShim[1] = {high};
|
|
const {name} = {f}[0];
|
|
",
|
|
low = abi,
|
|
high = high,
|
|
f = f,
|
|
name = name,
|
|
));
|
|
self.js_arguments.push(name);
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(class) = arg.rust_struct() {
|
|
if arg.is_by_ref() {
|
|
bail!("cannot invoke JS functions with custom ref types yet")
|
|
}
|
|
self.cx.require_class_wrap(class);
|
|
let assign = format!("let c{0} = {1}.__wrap({0});", abi, class);
|
|
self.prelude(&assign);
|
|
self.js_arguments.push(format!("c{}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some((f, mutable)) = arg.stack_closure() {
|
|
let arg2 = self.shim_argument();
|
|
let mut shim = f.shim_idx;
|
|
let (js, _ts, _js_doc) = {
|
|
let mut builder = Js2Rust::new("", self.cx);
|
|
if mutable {
|
|
builder
|
|
.prelude("let a = this.a;\n")
|
|
.prelude("this.a = 0;\n")
|
|
.rust_argument("a")
|
|
.finally("this.a = a;\n");
|
|
} else {
|
|
builder.rust_argument("this.a");
|
|
}
|
|
builder.rust_argument("this.b").process(f, None)?.finish(
|
|
"function",
|
|
"this.f",
|
|
ExportedShim::TableElement(&mut shim),
|
|
)
|
|
};
|
|
self.cx.function_table_needed = true;
|
|
self.global_idx();
|
|
self.prelude(&format!(
|
|
"\
|
|
let cb{0} = {js};\n\
|
|
cb{0}.f = wasm.__wbg_function_table.get({idx});\n\
|
|
cb{0}.a = {0};\n\
|
|
cb{0}.b = {1};\n\
|
|
",
|
|
abi,
|
|
arg2,
|
|
js = js,
|
|
idx = shim,
|
|
));
|
|
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
|
|
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(num) = arg.number() {
|
|
if num.is_u32() {
|
|
self.js_arguments.push(format!("{} >>> 0", abi));
|
|
} else {
|
|
self.js_arguments.push(abi);
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
let invoc_arg = match *arg {
|
|
Descriptor::Boolean => format!("{} !== 0", abi),
|
|
Descriptor::Char => format!("String.fromCodePoint({})", abi),
|
|
_ => bail!(
|
|
"unsupported argument type for calling JS function from Rust: {:?}",
|
|
arg
|
|
),
|
|
};
|
|
self.js_arguments.push(invoc_arg);
|
|
Ok(())
|
|
}
|
|
|
|
fn ret(&mut self, ty: &Descriptor) -> Result<(), Error> {
|
|
if let Descriptor::Unit = ty {
|
|
self.ret_expr = "JS;".to_string();
|
|
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")
|
|
}
|
|
if let Some(ty) = ty.vector_kind() {
|
|
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 = {};
|
|
const retlen = WASM_VECTOR_LEN;
|
|
const mem = getUint32Memory();
|
|
mem[ret / 4] = retptr;
|
|
mem[ret / 4 + 1] = retlen;
|
|
",
|
|
prelude, expr
|
|
);
|
|
return Ok(());
|
|
}
|
|
if ty.is_anyref() {
|
|
if self.cx.config.anyref {
|
|
if optional {
|
|
self.cx.expose_add_to_anyref_table()?;
|
|
self.cx.expose_is_like_none();
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0 : addToAnyrefTable(val);
|
|
"
|
|
.to_string();
|
|
} else {
|
|
self.ret_anyref = true;
|
|
self.ret_expr = "return JS;".to_string()
|
|
}
|
|
} else {
|
|
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 {
|
|
self.cx.expose_is_like_none();
|
|
|
|
if ty.is_wasm_native() {
|
|
self.cx.expose_uint32_memory();
|
|
match ty {
|
|
Descriptor::I32 => self.cx.expose_int32_memory(),
|
|
Descriptor::U32 => (),
|
|
Descriptor::F32 => self.cx.expose_f32_memory(),
|
|
Descriptor::F64 => self.cx.expose_f64_memory(),
|
|
_ => (),
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
getUint32Memory()[ret / 4] = !isLikeNone(val);
|
|
{mem}[ret / {size} + 1] = isLikeNone(val) ? 0 : val;
|
|
",
|
|
size = match ty {
|
|
Descriptor::I32 => 4,
|
|
Descriptor::U32 => 4,
|
|
Descriptor::F32 => 4,
|
|
Descriptor::F64 => 8,
|
|
_ => unreachable!(),
|
|
},
|
|
mem = match ty {
|
|
Descriptor::I32 => "getInt32Memory()",
|
|
Descriptor::U32 => "getUint32Memory()",
|
|
Descriptor::F32 => "getFloat32Memory()",
|
|
Descriptor::F64 => "getFloat64Memory()",
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
if ty.is_abi_as_u32() {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val;
|
|
"
|
|
.to_string();
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(signed) = ty.get_64() {
|
|
self.cx.expose_uint32_memory();
|
|
let f = if signed {
|
|
self.cx.expose_int64_memory();
|
|
"getInt64Memory"
|
|
} else {
|
|
self.cx.expose_uint64_memory();
|
|
"getUint64Memory"
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
getUint32Memory()[ret / 4] = !isLikeNone(val);
|
|
{}()[ret / 8 + 1] = isLikeNone(val) ? BigInt(0) : val;
|
|
",
|
|
f
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
match *ty {
|
|
Descriptor::Boolean => {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val ? 1 : 0;
|
|
"
|
|
.to_string();
|
|
}
|
|
Descriptor::Char => {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val.codePointAt(0);
|
|
"
|
|
.to_string();
|
|
}
|
|
Descriptor::Enum { hole } => {
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
return isLikeNone(val) ? {} : val;
|
|
",
|
|
hole
|
|
);
|
|
}
|
|
Descriptor::RustStruct(ref class) => {
|
|
// Like below, assert the type
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;
|
|
if (isLikeNone(val))
|
|
return 0;
|
|
if (!(val instanceof {0})) {{
|
|
throw new Error('expected value of type {0}');
|
|
}}
|
|
const ret = val.ptr;
|
|
val.ptr = 0;
|
|
return ret;\
|
|
",
|
|
class
|
|
);
|
|
}
|
|
_ => bail!(
|
|
"unsupported optional return type for calling JS function from Rust: {:?}",
|
|
ty
|
|
),
|
|
};
|
|
|
|
return Ok(());
|
|
}
|
|
if ty.number().is_some() {
|
|
self.ret_expr = "return JS;".to_string();
|
|
return Ok(());
|
|
}
|
|
if let Some(signed) = ty.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_memory();
|
|
"getInt64Memory"
|
|
} else {
|
|
self.cx.expose_uint64_memory();
|
|
"getUint64Memory"
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;\n\
|
|
{}()[ret / 8] = val;\n\
|
|
",
|
|
f
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(class) = ty.rust_struct() {
|
|
if ty.is_by_ref() {
|
|
bail!("cannot invoke JS functions returning custom ref types yet")
|
|
}
|
|
// Insert an assertion to the type of the returned value as
|
|
// otherwise this will cause memory unsafety on the Rust side of
|
|
// things.
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;
|
|
if (!(val instanceof {0})) {{
|
|
throw new Error('expected value of type {0}');
|
|
}}
|
|
const ret = val.ptr;
|
|
val.ptr = 0;
|
|
return ret;\
|
|
",
|
|
class
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
self.ret_expr = match *ty {
|
|
Descriptor::Boolean => "return JS;".to_string(),
|
|
Descriptor::Char => "return JS.codePointAt(0);".to_string(),
|
|
_ => bail!(
|
|
"unsupported return type for calling JS function from Rust: {:?}",
|
|
ty
|
|
),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns whether this shim won't actually do anything when called other
|
|
/// than forward the invocation somewhere else.
|
|
///
|
|
/// This is used as an optimization to wire up imports directly where
|
|
/// possible and avoid a shim in some circumstances.
|
|
pub fn is_noop(&self) -> bool {
|
|
let Rust2Js {
|
|
// fields which may affect whether we do nontrivial work
|
|
catch,
|
|
catch_and_rethrow,
|
|
finally,
|
|
js_arguments,
|
|
prelude,
|
|
ret_expr,
|
|
variadic,
|
|
shim_arguments,
|
|
|
|
// all other fields, listed explicitly here so if one is added we'll
|
|
// trigger a nonexhaustive error.
|
|
arg_idx: _,
|
|
cx: _,
|
|
global_idx: _,
|
|
anyref_args: _,
|
|
ret_anyref: _,
|
|
} = self;
|
|
|
|
!catch &&
|
|
!catch_and_rethrow &&
|
|
!variadic &&
|
|
prelude.is_empty() &&
|
|
finally.is_empty() &&
|
|
// make sure our faux return expression is "simple" by not
|
|
// performing any sort of transformation on the return value
|
|
(ret_expr == "JS;" || ret_expr == "return JS;") &&
|
|
// similarly we want to make sure that all the arguments are simply
|
|
// forwarded from the shim we would generate to the import,
|
|
// requiring no transformations
|
|
js_arguments == shim_arguments
|
|
}
|
|
|
|
pub fn finish(&mut self, invoc: &ImportTarget, shim: &str) -> Result<String, Error> {
|
|
let mut ret = String::new();
|
|
ret.push_str("function(");
|
|
ret.push_str(&self.shim_arguments.join(", "));
|
|
if self.catch {
|
|
if self.shim_arguments.len() > 0 {
|
|
ret.push_str(", ")
|
|
}
|
|
ret.push_str("exnptr");
|
|
}
|
|
ret.push_str(") {\n");
|
|
ret.push_str(&self.prelude);
|
|
|
|
let variadic = self.variadic;
|
|
let ret_expr = &self.ret_expr;
|
|
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
|
let ret = if variadic {
|
|
let (last_arg, args) = match js_arguments.split_last() {
|
|
Some(pair) => pair,
|
|
None => bail!("a function with no arguments cannot be variadic"),
|
|
};
|
|
if args.len() > 0 {
|
|
ret_expr.replace(
|
|
"JS",
|
|
&format!("{}({}, ...{})", invoc, args.join(", "), last_arg),
|
|
)
|
|
} else {
|
|
ret_expr.replace("JS", &format!("{}(...{})", invoc, last_arg))
|
|
}
|
|
} else {
|
|
ret_expr.replace("JS", &format!("{}({})", invoc, js_arguments.join(", ")))
|
|
};
|
|
Ok(ret)
|
|
};
|
|
|
|
let js_arguments = &self.js_arguments;
|
|
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
|
if variadic {
|
|
bail!("{} cannot be variadic", desc);
|
|
}
|
|
match (class, js_arguments.len()) {
|
|
(None, n) if n == amt + 1 => Ok((js_arguments[0].clone(), &js_arguments[1..])),
|
|
(None, _) => bail!("setters must have {} arguments", amt + 1),
|
|
(Some(class), n) if n == amt => Ok((class.clone(), &js_arguments[..])),
|
|
(Some(_), _) => bail!("static setters must have {} arguments", amt),
|
|
}
|
|
};
|
|
|
|
let mut invoc = match invoc {
|
|
ImportTarget::Function(f) => handle_variadic(&f, &self.js_arguments)?,
|
|
ImportTarget::Constructor(c) => {
|
|
handle_variadic(&format!("new {}", c), &self.js_arguments)?
|
|
}
|
|
ImportTarget::Method(f) => handle_variadic(&format!("{}.call", f), &self.js_arguments)?,
|
|
ImportTarget::StructuralMethod(f) => {
|
|
let (receiver, args) = match self.js_arguments.split_first() {
|
|
Some(pair) => pair,
|
|
None => bail!("methods must have at least one argument"),
|
|
};
|
|
handle_variadic(&format!("{}.{}", receiver, f), args)?
|
|
}
|
|
ImportTarget::StructuralGetter(class, field) => {
|
|
let (receiver, _) = fixed("getter", class, 0)?;
|
|
let expr = format!("{}.{}", receiver, field);
|
|
self.ret_expr.replace("JS", &expr)
|
|
}
|
|
ImportTarget::StructuralSetter(class, field) => {
|
|
let (receiver, val) = fixed("setter", class, 1)?;
|
|
let expr = format!("{}.{} = {}", receiver, field, val[0]);
|
|
self.ret_expr.replace("JS", &expr)
|
|
}
|
|
ImportTarget::StructuralIndexingGetter(class) => {
|
|
let (receiver, field) = fixed("indexing getter", class, 1)?;
|
|
let expr = format!("{}[{}]", receiver, field[0]);
|
|
self.ret_expr.replace("JS", &expr)
|
|
}
|
|
ImportTarget::StructuralIndexingSetter(class) => {
|
|
let (receiver, field) = fixed("indexing setter", class, 2)?;
|
|
let expr = format!("{}[{}] = {}", receiver, field[0], field[1]);
|
|
self.ret_expr.replace("JS", &expr)
|
|
}
|
|
ImportTarget::StructuralIndexingDeleter(class) => {
|
|
let (receiver, field) = fixed("indexing deleter", class, 1)?;
|
|
let expr = format!("delete {}[{}]", receiver, field[0]);
|
|
self.ret_expr.replace("JS", &expr)
|
|
}
|
|
};
|
|
|
|
if self.catch {
|
|
self.cx.expose_handle_error()?;
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} catch (e) {{\n\
|
|
handleError(exnptr, e);\n\
|
|
}}\
|
|
",
|
|
&invoc
|
|
);
|
|
} else if self.catch_and_rethrow {
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} catch (e) {{\n\
|
|
let error = (function () {{
|
|
try {{
|
|
return e instanceof Error
|
|
? `${{e.message}}\n\nStack:\n${{e.stack}}`
|
|
: e.toString();
|
|
}} catch(_) {{
|
|
return \"<failed to stringify thrown value>\";
|
|
}}
|
|
}}());
|
|
console.error(\"wasm-bindgen: imported JS function `{}` that \
|
|
was not marked as `catch` threw an error:\", \
|
|
error);
|
|
throw e;
|
|
}}\
|
|
",
|
|
&invoc,
|
|
shim,
|
|
);
|
|
}
|
|
|
|
if self.finally.len() > 0 {
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} finally {{\n\
|
|
{}
|
|
}}\
|
|
",
|
|
&invoc, &self.finally
|
|
);
|
|
}
|
|
ret.push_str(&invoc);
|
|
|
|
ret.push_str("\n}\n");
|
|
|
|
if self.ret_anyref || self.anyref_args.len() > 0 {
|
|
// Some return values go at the the beginning of the argument list
|
|
// (they force a return pointer). Handle that here by offsetting all
|
|
// our arg indices by one, but throw in some sanity checks for if
|
|
// this ever changes.
|
|
if let Some(start) = self.shim_arguments.get(0) {
|
|
if start == "ret" {
|
|
assert!(!self.ret_anyref);
|
|
if let Some(next) = self.shim_arguments.get(1) {
|
|
assert_eq!(next, "arg0");
|
|
}
|
|
for (idx, _) in self.anyref_args.iter_mut() {
|
|
*idx += 1;
|
|
}
|
|
} else {
|
|
assert_eq!(start, "arg0");
|
|
}
|
|
}
|
|
self.cx.anyref.import_xform(
|
|
"__wbindgen_placeholder__",
|
|
shim,
|
|
&self.anyref_args,
|
|
self.ret_anyref,
|
|
);
|
|
}
|
|
|
|
Ok(ret)
|
|
}
|
|
|
|
fn global_idx(&mut self) -> usize {
|
|
let ret = self.global_idx;
|
|
self.global_idx += 1;
|
|
ret
|
|
}
|
|
|
|
fn prelude(&mut self, s: &str) -> &mut Self {
|
|
for line in s.lines() {
|
|
self.prelude.push_str(line);
|
|
self.prelude.push_str("\n");
|
|
}
|
|
self
|
|
}
|
|
|
|
fn finally(&mut self, s: &str) -> &mut Self {
|
|
for line in s.lines() {
|
|
self.finally.push_str(line);
|
|
self.finally.push_str("\n");
|
|
}
|
|
self
|
|
}
|
|
}
|