Nick Fitzgerald f9ac4e9c90 Always bind static operations to their class
For example, `Promise.resolve` must always be called with the `Promise`
constructor as its `this`, or else it will throw an error.
2018-08-09 16:17:34 -07:00

2086 lines
68 KiB
Rust

use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::mem;
use failure::{Error, ResultExt};
use parity_wasm;
use parity_wasm::elements::*;
use shared;
use wasm_gc;
use super::Bindgen;
use descriptor::{Descriptor, VectorKind};
mod js2rust;
use self::js2rust::Js2Rust;
mod rust2js;
use self::rust2js::Rust2Js;
pub struct Context<'a> {
pub globals: String,
pub imports: String,
pub footer: String,
pub typescript: String,
pub exposed_globals: HashSet<&'static str>,
pub required_internal_exports: HashSet<&'static str>,
pub config: &'a Bindgen,
pub module: &'a mut Module,
/// A map which maintains a list of what identifiers we've imported and what
/// they're named locally.
///
/// The `Option<String>` key is the module that identifiers were imported
/// from, `None` being the global module. The second key is a map of
/// identifiers we've already imported from the module to what they're
/// called locally.
pub imported_names: HashMap<Option<String>, HashMap<String, String>>,
/// A set of all imported identifiers to the number of times they've been
/// imported, used to generate new identifiers.
pub imported_identifiers: HashMap<String, usize>,
pub exported_classes: HashMap<String, ExportedClass>,
pub function_table_needed: bool,
pub run_descriptor: &'a Fn(&str) -> Option<Vec<u32>>,
}
#[derive(Default)]
pub struct ExportedClass {
comments: String,
contents: String,
typescript: String,
constructor: Option<String>,
fields: Vec<ClassField>,
}
struct ClassField {
comments: Vec<String>,
name: String,
readonly: bool,
}
pub struct SubContext<'a, 'b: 'a> {
pub program: &'a shared::Program,
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();
if let Some(ref c) = comments {
self.globals.push_str(c);
}
let global = if self.use_node_require() {
if contents.starts_with("class") {
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
} else {
format!("module.exports.{} = {};\n", name, contents)
}
} else if self.config.no_modules {
if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents)
} else {
format!("__exports.{} = {};\n", name, contents)
}
} else {
if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") {
format!("export {}\n", contents)
} else {
format!("export const {} = {};\n", name, contents)
}
};
self.global(&global);
}
fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
if !self.required_internal_exports.insert(name) {
return Ok(());
}
if let Some(s) = self.module.export_section() {
if s.entries().iter().any(|e| e.field() == name) {
return Ok(());
}
}
bail!(
"the exported function `{}` is required to generate bindings \
but it was not found in the wasm file, perhaps the `std` feature \
of the `wasm-bindgen` crate needs to be enabled?",
name
);
}
pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
self.write_classes()?;
self.bind("__wbindgen_object_clone_ref", &|me| {
me.expose_add_heap_object();
me.expose_get_object();
let bump_cnt = if me.config.debug {
String::from(
"
if (typeof(val) === 'number') throw new Error('corrupt slab');
val.cnt += 1;
",
)
} else {
String::from("val.cnt += 1;")
};
Ok(format!(
"
function(idx) {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1) return addHeapObject(getObject(idx));
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}}
",
bump_cnt
))
})?;
self.bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
Ok(String::from(
"
function(i) {
dropRef(i);
}
",
))
})?;
self.bind("__wbindgen_string_new", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
Ok(String::from(
"
function(p, l) {
return addHeapObject(getStringFromWasm(p, l));
}
",
))
})?;
self.bind("__wbindgen_number_new", &|me| {
me.expose_add_heap_object();
Ok(String::from(
"
function(i) {
return addHeapObject(i);
}
",
))
})?;
self.bind("__wbindgen_number_get", &|me| {
me.expose_get_object();
me.expose_uint8_memory();
Ok(String::from(
"
function(n, invalid) {
let obj = getObject(n);
if (typeof(obj) === 'number') return obj;
getUint8Memory()[invalid] = 1;
return 0;
}
",
))
})?;
self.bind("__wbindgen_is_null", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(idx) {
return getObject(idx) === null ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_is_undefined", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(idx) {
return getObject(idx) === undefined ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_boolean_get", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(i) {
let v = getObject(i);
if (typeof(v) === 'boolean') {
return v ? 1 : 0;
} else {
return 2;
}
}
",
))
})?;
self.bind("__wbindgen_symbol_new", &|me| {
me.expose_get_string_from_wasm();
me.expose_add_heap_object();
Ok(String::from(
"
function(ptr, len) {
let a;
if (ptr === 0) {
a = Symbol();
} else {
a = Symbol(getStringFromWasm(ptr, len));
}
return addHeapObject(a);
}
",
))
})?;
self.bind("__wbindgen_is_symbol", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(i) {
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_is_object", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(i) {
const val = getObject(i);
return typeof(val) === 'object' && val !== null ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_is_function", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(i) {
return typeof(getObject(i)) === 'function' ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_is_string", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(i) {
return typeof(getObject(i)) === 'string' ? 1 : 0;
}
",
))
})?;
self.bind("__wbindgen_string_get", &|me| {
me.expose_pass_string_to_wasm()?;
me.expose_get_object();
me.expose_uint32_memory();
Ok(String::from(
"
function(i, len_ptr) {
let obj = getObject(i);
if (typeof(obj) !== 'string') return 0;
const [ptr, len] = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = len;
return ptr;
}
",
))
})?;
self.bind("__wbindgen_cb_drop", &|me| {
me.expose_drop_ref();
Ok(String::from(
"
function(i) {
let obj = getObject(i).original;
obj.a = obj.b = 0;
dropRef(i);
}
",
))
})?;
self.bind("__wbindgen_cb_forget", &|me| {
me.expose_drop_ref();
Ok(String::from(
"
function(i) {
dropRef(i);
}
",
))
})?;
self.bind("__wbindgen_json_parse", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
Ok(String::from(
"
function(ptr, len) {
return addHeapObject(JSON.parse(getStringFromWasm(ptr, len)));
}
",
))
})?;
self.bind("__wbindgen_json_serialize", &|me| {
me.expose_get_object();
me.expose_pass_string_to_wasm()?;
me.expose_uint32_memory();
Ok(String::from(
"
function(idx, ptrptr) {
const [ptr, len] = passStringToWasm(JSON.stringify(getObject(idx)));
getUint32Memory()[ptrptr / 4] = ptr;
return len;
}
",
))
})?;
self.bind("__wbindgen_jsval_eq", &|me| {
me.expose_get_object();
Ok(String::from(
"
function(a, b) {
return getObject(a) === getObject(b) ? 1 : 0;
}
",
))
})?;
self.unexport_unused_internal_exports();
self.gc()?;
// Note that it's important `throw` comes last *after* we gc. The
// `__wbindgen_malloc` function may call this but we only want to
// generate code for this if it's actually live (and __wbindgen_malloc
// isn't gc'd).
self.bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
Ok(String::from(
"
function(ptr, len) {
throw new Error(getStringFromWasm(ptr, len));
}
",
))
})?;
self.rewrite_imports(module_name);
let mut js = if self.config.no_modules {
format!(
"
(function() {{
var wasm;
const __exports = {{}};
{globals}
function init(wasm_path) {{
const fetchPromise = fetch(wasm_path);
let resultPromise;
if (typeof WebAssembly.instantiateStreaming === 'function') {{
resultPromise = WebAssembly.instantiateStreaming(fetchPromise, {{ './{module}': __exports }});
}} else {{
resultPromise = fetchPromise
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, {{ './{module}': __exports }}));
}}
return resultPromise.then(({{instance}}) => {{
wasm = init.wasm = instance.exports;
return;
}});
}};
self.{global_name} = Object.assign(init, __exports);
}})();
",
globals = self.globals,
module = module_name,
global_name = self.config.no_modules_global
.as_ref()
.map(|s| &**s)
.unwrap_or("wasm_bindgen"),
)
} else {
let import_wasm = if self.globals.len() == 0 {
String::new()
} else if self.use_node_require() {
self.footer
.push_str(&format!("wasm = require('./{}_bg');", module_name));
format!("var wasm;")
} else {
format!("import * as wasm from './{}_bg';", module_name)
};
format!(
"\
/* tslint:disable */\n\
{import_wasm}\n\
{imports}\n\
{globals}\n\
{footer}",
import_wasm = import_wasm,
globals = self.globals,
imports = self.imports,
footer = self.footer,
)
};
self.export_table();
self.gc()?;
while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n");
}
Ok((js, self.typescript.clone()))
}
fn bind(
&mut self,
name: &str,
f: &Fn(&mut Self) -> Result<String, Error>,
) -> Result<(), Error> {
if !self.wasm_import_needed(name) {
return Ok(());
}
let contents = f(self)
.with_context(|_| format!("failed to generate internal JS function `{}`", name))?;
self.export(name, &contents, None);
Ok(())
}
fn write_classes(&mut self) -> Result<(), Error> {
let classes = mem::replace(&mut self.exported_classes, Default::default());
for (class, exports) in classes {
self.write_class(&class, &exports)?;
}
Ok(())
}
fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> {
let mut dst = format!("class {} {{\n", name);
let mut ts_dst = format!("export {}", dst);
if self.config.debug || class.constructor.is_some() {
self.expose_constructor_token();
dst.push_str(&format!(
"
static __construct(ptr) {{
return new {}(new ConstructorToken(ptr));
}}
constructor(...args) {{
if (args.length === 1 && args[0] instanceof ConstructorToken) {{
this.ptr = args[0].ptr;
return;
}}
",
name
));
if let Some(ref constructor) = class.constructor {
ts_dst.push_str(&format!("constructor(...args: any[]);\n"));
dst.push_str(&format!(
"
// This invocation of new will call this constructor with a ConstructorToken
let instance = {class}.{constructor}(...args);
this.ptr = instance.ptr;
",
class = name,
constructor = constructor
));
} else {
dst.push_str(
"throw new Error('you cannot invoke `new` directly without having a \
method annotated a constructor');\n",
);
}
dst.push_str("}");
} else {
dst.push_str(&format!(
"
static __construct(ptr) {{
return new {}(ptr);
}}
constructor(ptr) {{
this.ptr = ptr;
}}
",
name
));
}
let new_name = shared::new_function(&name);
if self.wasm_import_needed(&new_name) {
self.expose_add_heap_object();
self.export(
&new_name,
&format!(
"
function(ptr) {{
return addHeapObject({}.__construct(ptr));
}}
",
name
),
None,
);
}
for field in class.fields.iter() {
let wasm_getter = shared::struct_field_get(name, &field.name);
let wasm_setter = shared::struct_field_set(name, &field.name);
let descriptor = match self.describe(&wasm_getter) {
None => continue,
Some(d) => d,
};
let set = {
let mut cx = Js2Rust::new(&field.name, self);
cx.method(true, false).argument(&descriptor)?.ret(&None)?;
ts_dst.push_str(&format!(
"{}{}: {}\n",
if field.readonly { "readonly " } else { "" },
field.name,
&cx.js_arguments[0].1
));
cx.finish("", &format!("wasm.{}", wasm_setter)).0
};
let (get, _ts, js_doc) = Js2Rust::new(&field.name, self)
.method(true, false)
.ret(&Some(descriptor))?
.finish("", &format!("wasm.{}", wasm_getter));
if !dst.ends_with("\n") {
dst.push_str("\n");
}
dst.push_str(&format_doc_comments(&field.comments, Some(js_doc)));
dst.push_str("get ");
dst.push_str(&field.name);
dst.push_str(&get);
dst.push_str("\n");
if !field.readonly {
dst.push_str("set ");
dst.push_str(&field.name);
dst.push_str(&set);
}
}
dst.push_str(&format!(
"
free() {{
const ptr = this.ptr;
this.ptr = 0;
wasm.{}(ptr);
}}
",
shared::free_function(&name)
));
ts_dst.push_str("free(): void;\n");
dst.push_str(&class.contents);
ts_dst.push_str(&class.typescript);
dst.push_str("}\n");
ts_dst.push_str("}\n");
self.export(&name, &dst, Some(class.comments.clone()));
self.typescript.push_str(&ts_dst);
Ok(())
}
fn export_table(&mut self) {
if !self.function_table_needed {
return;
}
for section in self.module.sections_mut() {
let exports = match *section {
Section::Export(ref mut s) => s,
_ => continue,
};
let entry = ExportEntry::new("__wbg_function_table".to_string(), Internal::Table(0));
exports.entries_mut().push(entry);
break;
}
}
fn rewrite_imports(&mut self, module_name: &str) {
for (name, contents) in self._rewrite_imports(module_name) {
self.export(&name, &contents, None);
}
}
fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> {
let mut math_imports = Vec::new();
let imports = self
.module
.sections_mut()
.iter_mut()
.filter_map(|s| match *s {
Section::Import(ref mut s) => Some(s),
_ => None,
})
.flat_map(|s| s.entries_mut());
for import in imports {
if import.module() == "__wbindgen_placeholder__" {
import.module_mut().truncate(0);
import.module_mut().push_str("./");
import.module_mut().push_str(module_name);
continue;
}
if import.module() != "env" {
continue;
}
let renamed_import = format!("__wbindgen_{}", import.field());
let mut bind_math = |expr: &str| {
math_imports.push((renamed_import.clone(), format!("function{}", expr)));
};
// FIXME(#32): try to not use function shims
match import.field() {
"Math_acos" => bind_math("(x) { return Math.acos(x); }"),
"Math_asin" => bind_math("(x) { return Math.asin(x); }"),
"Math_atan" => bind_math("(x) { return Math.atan(x); }"),
"Math_atan2" => bind_math("(x, y) { return Math.atan2(x, y); }"),
"Math_cbrt" => bind_math("(x) { return Math.cbrt(x); }"),
"Math_cosh" => bind_math("(x) { return Math.cosh(x); }"),
"Math_expm1" => bind_math("(x) { return Math.expm1(x); }"),
"Math_hypot" => bind_math("(x, y) { return Math.hypot(x, y); }"),
"Math_log1p" => bind_math("(x) { return Math.log1p(x); }"),
"Math_sinh" => bind_math("(x) { return Math.sinh(x); }"),
"Math_tan" => bind_math("(x) { return Math.tan(x); }"),
"Math_tanh" => bind_math("(x) { return Math.tanh(x); }"),
"cos" => bind_math("(x) { return Math.cos(x); }"),
"cosf" => bind_math("(x) { return Math.cos(x); }"),
"exp" => bind_math("(x) { return Math.exp(x); }"),
"expf" => bind_math("(x) { return Math.exp(x); }"),
"log2" => bind_math("(x) { return Math.log2(x); }"),
"log2f" => bind_math("(x) { return Math.log2(x); }"),
"log10" => bind_math("(x) { return Math.log10(x); }"),
"log10f" => bind_math("(x) { return Math.log10(x); }"),
"log" => bind_math("(x) { return Math.log(x); }"),
"logf" => bind_math("(x) { return Math.log(x); }"),
"round" => bind_math("(x) { return Math.round(x); }"),
"roundf" => bind_math("(x) { return Math.round(x); }"),
"sin" => bind_math("(x) { return Math.sin(x); }"),
"sinf" => bind_math("(x) { return Math.sin(x); }"),
"pow" => bind_math("(x, y) { return Math.pow(x, y); }"),
"powf" => bind_math("(x, y) { return Math.pow(x, y); }"),
"exp2" => bind_math("(a) { return Math.pow(2, a); }"),
"exp2f" => bind_math("(a) { return Math.pow(2, a); }"),
"fmod" => bind_math("(a, b) { return a % b; }"),
"fmodf" => bind_math("(a, b) { return a % b; }"),
"fma" => bind_math("(a, b, c) { return (a * b) + c; }"),
"fmaf" => bind_math("(a, b, c) { return (a * b) + c; }"),
_ => continue,
}
import.module_mut().truncate(0);
import.module_mut().push_str("./");
import.module_mut().push_str(module_name);
*import.field_mut() = renamed_import.clone();
}
math_imports
}
fn unexport_unused_internal_exports(&mut self) {
let required = &self.required_internal_exports;
for section in self.module.sections_mut() {
let exports = match *section {
Section::Export(ref mut s) => s,
_ => continue,
};
exports.entries_mut().retain(|export| {
!export.field().starts_with("__wbindgen") || required.contains(export.field())
});
}
}
fn expose_drop_ref(&mut self) {
if !self.exposed_globals.insert("drop_ref") {
return;
}
self.expose_global_slab();
self.expose_global_slab_next();
let validate_owned = if self.config.debug {
String::from(
"
if ((idx & 1) === 1) throw new Error('cannot drop ref of stack objects');
",
)
} else {
String::new()
};
let dec_ref = if self.config.debug {
String::from(
"
if (typeof(obj) === 'number') throw new Error('corrupt slab');
obj.cnt -= 1;
if (obj.cnt > 0) return;
",
)
} else {
String::from(
"
obj.cnt -= 1;
if (obj.cnt > 0) return;
",
)
};
self.global(&format!(
"
function dropRef(idx) {{
{}
idx = idx >> 1;
if (idx < {}) return;
let obj = slab[idx];
{}
// If we hit 0 then free up our space in the slab
slab[idx] = slab_next;
slab_next = idx;
}}
",
validate_owned, INITIAL_SLAB_VALUES.len(), dec_ref
));
}
fn expose_global_stack(&mut self) {
if !self.exposed_globals.insert("stack") {
return;
}
self.global(&format!(
"
const stack = [];
"
));
if self.config.debug {
self.export(
"assertStackEmpty",
"
function() {
if (stack.length === 0) return;
throw new Error('stack is not currently empty');
}
",
None,
);
}
}
fn expose_global_slab(&mut self) {
if !self.exposed_globals.insert("slab") {
return;
}
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(
"assertSlabEmpty",
&format!(
"
function() {{
for (let i = {}; i < slab.length; i++) {{
if (typeof(slab[i]) === 'number') continue;
throw new Error('slab is not currently empty');
}}
}}
",
initial_values.len()
),
None,
);
}
}
fn expose_global_slab_next(&mut self) {
if !self.exposed_globals.insert("slab_next") {
return;
}
self.expose_global_slab();
self.global(
"
let slab_next = slab.length;
",
);
}
fn expose_get_object(&mut self) {
if !self.exposed_globals.insert("get_object") {
return;
}
self.expose_global_stack();
self.expose_global_slab();
let get_obj = if self.config.debug {
String::from(
"
if (typeof(val) === 'number') throw new Error('corrupt slab');
return val.obj;
",
)
} else {
String::from(
"
return val.obj;
",
)
};
self.global(&format!(
"
function getObject(idx) {{
if ((idx & 1) === 1) {{
return stack[idx >> 1];
}} else {{
const val = slab[idx >> 1];
{}
}}
}}
",
get_obj
));
}
fn expose_assert_num(&mut self) {
if !self.exposed_globals.insert("assert_num") {
return;
}
self.global(&format!(
"
function _assertNum(n) {{
if (typeof(n) !== 'number') throw new Error('expected a number argument');
}}
"
));
}
fn expose_assert_bool(&mut self) {
if !self.exposed_globals.insert("assert_bool") {
return;
}
self.global(&format!(
"
function _assertBoolean(n) {{
if (typeof(n) !== 'boolean') {{
throw new Error('expected a boolean argument');
}}
}}
"
));
}
fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("pass_string_to_wasm") {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.expose_text_encoder();
self.expose_uint8_memory();
let debug = if self.config.debug {
"
if (typeof(arg) !== 'string') throw new Error('expected a string argument');
"
} else {
""
};
self.global(&format!(
"
function passStringToWasm(arg) {{
{}
const buf = cachedEncoder.encode(arg);
const ptr = wasm.__wbindgen_malloc(buf.length);
getUint8Memory().set(buf, ptr);
return [ptr, buf.length];
}}
",
debug
));
Ok(())
}
fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint8_memory();
self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1)
}
fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint16_memory();
self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2)
}
fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint32_memory();
self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4)
}
fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint64_memory();
self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8)
}
fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> {
self.expose_f32_memory();
self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4)
}
fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> {
self.expose_f64_memory();
self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8)
}
fn expose_pass_array_jsvalue_to_wasm(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("pass_array_jsvalue") {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.expose_uint32_memory();
self.expose_add_heap_object();
self.global("
function passArrayJsValueToWasm(array) {
const ptr = wasm.__wbindgen_malloc(array.length * 4);
const mem = getUint32Memory();
for (let i = 0; i < array.length; i++) {
mem[ptr / 4 + i] = addHeapObject(array[i]);
}
return [ptr, array.length];
}
");
Ok(())
}
fn pass_array_to_wasm(
&mut self,
name: &'static str,
delegate: &str,
size: usize,
) -> Result<(), Error> {
if !self.exposed_globals.insert(name) {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.global(&format!(
"
function {}(arg) {{
const ptr = wasm.__wbindgen_malloc(arg.length * {size});
{}().set(arg, ptr / {size});
return [ptr, arg.length];
}}
",
name,
delegate,
size = size
));
Ok(())
}
fn expose_text_encoder(&mut self) {
if !self.exposed_globals.insert("text_encoder") {
return;
}
if self.config.nodejs_experimental_modules {
self.imports
.push_str("import { TextEncoder } from 'util';\n");
} else if self.config.nodejs {
self.global(
"
const TextEncoder = require('util').TextEncoder;
",
);
} else if !(self.config.browser || self.config.no_modules) {
self.global(
"
const TextEncoder = typeof self === 'object' && self.TextEncoder
? self.TextEncoder
: require('util').TextEncoder;
",
);
}
self.global(
"
let cachedEncoder = new TextEncoder('utf-8');
",
);
}
fn expose_text_decoder(&mut self) {
if !self.exposed_globals.insert("text_decoder") {
return;
}
if self.config.nodejs_experimental_modules {
self.imports
.push_str("import { TextDecoder } from 'util';\n");
} else if self.config.nodejs {
self.global(
"
const TextDecoder = require('util').TextDecoder;
",
);
} else if !(self.config.browser || self.config.no_modules) {
self.global(
"
const TextDecoder = typeof self === 'object' && self.TextDecoder
? self.TextDecoder
: require('util').TextDecoder;
",
);
}
self.global(
"
let cachedDecoder = new TextDecoder('utf-8');
",
);
}
fn expose_constructor_token(&mut self) {
if !self.exposed_globals.insert("ConstructorToken") {
return;
}
self.global(
"
class ConstructorToken {
constructor(ptr) {
this.ptr = ptr;
}
}
",
);
}
fn expose_get_string_from_wasm(&mut self) {
if !self.exposed_globals.insert("get_string_from_wasm") {
return;
}
self.expose_text_decoder();
self.expose_uint8_memory();
self.global(
"
function getStringFromWasm(ptr, len) {
return cachedDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}
",
);
}
fn expose_get_array_js_value_from_wasm(&mut self) {
if !self.exposed_globals.insert("get_array_js_value_from_wasm") {
return;
}
self.expose_uint32_memory();
self.expose_take_object();
self.global(
"
function getArrayJsValueFromWasm(ptr, len) {
const mem = getUint32Memory();
const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
for (let i = 0; i < slice.length; i++) {
result.push(takeObject(slice[i]));
}
return result;
}
",
);
}
fn expose_get_array_i8_from_wasm(&mut self) {
self.expose_int8_memory();
self.arrayget("getArrayI8FromWasm", "getInt8Memory", 1);
}
fn expose_get_array_u8_from_wasm(&mut self) {
self.expose_uint8_memory();
self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1);
}
fn expose_get_array_i16_from_wasm(&mut self) {
self.expose_int16_memory();
self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2);
}
fn expose_get_array_u16_from_wasm(&mut self) {
self.expose_uint16_memory();
self.arrayget("getArrayU16FromWasm", "getUint16Memory", 2);
}
fn expose_get_array_i32_from_wasm(&mut self) {
self.expose_int32_memory();
self.arrayget("getArrayI32FromWasm", "getInt32Memory", 4);
}
fn expose_get_array_u32_from_wasm(&mut self) {
self.expose_uint32_memory();
self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4);
}
fn expose_get_array_i64_from_wasm(&mut self) {
self.expose_int64_memory();
self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8);
}
fn expose_get_array_u64_from_wasm(&mut self) {
self.expose_uint64_memory();
self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8);
}
fn expose_get_array_f32_from_wasm(&mut self) {
self.expose_f32_memory();
self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4);
}
fn expose_get_array_f64_from_wasm(&mut self) {
self.expose_f64_memory();
self.arrayget("getArrayF64FromWasm", "getFloat64Memory", 8);
}
fn arrayget(&mut self, name: &'static str, mem: &'static str, size: usize) {
if !self.exposed_globals.insert(name) {
return;
}
self.global(&format!(
"
function {name}(ptr, len) {{
return {mem}().subarray(ptr / {size}, ptr / {size} + len);
}}
",
name = name,
mem = mem,
size = size,
));
}
fn expose_int8_memory(&mut self) {
self.memview("getInt8Memory", "Int8Array");
}
fn expose_uint8_memory(&mut self) {
self.memview("getUint8Memory", "Uint8Array");
}
fn expose_int16_memory(&mut self) {
self.memview("getInt16Memory", "Int16Array");
}
fn expose_uint16_memory(&mut self) {
self.memview("getUint16Memory", "Uint16Array");
}
fn expose_int32_memory(&mut self) {
self.memview("getInt32Memory", "Int32Array");
}
fn expose_uint32_memory(&mut self) {
self.memview("getUint32Memory", "Uint32Array");
}
fn expose_int64_memory(&mut self) {
self.memview("getInt64Memory", "BigInt64Array");
}
fn expose_uint64_memory(&mut self) {
self.memview("getUint64Memory", "BigUint64Array");
}
fn expose_f32_memory(&mut self) {
self.memview("getFloat32Memory", "Float32Array");
}
fn expose_f64_memory(&mut self) {
self.memview("getFloat64Memory", "Float64Array");
}
fn memview_function(&mut self, t: VectorKind) -> &'static str {
match t {
VectorKind::String => {
self.expose_uint8_memory();
"getUint8Memory"
}
VectorKind::I8 => {
self.expose_int8_memory();
"getInt8Memory"
}
VectorKind::U8 => {
self.expose_uint8_memory();
"getUint8Memory"
}
VectorKind::I16 => {
self.expose_int16_memory();
"getInt16Memory"
}
VectorKind::U16 => {
self.expose_uint16_memory();
"getUint16Memory"
}
VectorKind::I32 => {
self.expose_int32_memory();
"getInt32Memory"
}
VectorKind::U32 => {
self.expose_uint32_memory();
"getUint32Memory"
}
VectorKind::I64 => {
self.expose_int64_memory();
"getInt64Memory"
}
VectorKind::U64 => {
self.expose_uint64_memory();
"getUint64Memory"
}
VectorKind::F32 => {
self.expose_f32_memory();
"getFloat32Memory"
}
VectorKind::F64 => {
self.expose_f64_memory();
"getFloat64Memory"
}
VectorKind::Anyref => {
self.expose_uint32_memory();
"getUint32Memory"
}
}
}
fn memview(&mut self, name: &'static str, js: &str) {
if !self.exposed_globals.insert(name) {
return;
}
self.global(&format!(
"
let cache{name} = null;
function {name}() {{
if (cache{name} === null || cache{name}.buffer !== wasm.memory.buffer) {{
cache{name} = new {js}(wasm.memory.buffer);
}}
return cache{name};
}}
",
name = name,
js = js,
));
}
fn expose_assert_class(&mut self) {
if !self.exposed_globals.insert("assert_class") {
return;
}
self.global(
"
function _assertClass(instance, klass) {
if (!(instance instanceof klass)) {
throw new Error(`expected instance of ${klass.name}`);
}
return instance.ptr;
}
",
);
}
fn expose_borrowed_objects(&mut self) {
if !self.exposed_globals.insert("borrowed_objects") {
return;
}
self.expose_global_stack();
self.global(
"
function addBorrowedObject(obj) {
stack.push(obj);
return ((stack.length - 1) << 1) | 1;
}
",
);
}
fn expose_take_object(&mut self) {
if !self.exposed_globals.insert("take_object") {
return;
}
self.expose_get_object();
self.expose_drop_ref();
self.global(
"
function takeObject(idx) {
const ret = getObject(idx);
dropRef(idx);
return ret;
}
",
);
}
fn expose_add_heap_object(&mut self) {
if !self.exposed_globals.insert("add_heap_object") {
return;
}
self.expose_global_slab();
self.expose_global_slab_next();
let set_slab_next = if self.config.debug {
String::from(
"
if (typeof(next) !== 'number') throw new Error('corrupt slab');
slab_next = next;
",
)
} else {
String::from(
"
slab_next = next;
",
)
};
self.global(&format!(
"
function addHeapObject(obj) {{
if (slab_next === slab.length) slab.push(slab.length + 1);
const idx = slab_next;
const next = slab[idx];
{}
slab[idx] = {{ obj, cnt: 1 }};
return idx << 1;
}}
",
set_slab_next
));
}
fn wasm_import_needed(&self, name: &str) -> bool {
let imports = match self.module.import_section() {
Some(s) => s,
None => return false,
};
imports
.entries()
.iter()
.any(|i| i.module() == "__wbindgen_placeholder__" && i.field() == name)
}
fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> {
let s = match t {
VectorKind::String => {
self.expose_pass_string_to_wasm()?;
"passStringToWasm"
}
VectorKind::I8 | VectorKind::U8 => {
self.expose_pass_array8_to_wasm()?;
"passArray8ToWasm"
}
VectorKind::U16 | VectorKind::I16 => {
self.expose_pass_array16_to_wasm()?;
"passArray16ToWasm"
}
VectorKind::I32 | VectorKind::U32 => {
self.expose_pass_array32_to_wasm()?;
"passArray32ToWasm"
}
VectorKind::I64 | VectorKind::U64 => {
self.expose_pass_array64_to_wasm()?;
"passArray64ToWasm"
}
VectorKind::F32 => {
self.expose_pass_array_f32_to_wasm()?;
"passArrayF32ToWasm"
}
VectorKind::F64 => {
self.expose_pass_array_f64_to_wasm()?;
"passArrayF64ToWasm"
}
VectorKind::Anyref => {
self.expose_pass_array_jsvalue_to_wasm()?;
"passArrayJsValueToWasm"
}
};
Ok(s)
}
fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> &'static str {
match ty {
VectorKind::String => {
self.expose_get_string_from_wasm();
"getStringFromWasm"
}
VectorKind::I8 => {
self.expose_get_array_i8_from_wasm();
"getArrayI8FromWasm"
}
VectorKind::U8 => {
self.expose_get_array_u8_from_wasm();
"getArrayU8FromWasm"
}
VectorKind::I16 => {
self.expose_get_array_i16_from_wasm();
"getArrayI16FromWasm"
}
VectorKind::U16 => {
self.expose_get_array_u16_from_wasm();
"getArrayU16FromWasm"
}
VectorKind::I32 => {
self.expose_get_array_i32_from_wasm();
"getArrayI32FromWasm"
}
VectorKind::U32 => {
self.expose_get_array_u32_from_wasm();
"getArrayU32FromWasm"
}
VectorKind::I64 => {
self.expose_get_array_i64_from_wasm();
"getArrayI64FromWasm"
}
VectorKind::U64 => {
self.expose_get_array_u64_from_wasm();
"getArrayU64FromWasm"
}
VectorKind::F32 => {
self.expose_get_array_f32_from_wasm();
"getArrayF32FromWasm"
}
VectorKind::F64 => {
self.expose_get_array_f64_from_wasm();
"getArrayF64FromWasm"
}
VectorKind::Anyref => {
self.expose_get_array_js_value_from_wasm();
"getArrayJsValueFromWasm"
}
}
}
fn expose_get_global_argument(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("get_global_argument") {
return Ok(());
}
self.expose_uint32_memory();
self.expose_global_argument_ptr()?;
self.global(
"
function getGlobalArgument(arg) {
const idx = globalArgumentPtr() / 4 + arg;
return getUint32Memory()[idx];
}
",
);
Ok(())
}
fn expose_global_argument_ptr(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("global_argument_ptr") {
return Ok(());
}
self.require_internal_export("__wbindgen_global_argument_ptr")?;
self.global(
"
let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
if (cachedGlobalArgumentPtr === null) {
cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
}
return cachedGlobalArgumentPtr;
}
",
);
Ok(())
}
fn expose_get_inherited_descriptor(&mut self) {
if !self.exposed_globals.insert("get_inherited_descriptor") {
return;
}
// It looks like while rare some browsers will move descriptors up the
// property chain which runs the risk of breaking wasm-bindgen-generated
// code because we're looking for precise descriptor functions rather
// than relying on the prototype chain like most "normal JS" projects
// do.
//
// As a result we have a small helper here which will walk the prototype
// chain looking for a descriptor. For some more information on this see
// #109
self.global(
"
function GetOwnOrInheritedPropertyDescriptor(obj, id) {
while (obj) {
let desc = Object.getOwnPropertyDescriptor(obj, id);
if (desc) return desc;
obj = Object.getPrototypeOf(obj);
}
throw new Error(`descriptor for id='${id}' not found`);
}
",
);
}
fn expose_u32_cvt_shim(&mut self) -> &'static str {
let name = "u32CvtShim";
if !self.exposed_globals.insert(name) {
return name;
}
self.global(&format!("const {} = new Uint32Array(2);", name));
name
}
fn expose_int64_cvt_shim(&mut self) -> &'static str {
let name = "int64CvtShim";
if !self.exposed_globals.insert(name) {
return name;
}
let n = self.expose_u32_cvt_shim();
self.global(&format!(
"const {} = new BigInt64Array({}.buffer);",
name, n
));
name
}
fn expose_uint64_cvt_shim(&mut self) -> &'static str {
let name = "uint64CvtShim";
if !self.exposed_globals.insert(name) {
return name;
}
let n = self.expose_u32_cvt_shim();
self.global(&format!(
"const {} = new BigUint64Array({}.buffer);",
name, n
));
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 module = module.parse_names().unwrap_or_else(|p| p.1);
let result = wasm_gc::Config::new()
.demangle(self.config.demangle)
.keep_debug(self.config.keep_debug || self.config.debug)
.run(module, |m| parity_wasm::serialize(m).unwrap())?;
*self.module = match result.into_module() {
Ok(m) => m,
Err(result) => deserialize_buffer(&result.into_bytes()?)?,
};
Ok(())
}
fn describe(&self, name: &str) -> Option<Descriptor> {
let name = format!("__wbindgen_describe_{}", name);
(self.run_descriptor)(&name).map(|d| Descriptor::decode(&d))
}
fn global(&mut self, s: &str) {
let s = s;
let s = s.trim();
// Ensure a blank line between adjacent items, and ensure everything is
// terminated with a newline.
while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
self.globals.push_str("\n");
}
self.globals.push_str(s);
self.globals.push_str("\n");
}
fn use_node_require(&self) -> bool {
self.config.nodejs && !self.config.nodejs_experimental_modules
}
}
impl<'a, 'b> SubContext<'a, 'b> {
pub fn generate(&mut self) -> Result<(), Error> {
for f in self.program.exports.iter() {
self.generate_export(f).with_context(|_| {
format!(
"failed to generate bindings for Rust export `{}`",
f.function.name
)
})?;
}
for f in self.program.imports.iter() {
self.generate_import(f)?;
}
for e in self.program.enums.iter() {
self.generate_enum(e);
}
for s in self.program.structs.iter() {
let mut class = self
.cx
.exported_classes
.entry(s.name.clone())
.or_insert_with(Default::default);
class.comments = format_doc_comments(&s.comments, None);
class.fields.extend(s.fields.iter().map(|f| ClassField {
name: f.name.clone(),
readonly: f.readonly,
comments: f.comments.clone(),
}));
}
Ok(())
}
fn generate_export(&mut self, export: &shared::Export) -> Result<(), Error> {
if let Some(ref class) = export.class {
return self.generate_export_for_class(class, export);
}
let descriptor = match self.cx.describe(&export.function.name) {
None => return Ok(()),
Some(d) => d,
};
let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
.process(descriptor.unwrap_function())?
.finish("function", &format!("wasm.{}", export.function.name));
self.cx.export(
&export.function.name,
&js,
Some(format_doc_comments(&export.comments, Some(js_doc))),
);
self.cx.globals.push_str("\n");
self.cx.typescript.push_str("export ");
self.cx.typescript.push_str(&ts);
self.cx.typescript.push_str("\n");
Ok(())
}
fn generate_export_for_class(
&mut self,
class_name: &str,
export: &shared::Export,
) -> Result<(), Error> {
let wasm_name = shared::struct_function_export_name(class_name, &export.function.name);
let descriptor = match self.cx.describe(&wasm_name) {
None => return Ok(()),
Some(d) => d,
};
let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
.method(export.method, export.consumed)
.process(descriptor.unwrap_function())?
.finish("", &format!("wasm.{}", wasm_name));
let class = self
.cx
.exported_classes
.entry(class_name.to_string())
.or_insert(ExportedClass::default());
class
.contents
.push_str(&format_doc_comments(&export.comments, Some(js_doc)));
if !export.method {
class.contents.push_str("static ");
class.typescript.push_str("static ");
}
let constructors: Vec<String> = self
.program
.exports
.iter()
.filter(|x| x.class == Some(class_name.to_string()))
.filter_map(|x| x.constructor.clone())
.collect();
class.constructor = match constructors.len() {
0 => None,
1 => Some(constructors[0].clone()),
x @ _ => bail!("there must be only one constructor, not {}", x),
};
class.contents.push_str(&export.function.name);
class.contents.push_str(&js);
class.contents.push_str("\n");
class.typescript.push_str(&ts);
class.typescript.push_str("\n");
Ok(())
}
fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> {
match import.kind {
shared::ImportKind::Function(ref f) => {
self.generate_import_function(import, f).with_context(|_| {
format!(
"failed to generate bindings for JS import `{}`",
f.function.name
)
})?;
}
shared::ImportKind::Static(ref s) => {
self.generate_import_static(import, s).with_context(|_| {
format!("failed to generate bindings for JS import `{}`", s.name)
})?;
}
shared::ImportKind::Type(ref ty) => {
self.generate_import_type(import, ty).with_context(|_| {
format!(
"failed to generate bindings for JS import `{}`",
ty.name,
)
})?;
}
shared::ImportKind::Enum(_) => {}
}
Ok(())
}
fn generate_import_static(
&mut self,
info: &shared::Import,
import: &shared::ImportStatic,
) -> Result<(), Error> {
// TODO: should support more types to import here
let obj = self.import_name(info, &import.name)?;
self.cx.expose_add_heap_object();
self.cx.export(
&import.shim,
&format!(
"
function() {{
return addHeapObject({});
}}
",
obj
),
None,
);
Ok(())
}
fn generate_import_function(
&mut self,
info: &shared::Import,
import: &shared::ImportFunction,
) -> Result<(), Error> {
if !self.cx.wasm_import_needed(&import.shim) {
return Ok(());
}
let descriptor = match self.cx.describe(&import.shim) {
None => return Ok(()),
Some(d) => d,
};
let target = match &import.method {
Some(shared::MethodData { class, kind }) => {
let class = self.import_name(info, class)?;
match kind {
shared::MethodKind::Constructor => format!("new {}", class),
shared::MethodKind::Operation(shared::Operation { is_static, kind }) => {
let target = if import.structural {
let location = if *is_static { &class } else { "this" };
match kind {
shared::OperationKind::Regular => {
let nargs = descriptor.unwrap_function().arguments.len();
let mut s = format!("function(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(") { \nreturn this.");
s.push_str(&import.function.name);
s.push_str("(");
for i in 0..nargs - 1 {
if i > 0 {
drop(write!(s, ", "));
}
drop(write!(s, "x{}", i));
}
s.push_str(");\n}");
s
}
shared::OperationKind::Getter(g) => format!(
"function() {{
return {}.{};
}}",
location, g
),
shared::OperationKind::Setter(s) => format!(
"function(y) {{
{}.{} = y;
}}",
location, s
),
shared::OperationKind::IndexingGetter => format!(
"function(y) {{
return {}[y];
}}",
location
),
shared::OperationKind::IndexingSetter => format!(
"function(y, z) {{
{}[y] = z;
}}",
location
),
shared::OperationKind::IndexingDeleter => format!(
"function(y) {{
delete {}[y];
}}",
location
),
}
} else {
let (location, binding) = if *is_static {
("", format!(".bind({})", class))
} else {
(".prototype", "".into())
};
match kind {
shared::OperationKind::Regular => {
format!("{}{}.{}{}", class, location, import.function.name, binding)
}
shared::OperationKind::Getter(g) => {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}",
class, location, g, binding,
)
}
shared::OperationKind::Setter(s) => {
self.cx.expose_get_inherited_descriptor();
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}",
class, location, s, binding,
)
}
shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"),
}
};
let fallback = if import.structural {
"".to_string()
} else {
format!(
" || function() {{
throw new Error(`wasm-bindgen: {} does not exist`);
}}",
target
)
};
self.cx.global(&format!(
"
const {}_target = {} {} ;
",
import.shim, target, fallback
));
format!(
"{}_target{}",
import.shim,
if *is_static { "" } else { ".call" }
)
}
}
}
None => {
let name = self.import_name(info, &import.function.name)?;
if name.contains(".") {
self.cx.global(&format!(
"
const {}_target = {};
",
import.shim, name
));
format!("{}_target", import.shim)
} else {
name
}
}
};
let js = Rust2Js::new(self.cx)
.catch(import.catch)
.process(descriptor.unwrap_function())?
.finish(&target);
self.cx.export(&import.shim, &js, None);
Ok(())
}
fn generate_import_type(
&mut self,
info: &shared::Import,
import: &shared::ImportType,
) -> Result<(), Error> {
if !self.cx.wasm_import_needed(&import.instanceof_shim) {
return Ok(());
}
let name = self.import_name(info, &import.name)?;
self.cx.expose_get_object();
let body = format!("
function(idx) {{
return getObject(idx) instanceof {} ? 1 : 0;
}}
",
name,
);
self.cx.export(&import.instanceof_shim, &body, None);
Ok(())
}
fn generate_enum(&mut self, enum_: &shared::Enum) {
let mut variants = String::new();
for variant in enum_.variants.iter() {
variants.push_str(&format!("{}:{},", variant.name, variant.value));
}
self.cx.export(
&enum_.name,
&format!("Object.freeze({{ {} }})", variants),
Some(format_doc_comments(&enum_.comments, None)),
);
self.cx
.typescript
.push_str(&format!("export enum {} {{", enum_.name));
variants.clear();
for variant in enum_.variants.iter() {
variants.push_str(&format!("{},", variant.name));
}
self.cx.typescript.push_str(&variants);
self.cx.typescript.push_str("}\n");
}
fn import_name(&mut self, import: &shared::Import, item: &str) -> Result<String, Error> {
// First up, imports don't work at all in `--no-modules` mode as we're
// not sure how to import them.
if self.cx.config.no_modules {
if let Some(module) = &import.module {
bail!(
"import from `{}` module not allowed with `--no-modules`; \
use `--nodejs` or `--browser` instead",
module
);
}
}
// Figure out what identifier we're importing from the module. If we've
// got a namespace we use that, otherwise it's the name specified above.
let name_to_import = import.js_namespace
.as_ref()
.map(|s| &**s)
.unwrap_or(item);
// Here's where it's a bit tricky. We need to make sure that importing
// the same identifier from two different modules works, and they're
// named uniquely below. Additionally if we've already imported the same
// identifier from the module in question then we'd like to reuse the
// one that was previously imported.
//
// Our `imported_names` map keeps track of all imported identifiers from
// modules, mapping the imported names onto names actually available for
// use in our own module. If our identifier isn't present then we
// generate a new identifier and are sure to generate the appropriate JS
// import for our new identifier.
let use_node_require = self.cx.use_node_require();
let imported_identifiers = &mut self.cx.imported_identifiers;
let imports = &mut self.cx.imports;
let identifier = self.cx.imported_names.entry(import.module.clone())
.or_insert_with(Default::default)
.entry(name_to_import.to_string())
.or_insert_with(|| {
let name = generate_identifier(name_to_import, imported_identifiers);
if let Some(module) = &import.module {
if use_node_require {
imports.push_str(&format!(
"const {} = require('{}').{};\n",
name, module, name_to_import
));
} else if name_to_import == name {
imports.push_str(&format!(
"import {{ {} }} from '{}';\n",
name, module
));
} else {
imports.push_str(&format!(
"import {{ {} as {} }} from '{}';\n",
name_to_import, name, module
));
}
}
name
});
// If there's a namespace we didn't actually import `item` but rather
// the namespace, so access through that.
if import.js_namespace.is_some() {
Ok(format!("{}.{}", identifier, item))
} else {
Ok(identifier.to_string())
}
}
}
fn generate_identifier(name: &str, used_names: &mut HashMap<String, usize>) -> String {
let cnt = used_names.entry(name.to_string()).or_insert(0);
*cnt += 1;
if *cnt == 1 {
name.to_string()
} else {
format!("{}{}", name, cnt)
}
}
fn format_doc_comments(comments: &Vec<String>, js_doc_comments: Option<String>) -> String {
let body: String = comments
.iter()
.map(|c| format!("*{}\n", c.trim_matches('"')))
.collect();
let doc = if let Some(docs) = js_doc_comments {
docs.lines().map(|l| format!("* {} \n", l)).collect()
} else {
String::new()
};
format!("/**\n{}{}*/\n", body, doc)
}