use crate::descriptor::VectorKind;
use crate::intrinsic::Intrinsic;
use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue};
use crate::wit::{AdapterKind, Instruction, InstructionData};
use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux};
use crate::{Bindgen, EncodeInto, OutputMode};
use anyhow::{anyhow, bail, Context as _, Error};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use walrus::{FunctionId, ImportId, MemoryId, Module, TableId};
mod binding;
pub struct Context<'a> {
globals: String,
imports_post: String,
typescript: String,
exposed_globals: Option<HashSet<Cow<'static, str>>>,
next_export_idx: usize,
config: &'a Bindgen,
pub module: &'a mut Module,
aux: &'a WasmBindgenAux,
wit: &'a NonstandardWitSection,
js_imports: HashMap<String, Vec<(String, Option<String>)>>,
wasm_import_definitions: HashMap<ImportId, String>,
imported_names: HashMap<JsImportName, String>,
defined_identifiers: HashMap<String, usize>,
exported_classes: Option<BTreeMap<String, ExportedClass>>,
pub npm_dependencies: HashMap<String, (PathBuf, String)>,
memory_indices: HashMap<MemoryId, usize>,
table_indices: HashMap<TableId, usize>,
}
#[derive(Default)]
pub struct ExportedClass {
comments: String,
contents: String,
typescript: String,
has_constructor: bool,
wrap_needed: bool,
is_inspectable: bool,
readable_properties: Vec<String>,
typescript_fields: HashMap<String, (String, bool)>,
}
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
const INITIAL_HEAP_OFFSET: usize = 32;
impl<'a> Context<'a> {
pub fn new(
module: &'a mut Module,
config: &'a Bindgen,
wit: &'a NonstandardWitSection,
aux: &'a WasmBindgenAux,
) -> Result<Context<'a>, Error> {
Ok(Context {
globals: String::new(),
imports_post: String::new(),
typescript: "/* tslint:disable */\n/* eslint-disable */\n".to_string(),
exposed_globals: Some(Default::default()),
imported_names: Default::default(),
js_imports: Default::default(),
defined_identifiers: Default::default(),
wasm_import_definitions: Default::default(),
exported_classes: Some(Default::default()),
config,
module,
npm_dependencies: Default::default(),
next_export_idx: 0,
wit,
aux,
memory_indices: Default::default(),
table_indices: Default::default(),
})
}
fn should_write_global(&mut self, name: impl Into<Cow<'static, str>>) -> bool {
self.exposed_globals.as_mut().unwrap().insert(name.into())
}
fn export(
&mut self,
export_name: &str,
contents: &str,
comments: Option<String>,
) -> Result<(), Error> {
let definition_name = generate_identifier(export_name, &mut self.defined_identifiers);
if contents.starts_with("class") && definition_name != export_name {
bail!("cannot shadow already defined class `{}`", export_name);
}
let contents = contents.trim();
if let Some(ref c) = comments {
self.globals.push_str(c);
self.typescript.push_str(c);
}
let global = match self.config.mode {
OutputMode::Node {
experimental_modules: false,
} => {
if contents.starts_with("class") {
format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
} else {
format!("module.exports.{} = {};\n", export_name, contents)
}
}
OutputMode::NoModules { .. } => {
if contents.starts_with("class") {
format!("{}\n__exports.{1} = {1};\n", contents, export_name)
} else {
format!("__exports.{} = {};\n", export_name, contents)
}
}
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
}
| OutputMode::Web => {
if contents.starts_with("function") {
let body = &contents[8..];
if export_name == definition_name {
format!("export function {}{}\n", export_name, body)
} else {
format!(
"function {}{}\nexport {{ {} as {} }};\n",
definition_name, body, definition_name, export_name,
)
}
} else if contents.starts_with("class") {
assert_eq!(export_name, definition_name);
format!("export {}\n", contents)
} else {
assert_eq!(export_name, definition_name);
format!("export const {} = {};\n", export_name, contents)
}
}
};
self.global(&global);
Ok(())
}
pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
self.write_classes()?;
let needs_manual_start = self.unstart_start_function();
drop(self.exposed_globals.take().unwrap());
self.finalize_js(module_name, needs_manual_start)
}
fn finalize_js(
&mut self,
module_name: &str,
needs_manual_start: bool,
) -> Result<(String, String), Error> {
let mut ts = self.typescript.clone();
let mut js = String::new();
if self.config.mode.no_modules() {
js.push_str("(function() {\n");
}
let mut init = (String::new(), String::new());
let mut footer = String::new();
let mut imports = self.js_import_header()?;
match &self.config.mode {
OutputMode::NoModules { global } => {
js.push_str("const __exports = {};\n");
js.push_str("let wasm;\n");
init = self.gen_init(needs_manual_start, None)?;
footer.push_str(&format!(
"self.{} = Object.assign(init, __exports);\n",
global
));
}
OutputMode::Node {
experimental_modules: false,
} => {
js.push_str("let wasm;\n");
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
let import = self.module.imports.get_mut(*id);
import.module = format!("./{}.js", module_name);
footer.push_str("\nmodule.exports.");
footer.push_str(&import.name);
footer.push_str(" = ");
footer.push_str(js.trim());
footer.push_str(";\n");
}
footer.push_str(&format!("wasm = require('./{}_bg');\n", module_name));
if needs_manual_start {
footer.push_str("wasm.__wbindgen_start();\n");
}
}
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
} => {
imports.push_str(&format!(
"import * as wasm from './{}_bg.wasm';\n",
module_name
));
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
let import = self.module.imports.get_mut(*id);
import.module = format!("./{}.js", module_name);
footer.push_str("\nexport const ");
footer.push_str(&import.name);
footer.push_str(" = ");
footer.push_str(js.trim());
footer.push_str(";\n");
}
if needs_manual_start {
footer.push_str("\nwasm.__wbindgen_start();\n");
}
}
OutputMode::Web => {
self.imports_post.push_str("let wasm;\n");
init = self.gen_init(needs_manual_start, Some(&mut imports))?;
footer.push_str("export default init;\n");
}
}
let (init_js, init_ts) = init;
ts.push_str(&init_ts);
assert!(
!self.config.mode.uses_es_modules() || js.is_empty(),
"ES modules require imports to be at the start of the file"
);
js.push_str(&imports);
js.push_str("\n");
js.push_str(&self.imports_post);
js.push_str("\n");
js.push_str(&self.globals);
js.push_str("\n");
js.push_str(&init_js);
js.push_str("\n");
js.push_str(&footer);
js.push_str("\n");
if self.config.mode.no_modules() {
js.push_str("})();\n");
}
while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n");
}
Ok((js, ts))
}
fn js_import_header(&self) -> Result<String, Error> {
let mut imports = String::new();
match &self.config.mode {
OutputMode::NoModules { .. } => {
for (module, _items) in self.js_imports.iter() {
bail!(
"importing from `{}` isn't supported with `--target no-modules`",
module
);
}
}
OutputMode::Node {
experimental_modules: false,
} => {
for (module, items) in crate::sorted_iter(&self.js_imports) {
imports.push_str("const { ");
for (i, (item, rename)) in items.iter().enumerate() {
if i > 0 {
imports.push_str(", ");
}
imports.push_str(item);
if let Some(other) = rename {
imports.push_str(": ");
imports.push_str(other)
}
}
imports.push_str(" } = require(String.raw`");
imports.push_str(module);
imports.push_str("`);\n");
}
}
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
}
| OutputMode::Web => {
for (module, items) in crate::sorted_iter(&self.js_imports) {
imports.push_str("import { ");
for (i, (item, rename)) in items.iter().enumerate() {
if i > 0 {
imports.push_str(", ");
}
imports.push_str(item);
if let Some(other) = rename {
imports.push_str(" as ");
imports.push_str(other)
}
}
imports.push_str(" } from '");
imports.push_str(module);
imports.push_str("';\n");
}
}
}
Ok(imports)
}
fn ts_for_init_fn(has_memory: bool, has_module_or_path_optional: bool) -> String {
let (memory_doc, memory_param) = if has_memory {
(
"* @param {WebAssembly.Memory} maybe_memory\n",
", maybe_memory: WebAssembly.Memory",
)
} else {
("", "")
};
let arg_optional = if has_module_or_path_optional { "?" } else { "" };
format!(
"\n\
/**\n\
* If `module_or_path` is {{RequestInfo}}, makes a request and\n\
* for everything else, calls `WebAssembly.instantiate` directly.\n\
*\n\
* @param {{RequestInfo | BufferSource | WebAssembly.Module}} module_or_path\n\
{}\
*\n\
* @returns {{Promise<any>}}\n\
*/\n\
export default function init \
(module_or_path{}: RequestInfo | BufferSource | WebAssembly.Module{}): Promise<any>;
",
memory_doc, arg_optional, memory_param
)
}
fn gen_init(
&mut self,
needs_manual_start: bool,
mut imports: Option<&mut String>,
) -> Result<(String, String), Error> {
let module_name = "wbg";
let mut init_memory_arg = "";
let mut init_memory1 = String::new();
let mut init_memory2 = String::new();
let mut has_memory = false;
if let Some(mem) = self.module.memories.iter().next() {
if let Some(id) = mem.import {
self.module.imports.get_mut(id).module = module_name.to_string();
let mut memory = String::from("new WebAssembly.Memory({");
memory.push_str(&format!("initial:{}", mem.initial));
if let Some(max) = mem.maximum {
memory.push_str(&format!(",maximum:{}", max));
}
if mem.shared {
memory.push_str(",shared:true");
}
memory.push_str("})");
self.imports_post.push_str("let memory;\n");
init_memory1 = format!("memory = imports.{}.memory = maybe_memory;", module_name);
init_memory2 = format!("memory = imports.{}.memory = {};", module_name, memory);
init_memory_arg = ", maybe_memory";
has_memory = true;
}
}
let default_module_path = match self.config.mode {
OutputMode::Web => {
"\
if (typeof module === 'undefined') {
module = import.meta.url.replace(/\\.js$/, '_bg.wasm');
}"
}
OutputMode::NoModules { .. } => {
"\
if (typeof module === 'undefined') {
let src;
if (self.document === undefined) {
src = self.location.href;
} else {
src = self.document.currentScript.src;
}
module = src.replace(/\\.js$/, '_bg.wasm');
}"
}
_ => "",
};
let ts = Self::ts_for_init_fn(has_memory, !default_module_path.is_empty());
let mut imports_init = String::new();
if self.wasm_import_definitions.len() > 0 {
imports_init.push_str("imports.");
imports_init.push_str(module_name);
imports_init.push_str(" = {};\n");
}
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
let import = self.module.imports.get_mut(*id);
import.module = module_name.to_string();
imports_init.push_str("imports.");
imports_init.push_str(module_name);
imports_init.push_str(".");
imports_init.push_str(&import.name);
imports_init.push_str(" = ");
imports_init.push_str(js.trim());
imports_init.push_str(";\n");
}
let extra_modules = self
.module
.imports
.iter()
.filter(|i| !self.wasm_import_definitions.contains_key(&i.id()))
.filter(|i| {
match i.kind {
walrus::ImportKind::Memory(_) => false,
_ => true,
}
})
.map(|i| &i.module)
.collect::<BTreeSet<_>>();
for (i, extra) in extra_modules.iter().enumerate() {
let imports = match &mut imports {
Some(list) => list,
None => bail!(
"cannot import from modules (`{}`) with `--no-modules`",
extra
),
};
imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra));
imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i));
}
let js = format!(
"\
function init(module{init_memory_arg}) {{
{default_module_path}
let result;
const imports = {{}};
{imports_init}
if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{
{init_memory2}
const response = fetch(module);
if (typeof WebAssembly.instantiateStreaming === 'function') {{
result = WebAssembly.instantiateStreaming(response, imports)
.catch(e => {{
return response
.then(r => {{
if (r.headers.get('Content-Type') != 'application/wasm') {{
console.warn(\"`WebAssembly.instantiateStreaming` failed \
because your server does not serve wasm with \
`application/wasm` MIME type. Falling back to \
`WebAssembly.instantiate` which is slower. Original \
error:\\n\", e);
return r.arrayBuffer();
}} else {{
throw e;
}}
}})
.then(bytes => WebAssembly.instantiate(bytes, imports));
}});
}} else {{
result = response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}}
}} else {{
{init_memory1}
result = WebAssembly.instantiate(module, imports)
.then(result => {{
if (result instanceof WebAssembly.Instance) {{
return {{ instance: result, module }};
}} else {{
return result;
}}
}});
}}
return result.then(({{instance, module}}) => {{
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
{start}
return wasm;
}});
}}
",
init_memory_arg = init_memory_arg,
default_module_path = default_module_path,
init_memory1 = init_memory1,
init_memory2 = init_memory2,
start = if needs_manual_start {
"wasm.__wbindgen_start();"
} else {
""
},
imports_init = imports_init,
);
Ok((js, ts))
}
fn write_classes(&mut self) -> Result<(), Error> {
for (class, exports) in self.exported_classes.take().unwrap() {
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.has_constructor {
dst.push_str(
"
constructor() {
throw new Error('cannot invoke `new` directly');
}
",
);
}
if class.wrap_needed {
dst.push_str(&format!(
"
static __wrap(ptr) {{
const obj = Object.create({}.prototype);
obj.ptr = ptr;
{}
return obj;
}}
",
name,
if self.config.weak_refs {
format!("{}FinalizationGroup.register(obj, obj.ptr, obj.ptr);", name)
} else {
String::new()
},
));
}
if self.config.weak_refs {
self.global(&format!(
"
const {}FinalizationGroup = new FinalizationGroup((items) => {{
for (const ptr of items) {{
wasm.{}(ptr);
}}
}});
",
name,
wasm_bindgen_shared::free_function(&name),
));
}
if class.is_inspectable {
dst.push_str(&format!(
"
toJSON() {{
return {{{}}};
}}
toString() {{
return JSON.stringify(this);
}}
",
class
.readable_properties
.iter()
.fold(String::from("\n"), |fields, field_name| {
format!("{}{name}: this.{name},\n", fields, name = field_name)
})
));
if self.config.mode.nodejs() {
let module_name = self.import_name(&JsImport {
name: JsImportName::Module {
module: "util".to_string(),
name: "inspect".to_string(),
},
fields: Vec::new(),
})?;
dst.push_str(&format!(
"
[{}.custom]() {{
return Object.assign(Object.create({{constructor: this.constructor}}), this.toJSON());
}}
",
module_name
));
}
}
dst.push_str(&format!(
"
free() {{
const ptr = this.ptr;
this.ptr = 0;
{}
wasm.{}(ptr);
}}
",
if self.config.weak_refs {
format!("{}FinalizationGroup.unregister(ptr);", name)
} else {
String::new()
},
wasm_bindgen_shared::free_function(&name),
));
ts_dst.push_str(" free(): void;\n");
dst.push_str(&class.contents);
ts_dst.push_str(&class.typescript);
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
fields.sort(); for name in fields {
let (ty, has_setter) = &class.typescript_fields[name];
ts_dst.push_str(" ");
if !has_setter {
ts_dst.push_str("readonly ");
}
ts_dst.push_str(name);
ts_dst.push_str(": ");
ts_dst.push_str(ty);
ts_dst.push_str(";\n");
}
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 expose_drop_ref(&mut self) {
if !self.should_write_global("drop_ref") {
return;
}
self.expose_global_heap();
self.expose_global_heap_next();
self.global(&format!(
"
function dropObject(idx) {{
if (idx < {}) return;
heap[idx] = heap_next;
heap_next = idx;
}}
",
INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(),
));
}
fn expose_global_heap(&mut self) {
if !self.should_write_global("heap") {
return;
}
assert!(!self.config.anyref);
self.global(&format!("const heap = new Array({});", INITIAL_HEAP_OFFSET));
self.global("heap.fill(undefined);");
self.global(&format!("heap.push({});", INITIAL_HEAP_VALUES.join(", ")));
}
fn expose_global_heap_next(&mut self) {
if !self.should_write_global("heap_next") {
return;
}
self.expose_global_heap();
self.global("let heap_next = heap.length;");
}
fn expose_get_object(&mut self) {
if !self.should_write_global("get_object") {
return;
}
self.expose_global_heap();
self.global("function getObject(idx) { return heap[idx]; }");
}
fn expose_not_defined(&mut self) {
if !self.should_write_global("not_defined") {
return;
}
self.global(
"function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
);
}
fn expose_assert_num(&mut self) {
if !self.should_write_global("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.should_write_global("assert_bool") {
return;
}
self.global(&format!(
"
function _assertBoolean(n) {{
if (typeof(n) !== 'boolean') {{
throw new Error('expected a boolean argument');
}}
}}
"
));
}
fn expose_wasm_vector_len(&mut self) {
if !self.should_write_global("wasm_vector_len") {
return;
}
self.global("let WASM_VECTOR_LEN = 0;");
}
fn expose_pass_string_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
self.expose_wasm_vector_len();
let debug = if self.config.debug {
"
if (typeof(arg) !== 'string') throw new Error('expected a string argument');
"
} else {
""
};
if self.config.mode.nodejs() {
let get_buf = self.expose_node_buffer_memory(memory);
let ret = MemView {
name: "passStringToWasm",
num: get_buf.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
self.global(&format!(
"
function {}(arg, malloc) {{
{}
const len = Buffer.byteLength(arg);
const ptr = malloc(len);
{}().write(arg, ptr, len);
WASM_VECTOR_LEN = len;
return ptr;
}}
",
ret, debug, get_buf,
));
return Ok(ret);
}
let mem = self.expose_uint8_memory(memory);
let ret = MemView {
name: "passStringToWasm",
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
self.expose_text_encoder()?;
let encode = "function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
}";
let encode_into = "function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}";
let shared = self.module.memories.get(memory).shared;
match self.config.encode_into {
EncodeInto::Always if !shared => {
self.global(&format!(
"
const encodeString = {};
",
encode_into
));
}
EncodeInto::Test if !shared => {
self.global(&format!(
"
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? {}
: {});
",
encode_into, encode
));
}
_ => {
self.global(&format!(
"
const encodeString = {};
",
encode
));
}
}
let encode_as_ascii = format!(
"\
if (realloc === undefined) {{
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
{mem}().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}}
let len = arg.length;
let ptr = malloc(len);
const mem = {mem}();
let offset = 0;
for (; offset < len; offset++) {{
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}}
",
mem = mem,
);
self.global(&format!(
"function {name}(arg, malloc, realloc) {{
{debug}
{ascii}
if (offset !== len) {{
if (offset !== 0) {{
arg = arg.slice(offset);
}}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = {mem}().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
{debug_end}
offset += ret.written;
}}
WASM_VECTOR_LEN = offset;
return ptr;
}}",
name = ret,
debug = debug,
ascii = encode_as_ascii,
mem = mem,
debug_end = if self.config.debug {
"if (ret.read !== arg.length) throw new Error('failed to pass whole string');"
} else {
""
},
));
Ok(ret)
}
fn expose_pass_array8_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_uint8_memory(memory);
self.pass_array_to_wasm("passArray8ToWasm", view, 1)
}
fn expose_pass_array16_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_uint16_memory(memory);
self.pass_array_to_wasm("passArray16ToWasm", view, 2)
}
fn expose_pass_array32_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_uint32_memory(memory);
self.pass_array_to_wasm("passArray32ToWasm", view, 4)
}
fn expose_pass_array64_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_uint64_memory(memory);
self.pass_array_to_wasm("passArray64ToWasm", view, 8)
}
fn expose_pass_array_f32_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_f32_memory(memory);
self.pass_array_to_wasm("passArrayF32ToWasm", view, 4)
}
fn expose_pass_array_f64_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let view = self.expose_f64_memory(memory);
self.pass_array_to_wasm("passArrayF64ToWasm", view, 8)
}
fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let mem = self.expose_uint32_memory(memory);
let ret = MemView {
name: "passArrayJsValueToWasm",
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
self.expose_wasm_vector_len();
match (self.aux.anyref_table, self.aux.anyref_alloc) {
(Some(table), Some(alloc)) => {
let add = self.expose_add_to_anyref_table(table, alloc)?;
self.global(&format!(
"
function {}(array, malloc) {{
const ptr = malloc(array.length * 4);
const mem = {}();
for (let i = 0; i < array.length; i++) {{
mem[ptr / 4 + i] = {}(array[i]);
}}
WASM_VECTOR_LEN = array.length;
return ptr;
}}
",
ret, mem, add,
));
}
_ => {
self.expose_add_heap_object();
self.global(&format!(
"
function {}(array, malloc) {{
const ptr = malloc(array.length * 4);
const mem = {}();
for (let i = 0; i < array.length; i++) {{
mem[ptr / 4 + i] = addHeapObject(array[i]);
}}
WASM_VECTOR_LEN = array.length;
return ptr;
}}
",
ret, mem,
));
}
}
Ok(ret)
}
fn pass_array_to_wasm(
&mut self,
name: &'static str,
view: MemView,
size: usize,
) -> Result<MemView, Error> {
let ret = MemView {
name,
num: view.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
self.expose_wasm_vector_len();
self.global(&format!(
"
function {}(arg, malloc) {{
const ptr = malloc(arg.length * {size});
{}().set(arg, ptr / {size});
WASM_VECTOR_LEN = arg.length;
return ptr;
}}
",
ret,
view,
size = size
));
Ok(ret)
}
fn expose_text_encoder(&mut self) -> Result<(), Error> {
if !self.should_write_global("text_encoder") {
return Ok(());
}
self.expose_text_processor("TextEncoder", "('utf-8')")
}
fn expose_text_decoder(&mut self) -> Result<(), Error> {
if !self.should_write_global("text_decoder") {
return Ok(());
}
self.expose_text_processor("TextDecoder", "('utf-8', { ignoreBOM: true, fatal: true })")?;
self.global("cachedTextDecoder.decode();");
Ok(())
}
fn expose_text_processor(&mut self, s: &str, args: &str) -> Result<(), Error> {
if self.config.mode.nodejs() {
let name = self.import_name(&JsImport {
name: JsImportName::Module {
module: "util".to_string(),
name: s.to_string(),
},
fields: Vec::new(),
})?;
self.global(&format!("let cached{} = new {}{};", s, name, args));
} else if !self.config.mode.always_run_in_browser() {
self.global(&format!(
"
const l{0} = typeof {0} === 'undefined' ? \
require('util').{0} : {0};\
",
s
));
self.global(&format!("let cached{0} = new l{0}{1};", s, args));
} else {
self.global(&format!("let cached{0} = new {0}{1};", s, args));
}
Ok(())
}
fn expose_get_string_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
self.expose_text_decoder()?;
let mem = self.expose_uint8_memory(memory);
let ret = MemView {
name: "getStringFromWasm",
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
let is_shared = self.module.memories.get(memory).shared;
let method = if is_shared { "slice" } else { "subarray" };
self.global(&format!(
"
function {}(ptr, len) {{
return cachedTextDecoder.decode({}().{}(ptr, ptr + len));
}}
",
ret, mem, method
));
Ok(ret)
}
fn expose_get_cached_string_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
self.expose_get_object();
let get = self.expose_get_string_from_wasm(memory)?;
let ret = MemView {
name: "getCachedStringFromWasm",
num: get.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
self.global(&format!(
"
function {}(ptr, len) {{
if (ptr === 0) {{
return getObject(len);
}} else {{
return {}(ptr, len);
}}
}}
",
ret, get,
));
Ok(ret)
}
fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
let mem = self.expose_uint32_memory(memory);
let ret = MemView {
name: "getArrayJsValueFromWasm",
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
return Ok(ret);
}
match (self.aux.anyref_table, self.aux.anyref_drop_slice) {
(Some(table), Some(drop)) => {
let table = self.export_name_of(table);
let drop = self.export_name_of(drop);
self.global(&format!(
"
function {}(ptr, len) {{
const mem = {}();
const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
for (let i = 0; i < slice.length; i++) {{
result.push(wasm.{}.get(slice[i]));
}}
wasm.{}(ptr, len);
return result;
}}
",
ret, mem, table, drop,
));
}
_ => {
self.expose_take_object();
self.global(&format!(
"
function {}(ptr, len) {{
const mem = {}();
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;
}}
",
ret, mem,
));
}
}
Ok(ret)
}
fn expose_get_array_i8_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_int8_memory(memory);
self.arrayget("getArrayI8FromWasm", view, 1)
}
fn expose_get_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_uint8_memory(memory);
self.arrayget("getArrayU8FromWasm", view, 1)
}
fn expose_get_clamped_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_clamped_uint8_memory(memory);
self.arrayget("getClampedArrayU8FromWasm", view, 1)
}
fn expose_get_array_i16_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_int16_memory(memory);
self.arrayget("getArrayI16FromWasm", view, 2)
}
fn expose_get_array_u16_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_uint16_memory(memory);
self.arrayget("getArrayU16FromWasm", view, 2)
}
fn expose_get_array_i32_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_int32_memory(memory);
self.arrayget("getArrayI32FromWasm", view, 4)
}
fn expose_get_array_u32_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_uint32_memory(memory);
self.arrayget("getArrayU32FromWasm", view, 4)
}
fn expose_get_array_i64_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_int64_memory(memory);
self.arrayget("getArrayI64FromWasm", view, 8)
}
fn expose_get_array_u64_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_uint64_memory(memory);
self.arrayget("getArrayU64FromWasm", view, 8)
}
fn expose_get_array_f32_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_f32_memory(memory);
self.arrayget("getArrayF32FromWasm", view, 4)
}
fn expose_get_array_f64_from_wasm(&mut self, memory: MemoryId) -> MemView {
let view = self.expose_f64_memory(memory);
self.arrayget("getArrayF64FromWasm", view, 8)
}
fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView {
let ret = MemView {
name,
num: view.num,
};
if !self.should_write_global(name) {
return ret;
}
self.global(&format!(
"
function {name}(ptr, len) {{
return {mem}().subarray(ptr / {size}, ptr / {size} + len);
}}
",
name = ret,
mem = view,
size = size,
));
return ret;
}
fn expose_node_buffer_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getNodeBufferMemory", "Buffer.from", memory)
}
fn expose_int8_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getInt8Memory", "new Int8Array", memory)
}
fn expose_uint8_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getUint8Memory", "new Uint8Array", memory)
}
fn expose_clamped_uint8_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getUint8ClampedMemory", "new Uint8ClampedArray", memory)
}
fn expose_int16_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getInt16Memory", "new Int16Array", memory)
}
fn expose_uint16_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getUint16Memory", "new Uint16Array", memory)
}
fn expose_int32_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getInt32Memory", "new Int32Array", memory)
}
fn expose_uint32_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getUint32Memory", "new Uint32Array", memory)
}
fn expose_int64_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getInt64Memory", "new BigInt64Array", memory)
}
fn expose_uint64_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getUint64Memory", "new BigUint64Array", memory)
}
fn expose_f32_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getFloat32Memory", "new Float32Array", memory)
}
fn expose_f64_memory(&mut self, memory: MemoryId) -> MemView {
self.memview("getFloat64Memory", "new Float64Array", memory)
}
fn memview_function(&mut self, t: VectorKind, memory: MemoryId) -> MemView {
match t {
VectorKind::String => self.expose_uint8_memory(memory),
VectorKind::I8 => self.expose_int8_memory(memory),
VectorKind::U8 => self.expose_uint8_memory(memory),
VectorKind::ClampedU8 => self.expose_clamped_uint8_memory(memory),
VectorKind::I16 => self.expose_int16_memory(memory),
VectorKind::U16 => self.expose_uint16_memory(memory),
VectorKind::I32 => self.expose_int32_memory(memory),
VectorKind::U32 => self.expose_uint32_memory(memory),
VectorKind::I64 => self.expose_int64_memory(memory),
VectorKind::U64 => self.expose_uint64_memory(memory),
VectorKind::F32 => self.expose_f32_memory(memory),
VectorKind::F64 => self.expose_f64_memory(memory),
VectorKind::Anyref => self.expose_uint32_memory(memory),
}
}
fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView {
let view = self.memview_memory(name, memory);
if !self.should_write_global(name.to_string()) {
return view;
}
let mem = self.export_name_of(memory);
self.global(&format!(
"
let cache{name} = null;
function {name}() {{
if (cache{name} === null || cache{name}.buffer !== wasm.{mem}.buffer) {{
cache{name} = {js}(wasm.{mem}.buffer);
}}
return cache{name};
}}
",
name = view,
js = js,
mem = mem,
));
return view;
}
fn memview_memory(&mut self, name: &'static str, memory: walrus::MemoryId) -> MemView {
let next = self.memory_indices.len();
let num = *self.memory_indices.entry(memory).or_insert(next);
MemView { name, num }
}
fn memview_table(&mut self, name: &'static str, table: walrus::TableId) -> MemView {
let next = self.table_indices.len();
let num = *self.table_indices.entry(table).or_insert(next);
MemView { name, num }
}
fn expose_assert_class(&mut self) {
if !self.should_write_global("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_global_stack_pointer(&mut self) {
if !self.should_write_global("stack_pointer") {
return;
}
self.global(&format!("let stack_pointer = {};", INITIAL_HEAP_OFFSET));
}
fn expose_borrowed_objects(&mut self) {
if !self.should_write_global("borrowed_objects") {
return;
}
self.expose_global_heap();
self.expose_global_stack_pointer();
self.global(
"
function addBorrowedObject(obj) {
if (stack_pointer == 1) throw new Error('out of js stack');
heap[--stack_pointer] = obj;
return stack_pointer;
}
",
);
}
fn expose_take_object(&mut self) {
if !self.should_write_global("take_object") {
return;
}
self.expose_get_object();
self.expose_drop_ref();
self.global(
"
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
",
);
}
fn expose_add_heap_object(&mut self) {
if !self.should_write_global("add_heap_object") {
return;
}
self.expose_global_heap();
self.expose_global_heap_next();
let set_heap_next = if self.config.debug {
String::from(
"
if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
",
)
} else {
String::new()
};
self.global(&format!(
"
function addHeapObject(obj) {{
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
{}
heap[idx] = obj;
return idx;
}}
",
set_heap_next
));
}
fn expose_handle_error(&mut self) -> Result<(), Error> {
if !self.should_write_global("handle_error") {
return Ok(());
}
let store = self
.aux
.exn_store
.ok_or_else(|| anyhow!("failed to find `__wbindgen_exn_store` intrinsic"))?;
let store = self.export_name_of(store);
match (self.aux.anyref_table, self.aux.anyref_alloc) {
(Some(table), Some(alloc)) => {
let add = self.expose_add_to_anyref_table(table, alloc)?;
self.global(&format!(
"
function handleError(e) {{
const idx = {}(e);
wasm.{}(idx);
}}
",
add, store,
));
}
_ => {
self.expose_add_heap_object();
self.global(&format!(
"
function handleError(e) {{
wasm.{}(addHeapObject(e));
}}
",
store,
));
}
}
Ok(())
}
fn expose_log_error(&mut self) {
if !self.should_write_global("log_error") {
return;
}
self.global(
"\
function logError(e) {
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;
}
",
);
}
fn pass_to_wasm_function(&mut self, t: VectorKind, memory: MemoryId) -> Result<MemView, Error> {
match t {
VectorKind::String => self.expose_pass_string_to_wasm(memory),
VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => {
self.expose_pass_array8_to_wasm(memory)
}
VectorKind::U16 | VectorKind::I16 => self.expose_pass_array16_to_wasm(memory),
VectorKind::I32 | VectorKind::U32 => self.expose_pass_array32_to_wasm(memory),
VectorKind::I64 | VectorKind::U64 => self.expose_pass_array64_to_wasm(memory),
VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory),
VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory),
VectorKind::Anyref => self.expose_pass_array_jsvalue_to_wasm(memory),
}
}
fn expose_get_vector_from_wasm(
&mut self,
ty: VectorKind,
memory: MemoryId,
) -> Result<MemView, Error> {
Ok(match ty {
VectorKind::String => self.expose_get_string_from_wasm(memory)?,
VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory),
VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory),
VectorKind::ClampedU8 => self.expose_get_clamped_array_u8_from_wasm(memory),
VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory),
VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory),
VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory),
VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory),
VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory),
VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory),
VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory),
VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory),
VectorKind::Anyref => self.expose_get_array_js_value_from_wasm(memory)?,
})
}
fn expose_get_inherited_descriptor(&mut self) {
if !self.should_write_global("get_inherited_descriptor") {
return;
}
self.global(
"
function GetOwnOrInheritedPropertyDescriptor(obj, id) {
while (obj) {
let desc = Object.getOwnPropertyDescriptor(obj, id);
if (desc) return desc;
obj = Object.getPrototypeOf(obj);
}
return {};
}
",
);
}
fn expose_u32_cvt_shim(&mut self) -> &'static str {
let name = "u32CvtShim";
if !self.should_write_global(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.should_write_global(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.should_write_global(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.should_write_global("is_like_none") {
return;
}
self.global(
"
function isLikeNone(x) {
return x === undefined || x === null;
}
",
);
}
fn global(&mut self, s: &str) {
let s = s.trim();
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 require_class_wrap(&mut self, name: &str) {
require_class(&mut self.exported_classes, name).wrap_needed = true;
}
fn import_name(&mut self, import: &JsImport) -> Result<String, Error> {
if let Some(name) = self.imported_names.get(&import.name) {
let mut name = name.clone();
for field in import.fields.iter() {
name.push_str(".");
name.push_str(field);
}
return Ok(name.clone());
}
let js_imports = &mut self.js_imports;
let mut add_module_import = |module: String, name: &str, actual: &str| {
let rename = if name == actual {
None
} else {
Some(actual.to_string())
};
js_imports
.entry(module)
.or_insert(Vec::new())
.push((name.to_string(), rename));
};
let mut name = match &import.name {
JsImportName::Module { module, name } => {
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
add_module_import(module.clone(), name, &unique_name);
unique_name
}
JsImportName::LocalModule { module, name } => {
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
let module = self.config.local_module_name(module);
add_module_import(module, name, &unique_name);
unique_name
}
JsImportName::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
name,
} => {
let module = self
.config
.inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
add_module_import(module, name, &unique_name);
unique_name
}
JsImportName::VendorPrefixed { name, prefixes } => {
self.imports_post.push_str("const l");
self.imports_post.push_str(&name);
self.imports_post.push_str(" = ");
switch(&mut self.imports_post, name, "", prefixes);
self.imports_post.push_str(";\n");
fn switch(dst: &mut String, name: &str, prefix: &str, left: &[String]) {
if left.len() == 0 {
dst.push_str(prefix);
return dst.push_str(name);
}
dst.push_str("(typeof ");
dst.push_str(prefix);
dst.push_str(name);
dst.push_str(" !== 'undefined' ? ");
dst.push_str(prefix);
dst.push_str(name);
dst.push_str(" : ");
switch(dst, name, &left[0], &left[1..]);
dst.push_str(")");
}
format!("l{}", name)
}
JsImportName::Global { name } => {
let unique_name = generate_identifier(name, &mut self.defined_identifiers);
if unique_name != *name {
bail!("cannot import `{}` from two locations", name);
}
unique_name
}
};
self.imported_names
.insert(import.name.clone(), name.clone());
for field in import.fields.iter() {
name.push_str(".");
name.push_str(field);
}
Ok(name)
}
fn unstart_start_function(&mut self) -> bool {
let start = match self.module.start.take() {
Some(id) => id,
None => return false,
};
self.module.exports.add("__wbindgen_start", start);
true
}
fn expose_add_to_anyref_table(
&mut self,
table: TableId,
alloc: FunctionId,
) -> Result<MemView, Error> {
let view = self.memview_table("addToAnyrefTable", table);
assert!(self.config.anyref);
if !self.should_write_global(view.to_string()) {
return Ok(view);
}
let alloc = self.export_name_of(alloc);
let table = self.export_name_of(table);
self.global(&format!(
"
function {}(obj) {{
const idx = wasm.{}();
wasm.{}.set(idx, obj);
return idx;
}}
",
view, alloc, table,
));
Ok(view)
}
pub fn generate(&mut self) -> Result<(), Error> {
for (id, adapter) in crate::sorted_iter(&self.wit.adapters) {
let instrs = match &adapter.kind {
AdapterKind::Import { .. } => continue,
AdapterKind::Local { instructions } => instructions,
};
self.generate_adapter(*id, adapter, instrs)?;
}
let mut pairs = self.aux.export_map.iter().collect::<Vec<_>>();
pairs.sort_by_key(|(k, _)| *k);
check_duplicated_getter_and_setter_names(&pairs)?;
for e in self.aux.enums.iter() {
self.generate_enum(e)?;
}
for s in self.aux.structs.iter() {
self.generate_struct(s)?;
}
self.typescript.push_str(&self.aux.extra_typescript);
for path in self.aux.package_jsons.iter() {
self.process_package_json(path)?;
}
Ok(())
}
fn generate_adapter(
&mut self,
id: AdapterId,
adapter: &Adapter,
instrs: &[InstructionData],
) -> Result<(), Error> {
enum Kind<'a> {
Export(&'a AuxExport),
Import(walrus::ImportId),
Adapter,
}
let kind = match self.aux.export_map.get(&id) {
Some(export) => Kind::Export(export),
None => {
let core = self.wit.implements.iter().find(|pair| pair.2 == id);
match core {
Some((core, _, _)) => Kind::Import(*core),
None => Kind::Adapter,
}
}
};
let catch = self.aux.imports_with_catch.contains(&id);
if let Kind::Import(core) = kind {
if !catch && self.attempt_direct_import(core, instrs)? {
return Ok(());
}
}
let mut builder = binding::Builder::new(self);
builder.log_error(match kind {
Kind::Export(_) | Kind::Adapter => false,
Kind::Import(_) => builder.cx.config.debug,
});
builder.catch(catch);
let mut arg_names = &None;
match kind {
Kind::Export(export) => {
arg_names = &export.arg_names;
match &export.kind {
AuxExportKind::Function(_) => {}
AuxExportKind::StaticFunction { .. } => {}
AuxExportKind::Constructor(class) => builder.constructor(class),
AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => {
builder.method(false)
}
AuxExportKind::Method { consumed, .. } => builder.method(*consumed),
}
}
Kind::Import(_) => {}
Kind::Adapter => {}
}
let binding::JsFunction {
ts_sig,
ts_arg_tys,
ts_ret_ty,
js_doc,
code,
} = builder
.process(&adapter, instrs, arg_names)
.with_context(|| match kind {
Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
Kind::Import(i) => {
let i = builder.cx.module.imports.get(i);
format!(
"failed to generate bindings for import of `{}::{}`",
i.module, i.name
)
}
Kind::Adapter => format!("failed to generates bindings for adapter"),
})?;
match kind {
Kind::Export(export) => {
let docs = format_doc_comments(&export.comments, Some(js_doc));
match &export.kind {
AuxExportKind::Function(name) => {
self.export(&name, &format!("function{}", code), Some(docs))?;
self.globals.push_str("\n");
self.typescript.push_str("export function ");
self.typescript.push_str(&name);
self.typescript.push_str(&ts_sig);
self.typescript.push_str(";\n");
}
AuxExportKind::Constructor(class) => {
let exported = require_class(&mut self.exported_classes, class);
if exported.has_constructor {
bail!("found duplicate constructor for class `{}`", class);
}
exported.has_constructor = true;
exported.push(&docs, "constructor", "", &code, &ts_sig);
}
AuxExportKind::Getter { class, field } => {
let ret_ty = ts_ret_ty.unwrap();
let exported = require_class(&mut self.exported_classes, class);
exported.push_getter(&docs, field, &code, &ret_ty);
}
AuxExportKind::Setter { class, field } => {
let arg_ty = ts_arg_tys[0].clone();
let exported = require_class(&mut self.exported_classes, class);
exported.push_setter(&docs, field, &code, &arg_ty);
}
AuxExportKind::StaticFunction { class, name } => {
let exported = require_class(&mut self.exported_classes, class);
exported.push(&docs, name, "static ", &code, &ts_sig);
}
AuxExportKind::Method { class, name, .. } => {
let exported = require_class(&mut self.exported_classes, class);
exported.push(&docs, name, "", &code, &ts_sig);
}
}
}
Kind::Import(core) => {
self.wasm_import_definitions
.insert(core, format!("function{}", code));
}
Kind::Adapter => {
self.globals.push_str("function ");
self.globals.push_str(&self.adapter_name(id));
self.globals.push_str(&code);
self.globals.push_str("\n\n");
}
}
return Ok(());
}
fn import_never_log_error(&self, import: &AuxImport) -> bool {
match import {
AuxImport::Intrinsic(_) => true,
_ => false,
}
}
fn attempt_direct_import(
&mut self,
id: ImportId,
instrs: &[InstructionData],
) -> Result<bool, Error> {
let mut call = None;
for instr in instrs {
match instr.instr {
Instruction::CallAdapter(id) => {
if call.is_some() {
return Ok(false);
} else {
call = Some(id);
}
}
Instruction::CallExport(_)
| Instruction::CallTableElement(_)
| Instruction::Standard(wit_walrus::Instruction::CallCore(_))
| Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => {
return Ok(false)
}
_ => {}
}
}
let adapter = match call {
Some(id) => id,
None => return Ok(false),
};
match &self.wit.adapters[&adapter].kind {
AdapterKind::Import { kind, .. } => match kind {
AdapterJsImportKind::Normal => {}
AdapterJsImportKind::Method | AdapterJsImportKind::Constructor => return Ok(false),
},
AdapterKind::Local { .. } => return Ok(false),
}
let js = match &self.aux.import_map[&adapter] {
AuxImport::Value(AuxValue::Bare(js)) => js,
_ => return Ok(false),
};
if self.aux.imports_with_variadic.contains(&adapter) {
return Ok(false);
}
if !self.representable_without_js_glue(instrs) {
return Ok(false);
}
if js.fields.len() == 0 {
match &js.name {
JsImportName::Module { module, name } => {
let import = self.module.imports.get_mut(id);
import.module = module.clone();
import.name = name.clone();
return Ok(true);
}
JsImportName::LocalModule { module, name } => {
let module = self.config.local_module_name(module);
let import = self.module.imports.get_mut(id);
import.module = module;
import.name = name.clone();
return Ok(true);
}
JsImportName::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
name,
} => {
let module = self
.config
.inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
let import = self.module.imports.get_mut(id);
import.module = module;
import.name = name.clone();
return Ok(true);
}
JsImportName::Global { .. } | JsImportName::VendorPrefixed { .. } => {}
}
}
self.expose_not_defined();
let name = self.import_name(js)?;
let js = format!(
"typeof {name} == 'function' ? {name} : notDefined('{name}')",
name = name,
);
self.wasm_import_definitions.insert(id, js);
Ok(true)
}
fn representable_without_js_glue(&self, instrs: &[InstructionData]) -> bool {
use Instruction::*;
let standard_enabled = self.config.wasm_interface_types;
let mut last_arg = None;
let mut saw_call = false;
for instr in instrs {
match instr.instr {
Standard(_) if standard_enabled => {}
Standard(wit_walrus::Instruction::ArgGet(i)) => {
if saw_call {
return false;
}
match (i, last_arg) {
(0, None) => last_arg = Some(0),
(n, Some(i)) if n == i + 1 => last_arg = Some(n),
_ => return false,
}
}
CallAdapter(_) => saw_call = true,
Standard(wit_walrus::Instruction::IntToWasm { .. }) => {}
Standard(wit_walrus::Instruction::WasmToInt {
output: wit_walrus::ValType::U32,
..
}) => return false,
Standard(wit_walrus::Instruction::WasmToInt { .. }) => {}
I32FromBool => {}
_ => return false,
}
}
return true;
}
fn invoke_import(
&mut self,
import: &AuxImport,
kind: AdapterJsImportKind,
args: &[String],
variadic: bool,
prelude: &mut String,
) -> Result<String, Error> {
let variadic_args = |js_arguments: &[String]| {
Ok(if !variadic {
format!("{}", js_arguments.join(", "))
} else {
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 {
format!("{}, ...{}", args.join(", "), last_arg)
} else {
format!("...{}", last_arg)
}
})
};
match import {
AuxImport::Value(val) => match kind {
AdapterJsImportKind::Constructor => {
let js = match val {
AuxValue::Bare(js) => self.import_name(js)?,
_ => bail!("invalid import set for constructor"),
};
Ok(format!("new {}({})", js, variadic_args(&args)?))
}
AdapterJsImportKind::Method => {
let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
format!(
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
anchor, extra, field, which
)
};
let js = match val {
AuxValue::Bare(js) => self.import_name(js)?,
AuxValue::Getter(class, field) => {
self.expose_get_inherited_descriptor();
let class = self.import_name(class)?;
descriptor(&class, ".prototype", field, "get")
}
AuxValue::ClassGetter(class, field) => {
self.expose_get_inherited_descriptor();
let class = self.import_name(class)?;
descriptor(&class, "", field, "get")
}
AuxValue::Setter(class, field) => {
self.expose_get_inherited_descriptor();
let class = self.import_name(class)?;
descriptor(&class, ".prototype", field, "set")
}
AuxValue::ClassSetter(class, field) => {
self.expose_get_inherited_descriptor();
let class = self.import_name(class)?;
descriptor(&class, "", field, "set")
}
};
Ok(format!("{}.call({})", js, variadic_args(&args)?))
}
AdapterJsImportKind::Normal => {
let js = match val {
AuxValue::Bare(js) => self.import_name(js)?,
_ => bail!("invalid import set for free function"),
};
Ok(format!("{}({})", js, variadic_args(&args)?))
}
},
AuxImport::ValueWithThis(class, name) => {
let class = self.import_name(class)?;
Ok(format!("{}.{}({})", class, name, variadic_args(&args)?))
}
AuxImport::Instanceof(js) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
let js = self.import_name(js)?;
Ok(format!("{} instanceof {}", args[0], js))
}
AuxImport::Static(js) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 0);
self.import_name(js)
}
AuxImport::Closure {
dtor,
mutable,
adapter,
nargs,
} => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 3);
let arg_names = (0..*nargs)
.map(|i| format!("arg{}", i))
.collect::<Vec<_>>()
.join(", ");
let mut js = format!("({}) => {{\n", arg_names);
js.push_str("state.cnt++;\n");
let table = self.export_function_table()?;
let dtor = format!("wasm.{}.get({})", table, dtor);
let call = self.adapter_name(*adapter);
if *mutable {
js.push_str("const a = state.a;\n");
js.push_str("state.a = 0;\n");
js.push_str("try {\n");
js.push_str(&format!("return {}(a, state.b, {});\n", call, arg_names));
js.push_str("} finally {\n");
js.push_str("if (--state.cnt === 0) ");
js.push_str(&dtor);
js.push_str("(a, state.b);\n");
js.push_str("else state.a = a;\n");
js.push_str("}\n");
} else {
js.push_str("try {\n");
js.push_str(&format!(
"return {}(state.a, state.b, {});\n",
call, arg_names
));
js.push_str("} finally {\n");
js.push_str("if (--state.cnt === 0) {\n");
js.push_str(&dtor);
js.push_str("(state.a, state.b);\n");
js.push_str("state.a = 0;\n");
js.push_str("}\n");
js.push_str("}\n");
}
js.push_str("}\n");
prelude.push_str(&format!(
"
const state = {{ a: {arg0}, b: {arg1}, cnt: 1 }};
const real = {body};
real.original = state;
",
body = js,
arg0 = &args[0],
arg1 = &args[1],
));
Ok("real".to_string())
}
AuxImport::StructuralMethod(name) => {
assert!(kind == AdapterJsImportKind::Normal);
let (receiver, args) = match args.split_first() {
Some(pair) => pair,
None => bail!("structural method calls must have at least one argument"),
};
Ok(format!("{}.{}({})", receiver, name, variadic_args(args)?))
}
AuxImport::StructuralGetter(field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
Ok(format!("{}.{}", args[0], field))
}
AuxImport::StructuralClassGetter(class, field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 0);
let class = self.import_name(class)?;
Ok(format!("{}.{}", class, field))
}
AuxImport::StructuralSetter(field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 2);
Ok(format!("{}.{} = {}", args[0], field, args[1]))
}
AuxImport::StructuralClassSetter(class, field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
let class = self.import_name(class)?;
Ok(format!("{}.{} = {}", class, field, args[0]))
}
AuxImport::IndexingGetterOfClass(class) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
let class = self.import_name(class)?;
Ok(format!("{}[{}]", class, args[0]))
}
AuxImport::IndexingGetterOfObject => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 2);
Ok(format!("{}[{}]", args[0], args[1]))
}
AuxImport::IndexingSetterOfClass(class) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 2);
let class = self.import_name(class)?;
Ok(format!("{}[{}] = {}", class, args[0], args[1]))
}
AuxImport::IndexingSetterOfObject => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 3);
Ok(format!("{}[{}] = {}", args[0], args[1], args[2]))
}
AuxImport::IndexingDeleterOfClass(class) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
let class = self.import_name(class)?;
Ok(format!("delete {}[{}]", class, args[0]))
}
AuxImport::IndexingDeleterOfObject => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 2);
Ok(format!("delete {}[{}]", args[0], args[1]))
}
AuxImport::WrapInExportedClass(class) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
self.require_class_wrap(class);
Ok(format!("{}.__wrap({})", class, args[0]))
}
AuxImport::Intrinsic(intrinsic) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
self.invoke_intrinsic(intrinsic, args, prelude)
}
}
}
fn invoke_intrinsic(
&mut self,
intrinsic: &Intrinsic,
args: &[String],
prelude: &mut String,
) -> Result<String, Error> {
let expr = match intrinsic {
Intrinsic::JsvalEq => {
assert_eq!(args.len(), 2);
format!("{} === {}", args[0], args[1])
}
Intrinsic::IsFunction => {
assert_eq!(args.len(), 1);
format!("typeof({}) === 'function'", args[0])
}
Intrinsic::IsUndefined => {
assert_eq!(args.len(), 1);
format!("{} === undefined", args[0])
}
Intrinsic::IsNull => {
assert_eq!(args.len(), 1);
format!("{} === null", args[0])
}
Intrinsic::IsObject => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const val = {};\n", args[0]));
format!("typeof(val) === 'object' && val !== null")
}
Intrinsic::IsSymbol => {
assert_eq!(args.len(), 1);
format!("typeof({}) === 'symbol'", args[0])
}
Intrinsic::IsString => {
assert_eq!(args.len(), 1);
format!("typeof({}) === 'string'", args[0])
}
Intrinsic::IsFalsy => {
assert_eq!(args.len(), 1);
format!("!{}", args[0])
}
Intrinsic::ObjectCloneRef => {
assert_eq!(args.len(), 1);
args[0].clone()
}
Intrinsic::ObjectDropRef => {
assert_eq!(args.len(), 1);
args[0].clone()
}
Intrinsic::CallbackDrop => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {}.original;\n", args[0]));
prelude.push_str("if (obj.cnt-- == 1) {\n");
prelude.push_str("obj.a = 0;\n");
prelude.push_str("return true;\n");
prelude.push_str("}\n");
"false".to_string()
}
Intrinsic::CallbackForget => {
assert_eq!(args.len(), 1);
args[0].clone()
}
Intrinsic::NumberNew => {
assert_eq!(args.len(), 1);
args[0].clone()
}
Intrinsic::StringNew => {
assert_eq!(args.len(), 1);
args[0].clone()
}
Intrinsic::SymbolNamedNew => {
assert_eq!(args.len(), 1);
format!("Symbol({})", args[0])
}
Intrinsic::SymbolAnonymousNew => {
assert_eq!(args.len(), 0);
"Symbol()".to_string()
}
Intrinsic::NumberGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {};\n", args[0]));
format!("typeof(obj) === 'number' ? obj : undefined")
}
Intrinsic::StringGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {};\n", args[0]));
format!("typeof(obj) === 'string' ? obj : undefined")
}
Intrinsic::BooleanGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const v = {};\n", args[0]));
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
}
Intrinsic::Throw => {
assert_eq!(args.len(), 1);
format!("throw new Error({})", args[0])
}
Intrinsic::Rethrow => {
assert_eq!(args.len(), 1);
format!("throw {}", args[0])
}
Intrinsic::Module => {
assert_eq!(args.len(), 0);
if !self.config.mode.no_modules() && !self.config.mode.web() {
bail!(
"`wasm_bindgen::module` is currently only supported with \
`--target no-modules` and `--target web`"
);
}
format!("init.__wbindgen_wasm_module")
}
Intrinsic::Memory => {
assert_eq!(args.len(), 0);
let mut memories = self.module.memories.iter();
let memory = memories
.next()
.ok_or_else(|| anyhow!("no memory found to return in memory intrinsic"))?
.id();
if memories.next().is_some() {
bail!(
"multiple memories found, unsure which to return \
from memory intrinsic"
);
}
drop(memories);
format!("wasm.{}", self.export_name_of(memory))
}
Intrinsic::FunctionTable => {
assert_eq!(args.len(), 0);
let name = self.export_function_table()?;
format!("wasm.{}", name)
}
Intrinsic::DebugString => {
assert_eq!(args.len(), 1);
self.expose_debug_string();
format!("debugString({})", args[0])
}
Intrinsic::JsonParse => {
assert_eq!(args.len(), 1);
format!("JSON.parse({})", args[0])
}
Intrinsic::JsonSerialize => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {};\n", args[0]));
"JSON.stringify(obj === undefined ? null : obj)".to_string()
}
Intrinsic::AnyrefHeapLiveCount => {
assert_eq!(args.len(), 0);
self.expose_global_heap();
prelude.push_str(
"
let free_count = 0;
let next = heap_next;
while (next < heap.length) {
free_count += 1;
next = heap[next];
}
",
);
format!(
"heap.length - free_count - {} - {}",
INITIAL_HEAP_OFFSET,
INITIAL_HEAP_VALUES.len(),
)
}
Intrinsic::InitAnyrefTable => {
let table = self
.aux
.anyref_table
.ok_or_else(|| anyhow!("must enable anyref to use anyref intrinsic"))?;
let name = self.export_name_of(table);
let mut base = format!(
"
const table = wasm.{};
const offset = table.grow({});
table.set(0, undefined);
",
name,
INITIAL_HEAP_VALUES.len(),
);
for (i, value) in INITIAL_HEAP_VALUES.iter().enumerate() {
base.push_str(&format!("table.set(offset + {}, {});\n", i, value));
}
base
}
};
Ok(expr)
}
fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
let mut variants = String::new();
self.typescript
.push_str(&format!("export enum {} {{", enum_.name));
for (name, value) in enum_.variants.iter() {
variants.push_str(&format!("{}:{},", name, value));
self.typescript.push_str(&format!("\n {},", name));
}
self.typescript.push_str("\n}\n");
self.export(
&enum_.name,
&format!("Object.freeze({{ {} }})", variants),
Some(format_doc_comments(&enum_.comments, None)),
)?;
Ok(())
}
fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> {
let class = require_class(&mut self.exported_classes, &struct_.name);
class.comments = format_doc_comments(&struct_.comments, None);
class.is_inspectable = struct_.is_inspectable;
Ok(())
}
fn process_package_json(&mut self, path: &Path) -> Result<(), Error> {
if !self.config.mode.nodejs() && !self.config.mode.bundler() {
bail!(
"NPM dependencies have been specified in `{}` but \
this is only compatible with the `bundler` and `nodejs` targets",
path.display(),
);
}
let contents =
fs::read_to_string(path).context(format!("failed to read `{}`", path.display()))?;
let json: serde_json::Value = serde_json::from_str(&contents)?;
let object = match json.as_object() {
Some(s) => s,
None => bail!(
"expected `package.json` to have an JSON object in `{}`",
path.display()
),
};
let mut iter = object.iter();
let (key, value) = match iter.next() {
Some(pair) => pair,
None => return Ok(()),
};
if key != "dependencies" || iter.next().is_some() {
bail!(
"NPM manifest found at `{}` can currently only have one key, \
`dependencies`, and no other fields",
path.display()
);
}
let value = match value.as_object() {
Some(s) => s,
None => bail!(
"expected `dependencies` to be a JSON object in `{}`",
path.display()
),
};
for (name, value) in value.iter() {
let value = match value.as_str() {
Some(s) => s,
None => bail!(
"keys in `dependencies` are expected to be strings in `{}`",
path.display()
),
};
if let Some((prev, _prev_version)) = self.npm_dependencies.get(name) {
bail!(
"dependency on NPM package `{}` specified in two `package.json` files, \
which at the time is not allowed:\n * {}\n * {}",
name,
path.display(),
prev.display(),
)
}
self.npm_dependencies
.insert(name.to_string(), (path.to_path_buf(), value.to_string()));
}
Ok(())
}
fn expose_debug_string(&mut self) {
if !self.should_write_global("debug_string") {
return;
}
self.global(
"
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `\"${val}\"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
",
);
}
fn export_function_table(&mut self) -> Result<String, Error> {
match self.module.tables.main_function_table()? {
Some(id) => Ok(self.export_name_of(id)),
None => bail!("no function table found in module"),
}
}
fn export_name_of(&mut self, id: impl Into<walrus::ExportItem>) -> String {
let id = id.into();
let export = self.module.exports.iter().find(|e| {
use walrus::ExportItem::*;
match (e.item, id) {
(Function(a), Function(b)) => a == b,
(Table(a), Table(b)) => a == b,
(Memory(a), Memory(b)) => a == b,
(Global(a), Global(b)) => a == b,
_ => false,
}
});
if let Some(export) = export {
return export.name.clone();
}
let default_name = format!("__wbindgen_export_{}", self.next_export_idx);
self.next_export_idx += 1;
let name = match id {
walrus::ExportItem::Function(f) => match &self.module.funcs.get(f).name {
Some(s) => to_js_identifier(s),
None => default_name,
},
_ => default_name,
};
self.module.exports.add(&name, id);
return name;
fn to_js_identifier(name: &str) -> String {
name.chars()
.map(|c| {
if c.is_ascii() && (c.is_alphabetic() || c.is_numeric()) {
c
} else {
'_'
}
})
.collect()
}
}
fn adapter_name(&self, id: AdapterId) -> String {
format!("__wbg_adapter_{}", id.0)
}
}
fn check_duplicated_getter_and_setter_names(
exports: &[(&AdapterId, &AuxExport)],
) -> Result<(), Error> {
let verify_exports =
|first_class, first_field, second_class, second_field| -> Result<(), Error> {
let both_are_in_the_same_class = first_class == second_class;
let both_are_referencing_the_same_field = first_field == second_field;
if both_are_in_the_same_class && both_are_referencing_the_same_field {
bail!(format!(
"There can be only one getter/setter definition for `{}` in `{}`",
first_field, first_class
));
}
Ok(())
};
for (idx, (_, first_export)) in exports.iter().enumerate() {
for (_, second_export) in exports.iter().skip(idx + 1) {
match (&first_export.kind, &second_export.kind) {
(
AuxExportKind::Getter {
class: first_class,
field: first_field,
},
AuxExportKind::Getter {
class: second_class,
field: second_field,
},
) => verify_exports(first_class, first_field, second_class, second_field)?,
(
AuxExportKind::Setter {
class: first_class,
field: first_field,
},
AuxExportKind::Setter {
class: second_class,
field: second_field,
},
) => verify_exports(first_class, first_field, second_class, second_field)?,
_ => {}
}
}
}
Ok(())
}
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 != "default" {
name.to_string()
} else {
format!("{}{}", name, cnt)
}
}
fn format_doc_comments(comments: &str, js_doc_comments: Option<String>) -> String {
let body: String = comments.lines().map(|c| format!("*{}\n", c)).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)
}
fn require_class<'a>(
exported_classes: &'a mut Option<BTreeMap<String, ExportedClass>>,
name: &str,
) -> &'a mut ExportedClass {
exported_classes
.as_mut()
.expect("classes already written")
.entry(name.to_string())
.or_insert_with(ExportedClass::default)
}
impl ExportedClass {
fn push(&mut self, docs: &str, function_name: &str, function_prefix: &str, js: &str, ts: &str) {
self.contents.push_str(docs);
self.contents.push_str(function_prefix);
self.contents.push_str(function_name);
self.contents.push_str(js);
self.contents.push_str("\n");
self.typescript.push_str(docs);
self.typescript.push_str(" ");
self.typescript.push_str(function_prefix);
self.typescript.push_str(function_name);
self.typescript.push_str(ts);
self.typescript.push_str(";\n");
}
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
self.push_accessor(docs, field, js, "get ", ret_ty);
self.readable_properties.push(field.to_string());
}
fn push_setter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
let has_setter = self.push_accessor(docs, field, js, "set ", ret_ty);
*has_setter = true;
}
fn push_accessor(
&mut self,
docs: &str,
field: &str,
js: &str,
prefix: &str,
ret_ty: &str,
) -> &mut bool {
self.contents.push_str(docs);
self.contents.push_str(prefix);
self.contents.push_str(field);
self.contents.push_str(js);
self.contents.push_str("\n");
let (ty, has_setter) = self
.typescript_fields
.entry(field.to_string())
.or_insert_with(Default::default);
*ty = ret_ty.to_string();
has_setter
}
}
#[test]
fn test_generate_identifier() {
let mut used_names: HashMap<String, usize> = HashMap::new();
assert_eq!(
generate_identifier("someVar", &mut used_names),
"someVar".to_string()
);
assert_eq!(
generate_identifier("someVar", &mut used_names),
"someVar2".to_string()
);
assert_eq!(
generate_identifier("default", &mut used_names),
"default1".to_string()
);
assert_eq!(
generate_identifier("default", &mut used_names),
"default2".to_string()
);
}
struct MemView {
name: &'static str,
num: usize,
}
impl fmt::Display for MemView {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.name, self.num)
}
}