diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index aff91c6cfcc..18ba1208606 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -61,6 +61,8 @@ pub struct Context<'a> { exported_classes: Option>, + exported_namespaces: Option>, + /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. pub npm_dependencies: HashMap, @@ -120,6 +122,35 @@ struct FieldAccessor { is_optional: bool, } +struct ExportedNamespace { + name: String, + contents: String, + /// The TypeScript for the namespace's methods. + typescript: String, + /// Whether TypeScript for this namespace should be emitted (i.e., `skip_typescript` wasn't specified). + generate_typescript: bool, +} + +enum ClassOrNamespace<'a> { + Class(&'a mut ExportedClass), + Namespace(&'a mut ExportedNamespace), +} + +/// Different JS constructs that can be exported. +enum ExportJs<'a> { + /// A class of the form `class Name {...}`. + Class(&'a str), + /// An anonymous function expression of the form `function(...) {...}`. + /// + /// Note that the function name is not included in the string. + Function(&'a str), + /// An arbitrary JS expression. + Expression(&'a str), + /// A namespace as a statement with multiple assignments of the form + /// `.prop = value;` + Namespace(&'a str), +} + const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; // Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate const INITIAL_HEAP_OFFSET: usize = 128; @@ -143,6 +174,7 @@ impl<'a> Context<'a> { typescript_refs: Default::default(), used_string_enums: Default::default(), exported_classes: Some(Default::default()), + exported_namespaces: Some(Default::default()), config, threads_enabled: config.threads.is_enabled(module), module, @@ -163,38 +195,74 @@ impl<'a> Context<'a> { fn export( &mut self, export_name: &str, - contents: &str, + export: ExportJs, comments: Option<&str>, ) -> Result<(), Error> { - let definition_name = self.generate_identifier(export_name); - if contents.starts_with("class") && definition_name != export_name { + // The definition is intended to allow for exports to be renamed to + // avoid conflicts. Since namespaces intentionally have the same name as + // other exports, we must not rename them. + let definition_name = if matches!(export, ExportJs::Namespace(_)) { + export_name.to_owned() + } else { + self.generate_identifier(export_name) + }; + + if matches!(export, ExportJs::Class(_)) && definition_name != export_name { bail!("cannot shadow already defined class `{}`", export_name); } - let contents = contents.trim(); + // write out comments if let Some(c) = comments { self.globals.push_str(c); } + + fn namespace_init_arg(name: &str) -> String { + format!("{name} || ({name} = {{}})", name = name) + } + let global = match self.config.mode { - OutputMode::Node { module: false } => { - if contents.starts_with("class") { - format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name) - } else { - format!("module.exports.{} = {};\n", export_name, contents) + OutputMode::Node { module: false } => match export { + ExportJs::Class(class) => { + format!("{}\nmodule.exports.{1} = {1};\n", class, export_name) } - } - OutputMode::NoModules { .. } => { - if contents.starts_with("class") { - format!("{}\n__exports.{1} = {1};\n", contents, export_name) - } else { - format!("__exports.{} = {};\n", export_name, contents) + ExportJs::Function(expr) | ExportJs::Expression(expr) => { + format!("module.exports.{} = {};\n", export_name, expr) } - } + ExportJs::Namespace(namespace) => { + format!( + "(function({}) {{\n{}}})({});\n", + export_name, + namespace, + namespace_init_arg(&format!("module.exports.{}", export_name)) + ) + } + }, + OutputMode::NoModules { .. } => match export { + ExportJs::Class(class) => { + format!("{}\n__exports.{1} = {1};\n", class, export_name) + } + ExportJs::Function(expr) | ExportJs::Expression(expr) => { + format!("__exports.{} = {};\n", export_name, expr) + } + ExportJs::Namespace(namespace) => { + format!( + "(function({}) {{\n{}}})({});\n", + export_name, + namespace, + namespace_init_arg(&format!("__exports.{}", export_name)) + ) + } + }, OutputMode::Bundler { .. } | OutputMode::Node { module: true } | OutputMode::Web - | OutputMode::Deno => { - if let Some(body) = contents.strip_prefix("function") { + | OutputMode::Deno => match export { + ExportJs::Class(class) => { + assert_eq!(export_name, definition_name); + format!("export {}\n", class) + } + ExportJs::Function(function) => { + let body = function.strip_prefix("function").unwrap(); if export_name == definition_name { format!("export function {}{}\n", export_name, body) } else { @@ -203,14 +271,29 @@ impl<'a> Context<'a> { definition_name, body, definition_name, export_name, ) } - } else if contents.starts_with("class") { + } + ExportJs::Expression(expr) => { assert_eq!(export_name, definition_name); - format!("export {}\n", contents) - } else { + format!("export const {} = {};\n", export_name, expr) + } + ExportJs::Namespace(namespace) => { assert_eq!(export_name, definition_name); - format!("export const {} = {};\n", export_name, contents) + + // In some cases (e.g. string enums), a namespace may be + // exported without an existing object of the same name. + // In that case, we need to create the object before + // initializing the namespace. + // + // It's only correct to do it like this, because namespaces + // are always the last things to be exported. + let mut definition = String::new(); + if !self.defined_identifiers.contains_key(export_name) { + definition = format!("export const {} = {{}};\n", export_name) + } + definition.push_str(namespace); + definition } - } + }, }; self.global(&global); Ok(()) @@ -225,6 +308,9 @@ impl<'a> Context<'a> { // `__wrap` and such. self.write_classes()?; + // Write out generated JS for namespaces. + self.write_namespaces()?; + // Initialization is just flat out tricky and not something we // understand super well. To try to handle various issues that have come // up we always remove the `start` function if one is present. The JS @@ -1005,6 +1091,22 @@ __wbg_set_wasm(wasm);" Ok((js, ts)) } + fn require_class_or_namespace(&mut self, name: &str) -> ClassOrNamespace { + if self.aux.enums.contains_key(name) || self.aux.string_enums.contains_key(name) { + ClassOrNamespace::Namespace(self.require_namespace(name)) + } else { + ClassOrNamespace::Class(self.require_class(name)) + } + } + + fn require_class(&mut self, name: &str) -> &'_ mut ExportedClass { + self.exported_classes + .as_mut() + .expect("classes already written") + .entry(name.to_string()) + .or_default() + } + fn write_classes(&mut self) -> Result<(), Error> { for (class, exports) in self.exported_classes.take().unwrap() { self.write_class(&class, &exports)?; @@ -1152,10 +1254,10 @@ __wbg_set_wasm(wasm);" self.write_class_field_types(class, &mut ts_dst); - dst.push_str("}\n"); + dst.push('}'); ts_dst.push_str("}\n"); - self.export(name, &dst, Some(&class.comments))?; + self.export(name, ExportJs::Class(&dst), Some(&class.comments))?; if class.generate_typescript { self.typescript.push_str(&class.comments); @@ -1283,6 +1385,53 @@ __wbg_set_wasm(wasm);" } } + fn require_namespace(&mut self, name: &str) -> &'_ mut ExportedNamespace { + self.exported_namespaces + .as_mut() + .expect("namespaces already written") + .entry(name.to_string()) + .or_insert_with(|| { + let _enum = self.aux.enums.get(name); + let string_enum = self.aux.string_enums.get(name); + + let generate_typescript = _enum.map_or(true, |e| e.generate_typescript) + && string_enum.map_or(true, |e| e.generate_typescript); + + ExportedNamespace { + name: name.to_string(), + contents: String::new(), + typescript: String::new(), + generate_typescript, + } + }) + } + + fn write_namespaces(&mut self) -> Result<(), Error> { + for (class, namespace) in self.exported_namespaces.take().unwrap() { + self.write_namespace(&class, &namespace)?; + } + Ok(()) + } + + fn write_namespace(&mut self, name: &str, namespace: &ExportedNamespace) -> Result<(), Error> { + if namespace.contents.is_empty() { + // don't emit empty namespaces + return Ok(()); + } + + self.export(name, ExportJs::Namespace(&namespace.contents), None)?; + + if namespace.generate_typescript { + let ts_dst = format!( + "export namespace {name} {{\n{ts}}}\n", + ts = namespace.typescript + ); + self.typescript.push_str(&ts_dst); + } + + Ok(()) + } + fn expose_drop_ref(&mut self) { if !self.should_write_global("drop_ref") { return; @@ -2461,11 +2610,11 @@ __wbg_set_wasm(wasm);" } fn require_class_wrap(&mut self, name: &str) { - require_class(&mut self.exported_classes, name).wrap_needed = true; + self.require_class(name).wrap_needed = true; } fn require_class_unwrap(&mut self, name: &str) { - require_class(&mut self.exported_classes, name).unwrap_needed = true; + self.require_class(name).unwrap_needed = true; } fn add_module_import(&mut self, module: String, name: &str, actual: &str) { @@ -2809,6 +2958,10 @@ __wbg_set_wasm(wasm);" self.typescript_refs.extend(ts_refs); + fn warn(message: &str) { + println!("warning: {}", message); + } + // Once we've got all the JS then put it in the right location depending // on what's being exported. match kind { @@ -2831,11 +2984,25 @@ __wbg_set_wasm(wasm);" self.typescript.push_str(";\n"); } - self.export(name, &format!("function{}", code), Some(&js_docs))?; + self.export( + name, + ExportJs::Function(&format!("function{}", code)), + Some(&js_docs), + )?; self.globals.push('\n'); } AuxExportKind::Constructor(class) => { - let exported = require_class(&mut self.exported_classes, class); + let exported = self.require_class_or_namespace(class); + let exported = match exported { + ClassOrNamespace::Class(class) => class, + ClassOrNamespace::Namespace(_) => { + warn(&format!( + "constructor is not supported on `{}`. Enums do not support exported constructors.", + class + )); + return Ok(()); + } + }; if exported.has_constructor { bail!("found duplicate constructor for class `{}`", class); @@ -2850,21 +3017,34 @@ __wbg_set_wasm(wasm);" receiver, kind, } => { - let exported = require_class(&mut self.exported_classes, class); + let is_static = receiver.is_static(); + let mut exported = self.require_class_or_namespace(class); let mut prefix = String::new(); - if receiver.is_static() { + if is_static { prefix += "static "; } let ts = match kind { AuxExportedMethodKind::Method => ts_sig, AuxExportedMethodKind::Getter => { + let class = match exported { + ClassOrNamespace::Class(ref mut class) => class, + ClassOrNamespace::Namespace(_) => { + warn(&format!( + "the getter `{}` is not supported on `{}`. Enums only support static methods on them.", + name, + class + )); + return Ok(()); + } + }; + prefix += "get "; // For getters and setters, we generate a separate TypeScript definition. if export.generate_typescript { let location = FieldLocation { name: name.clone(), - is_static: receiver.is_static(), + is_static, }; let accessor = FieldAccessor { // This is only set to `None` when generating a constructor. @@ -2873,19 +3053,31 @@ __wbg_set_wasm(wasm);" is_optional: false, }; - exported.push_accessor_ts(location, accessor, false); + class.push_accessor_ts(location, accessor, false); } // Add the getter to the list of readable fields (used to generate `toJSON`) - exported.readable_properties.push(name.clone()); + class.readable_properties.push(name.clone()); // Ignore the raw signature. None } AuxExportedMethodKind::Setter => { + let class = match exported { + ClassOrNamespace::Class(ref mut class) => class, + ClassOrNamespace::Namespace(_) => { + warn(&format!( + "the setter `{}` is not supported on `{}`. Enums only support static methods on them.", + name, + class + )); + return Ok(()); + } + }; + prefix += "set "; if export.generate_typescript { let location = FieldLocation { name: name.clone(), - is_static: receiver.is_static(), + is_static, }; let accessor = FieldAccessor { ty: ts_arg_tys[0].clone(), @@ -2893,13 +3085,32 @@ __wbg_set_wasm(wasm);" is_optional: might_be_optional_field, }; - exported.push_accessor_ts(location, accessor, true); + class.push_accessor_ts(location, accessor, true); } None } }; - exported.push(name, &prefix, &js_docs, &code, &ts_docs, ts); + match exported { + ClassOrNamespace::Class(class) => { + class.push(name, &prefix, &js_docs, &code, &ts_docs, ts); + } + ClassOrNamespace::Namespace(ns) => { + if !is_static { + warn(&format!( + "The enum `{}` cannot support the instance method `{}`. \ + No binding will be generated for this method. \ + Consider moving the method in an `impl` block with the `#[wasm_bindgen]` attribute to avoid exporting it, \ + or making it a static method by replacing `self` with `value: Self`.", + ns.name, + name + )); + return Ok(()); + } else { + ns.push(name, &js_docs, &code, &ts_docs, ts); + } + } + } } } } @@ -3957,7 +4168,7 @@ __wbg_set_wasm(wasm);" self.export( &enum_.name, - &format!("Object.freeze({{\n{}}})", variants), + ExportJs::Expression(&format!("{{\n{}}}", variants)), Some(&docs), )?; @@ -4008,7 +4219,7 @@ __wbg_set_wasm(wasm);" } fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> { - let class = require_class(&mut self.exported_classes, &struct_.name); + let class = self.require_class(&struct_.name); class.comments = format_doc_comments(&struct_.comments, None); class.is_inspectable = struct_.is_inspectable; class.generate_typescript = struct_.generate_typescript; @@ -4464,17 +4675,6 @@ fn format_doc_comments(comments: &str, js_doc_comments: Option) -> Strin } } -fn require_class<'a>( - exported_classes: &'a mut Option>, - name: &str, -) -> &'a mut ExportedClass { - exported_classes - .as_mut() - .expect("classes already written") - .entry(name.to_string()) - .or_default() -} - /// Returns whether a character has the Unicode `ID_Start` properly. /// /// This is only ever-so-slightly different from `XID_Start` in a few edge @@ -4588,6 +4788,42 @@ impl ExportedClass { } } +impl ExportedNamespace { + fn push( + &mut self, + function_name: &str, + js_docs: &str, + js: &str, + ts_docs: &str, + ts: Option<&str>, + ) { + self.contents.push_str(js_docs); + self.contents.push_str(&self.name); + self.contents.push('.'); + self.contents.push_str(function_name); + self.contents.push_str(" = function "); + self.contents.push_str(function_name); + self.contents.push_str(js); + self.contents.push_str(";\n"); + + if let Some(ts) = ts { + if !ts_docs.is_empty() { + for line in ts_docs.lines() { + self.typescript.push_str(" "); + self.typescript.push_str(line); + self.typescript.push('\n'); + } + } + self.typescript.push_str(" export function "); + self.typescript.push_str(function_name); + self.typescript.push_str(ts); + self.typescript.push_str(";\n"); + } + } +} + +impl ClassOrNamespace<'_> {} + struct MemView { name: Cow<'static, str>, num: usize, diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js index 76feae96525..39b01f0df91 100644 --- a/crates/cli/tests/reference/enums.js +++ b/crates/cli/tests/reference/enums.js @@ -75,7 +75,7 @@ export function option_order(order) { * A color. * @enum {0 | 1 | 2} */ -export const Color = Object.freeze({ +export const Color = { /** * Green as a leaf. */ @@ -88,25 +88,25 @@ export const Color = Object.freeze({ * Red as a rose. */ Red: 2, "2": "Red", -}); +}; /** * @enum {0 | 1 | 42 | 43} */ -export const ImplicitDiscriminant = Object.freeze({ +export const ImplicitDiscriminant = { A: 0, "0": "A", B: 1, "1": "B", C: 42, "42": "C", D: 43, "43": "D", -}); +}; /** * A C-style enum with negative discriminants. * @enum {-1 | 0 | 1} */ -export const Ordering = Object.freeze({ +export const Ordering = { Less: -1, "-1": "Less", Equal: 0, "0": "Equal", Greater: 1, "1": "Greater", -}); +}; const __wbindgen_enum_ColorName = ["green", "yellow", "red"]; diff --git a/crates/cli/tests/reference/namespace-0.d.ts b/crates/cli/tests/reference/namespace-0.d.ts new file mode 100644 index 00000000000..05e0fb23e34 --- /dev/null +++ b/crates/cli/tests/reference/namespace-0.d.ts @@ -0,0 +1,23 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * C-style enum + */ +export enum ImageFormat { + PNG = 0, + JPEG = 1, + GIF = 2, +} +/** + * String enum + */ +type Status = "success" | "failure"; +export namespace ImageFormat { + export function from_str(s: string): ImageFormat; +} +export namespace Status { + /** + * I have documentation. + */ + export function from_bool(success: boolean): Status; +} diff --git a/crates/cli/tests/reference/namespace-0.js b/crates/cli/tests/reference/namespace-0.js new file mode 100644 index 00000000000..fac130709fb --- /dev/null +++ b/crates/cli/tests/reference/namespace-0.js @@ -0,0 +1,121 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder; + +let cachedTextEncoder = new lTextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} +/** + * C-style enum + * @enum {0 | 1 | 2} + */ +export const ImageFormat = { + PNG: 0, "0": "PNG", + JPEG: 1, "1": "JPEG", + GIF: 2, "2": "GIF", +}; + +const __wbindgen_enum_Status = ["success", "failure"]; + +/** + * @param {string} s + * @returns {ImageFormat} + */ +ImageFormat.from_str = function from_str(s) { + const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.imageformat_from_str(ptr0, len0); + return ret; +}; + +export const Status = {}; +/** + * I have documentation. + * @param {boolean} success + * @returns {Status} + */ +Status.from_bool = function from_bool(success) { + const ret = wasm.status_from_bool(success); + return __wbindgen_enum_Status[ret]; +}; + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/namespace-0.wat b/crates/cli/tests/reference/namespace-0.wat new file mode 100644 index 00000000000..e68b54b461e --- /dev/null +++ b/crates/cli/tests/reference/namespace-0.wat @@ -0,0 +1,21 @@ +(module $reference_test.wasm + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (func $__wbindgen_realloc (;0;) (type 2) (param i32 i32 i32 i32) (result i32)) + (func $__wbindgen_malloc (;1;) (type 1) (param i32 i32) (result i32)) + (func $imageformat_from_str (;2;) (type 1) (param i32 i32) (result i32)) + (func $status_from_bool (;3;) (type 0) (param i32) (result i32)) + (func $imageformat_is_lossless (;4;) (type 0) (param i32) (result i32)) + (func $status_to_bool (;5;) (type 0) (param i32) (result i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "imageformat_from_str" (func $imageformat_from_str)) + (export "imageformat_is_lossless" (func $imageformat_is_lossless)) + (export "status_from_bool" (func $status_from_bool)) + (export "status_to_bool" (func $status_to_bool)) + (export "__wbindgen_malloc" (func $__wbindgen_malloc)) + (export "__wbindgen_realloc" (func $__wbindgen_realloc)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/namespace-1.d.ts b/crates/cli/tests/reference/namespace-1.d.ts new file mode 100644 index 00000000000..05e0fb23e34 --- /dev/null +++ b/crates/cli/tests/reference/namespace-1.d.ts @@ -0,0 +1,23 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * C-style enum + */ +export enum ImageFormat { + PNG = 0, + JPEG = 1, + GIF = 2, +} +/** + * String enum + */ +type Status = "success" | "failure"; +export namespace ImageFormat { + export function from_str(s: string): ImageFormat; +} +export namespace Status { + /** + * I have documentation. + */ + export function from_bool(success: boolean): Status; +} diff --git a/crates/cli/tests/reference/namespace-1.js b/crates/cli/tests/reference/namespace-1.js new file mode 100644 index 00000000000..628c4180589 --- /dev/null +++ b/crates/cli/tests/reference/namespace-1.js @@ -0,0 +1,141 @@ + +let imports = {}; +imports['__wbindgen_placeholder__'] = module.exports; +let wasm; +const { TextDecoder, TextEncoder } = require(`util`); + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +let cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} +/** + * C-style enum + * @enum {0 | 1 | 2} + */ +module.exports.ImageFormat = { + PNG: 0, "0": "PNG", + JPEG: 1, "1": "JPEG", + GIF: 2, "2": "GIF", +}; + +const __wbindgen_enum_Status = ["success", "failure"]; + +(function(ImageFormat) { + /** + * @param {string} s + * @returns {ImageFormat} + */ + ImageFormat.from_str = function from_str(s) { + const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.imageformat_from_str(ptr0, len0); + return ret; + }; +})(module.exports.ImageFormat || (module.exports.ImageFormat = {})); + +(function(Status) { + /** + * I have documentation. + * @param {boolean} success + * @returns {Status} + */ + Status.from_bool = function from_bool(success) { + const ret = wasm.status_from_bool(success); + return __wbindgen_enum_Status[ret]; + }; +})(module.exports.Status || (module.exports.Status = {})); + +module.exports.__wbindgen_init_externref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +module.exports.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + +const path = require('path').join(__dirname, 'reference_test_bg.wasm'); +const bytes = require('fs').readFileSync(path); + +const wasmModule = new WebAssembly.Module(bytes); +const wasmInstance = new WebAssembly.Instance(wasmModule, imports); +wasm = wasmInstance.exports; +module.exports.__wasm = wasm; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/namespace-1.wat b/crates/cli/tests/reference/namespace-1.wat new file mode 100644 index 00000000000..edd83863750 --- /dev/null +++ b/crates/cli/tests/reference/namespace-1.wat @@ -0,0 +1,26 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func (param i32 i32 i32 i32) (result i32))) + (import "__wbindgen_placeholder__" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $__wbindgen_realloc (;1;) (type 3) (param i32 i32 i32 i32) (result i32)) + (func $__wbindgen_malloc (;2;) (type 2) (param i32 i32) (result i32)) + (func $imageformat_from_str (;3;) (type 2) (param i32 i32) (result i32)) + (func $status_from_bool (;4;) (type 1) (param i32) (result i32)) + (func $imageformat_is_lossless (;5;) (type 1) (param i32) (result i32)) + (func $status_to_bool (;6;) (type 1) (param i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "imageformat_from_str" (func $imageformat_from_str)) + (export "imageformat_is_lossless" (func $imageformat_is_lossless)) + (export "status_from_bool" (func $status_from_bool)) + (export "status_to_bool" (func $status_to_bool)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_malloc" (func $__wbindgen_malloc)) + (export "__wbindgen_realloc" (func $__wbindgen_realloc)) + (export "__wbindgen_start" (func 0)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/namespace-2.d.ts b/crates/cli/tests/reference/namespace-2.d.ts new file mode 100644 index 00000000000..7cf2dd4c810 --- /dev/null +++ b/crates/cli/tests/reference/namespace-2.d.ts @@ -0,0 +1,50 @@ +declare namespace wasm_bindgen { + /* tslint:disable */ + /* eslint-disable */ + /** + * C-style enum + */ + export enum ImageFormat { + PNG = 0, + JPEG = 1, + GIF = 2, + } + /** + * String enum + */ + type Status = "success" | "failure"; + export namespace ImageFormat { + export function from_str(s: string): ImageFormat; + } + export namespace Status { + /** + * I have documentation. + */ + export function from_bool(success: boolean): Status; + } + +} + +declare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +declare interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly imageformat_from_str: (a: number, b: number) => number; + readonly imageformat_is_lossless: (a: number) => number; + readonly status_from_bool: (a: number) => number; + readonly status_to_bool: (a: number) => number; + readonly __wbindgen_export_0: WebAssembly.Table; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_start: () => void; +} + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +declare function wasm_bindgen (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/crates/cli/tests/reference/namespace-2.js b/crates/cli/tests/reference/namespace-2.js new file mode 100644 index 00000000000..b1ff3b25611 --- /dev/null +++ b/crates/cli/tests/reference/namespace-2.js @@ -0,0 +1,240 @@ +let wasm_bindgen; +(function() { + const __exports = {}; + let script_src; + if (typeof document !== 'undefined' && document.currentScript !== null) { + script_src = new URL(document.currentScript.src, location.href).toString(); + } + let wasm = undefined; + + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + + if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + + let cachedUint8ArrayMemory0 = null; + + function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; + } + + function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); + } + + let WASM_VECTOR_LEN = 0; + + const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + + const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }); + + function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; + } + /** + * C-style enum + * @enum {0 | 1 | 2} + */ + __exports.ImageFormat = { + PNG: 0, "0": "PNG", + JPEG: 1, "1": "JPEG", + GIF: 2, "2": "GIF", + }; + + const __wbindgen_enum_Status = ["success", "failure"]; + + (function(ImageFormat) { + /** + * @param {string} s + * @returns {ImageFormat} + */ + ImageFormat.from_str = function from_str(s) { + const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.imageformat_from_str(ptr0, len0); + return ret; + }; + })(__exports.ImageFormat || (__exports.ImageFormat = {})); + + (function(Status) { + /** + * I have documentation. + * @param {boolean} success + * @returns {Status} + */ + Status.from_bool = function from_bool(success) { + const ret = wasm.status_from_bool(success); + return __wbindgen_enum_Status[ret]; + }; + })(__exports.Status || (__exports.Status = {})); + + async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.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); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } + } + + function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_init_externref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; + } + + function __wbg_init_memory(imports, memory) { + + } + + function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedUint8ArrayMemory0 = null; + + + wasm.__wbindgen_start(); + return wasm; + } + + function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); + } + + async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined' && typeof script_src !== 'undefined') { + module_or_path = script_src.replace(/\.js$/, '_bg.wasm'); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); + } + + wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports); + +})(); diff --git a/crates/cli/tests/reference/namespace-2.wat b/crates/cli/tests/reference/namespace-2.wat new file mode 100644 index 00000000000..9b1e880fdbf --- /dev/null +++ b/crates/cli/tests/reference/namespace-2.wat @@ -0,0 +1,26 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func (param i32 i32 i32 i32) (result i32))) + (import "wbg" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $__wbindgen_realloc (;1;) (type 3) (param i32 i32 i32 i32) (result i32)) + (func $__wbindgen_malloc (;2;) (type 2) (param i32 i32) (result i32)) + (func $imageformat_from_str (;3;) (type 2) (param i32 i32) (result i32)) + (func $status_from_bool (;4;) (type 1) (param i32) (result i32)) + (func $imageformat_is_lossless (;5;) (type 1) (param i32) (result i32)) + (func $status_to_bool (;6;) (type 1) (param i32) (result i32)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "imageformat_from_str" (func $imageformat_from_str)) + (export "imageformat_is_lossless" (func $imageformat_is_lossless)) + (export "status_from_bool" (func $status_from_bool)) + (export "status_to_bool" (func $status_to_bool)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_malloc" (func $__wbindgen_malloc)) + (export "__wbindgen_realloc" (func $__wbindgen_realloc)) + (export "__wbindgen_start" (func 0)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/cli/tests/reference/namespace.rs b/crates/cli/tests/reference/namespace.rs new file mode 100644 index 00000000000..06682dd172d --- /dev/null +++ b/crates/cli/tests/reference/namespace.rs @@ -0,0 +1,57 @@ +// test the next export code +// FLAGS: --target=bundler +// FLAGS: --target=nodejs +// FLAGS: --target=no-modules + +use wasm_bindgen::prelude::*; + +/// C-style enum +#[wasm_bindgen] +#[derive(Copy, Clone)] +pub enum ImageFormat { + PNG, + JPEG, + GIF, +} + +#[wasm_bindgen] +impl ImageFormat { + pub fn from_str(s: &str) -> ImageFormat { + match s { + "PNG" => ImageFormat::PNG, + "JPEG" => ImageFormat::JPEG, + "GIF" => ImageFormat::GIF, + _ => panic!("unknown image format: {}", s), + } + } + + /// I will be ignored by the generated JS bindings. + pub fn is_lossless(self) -> bool { + matches!(self, ImageFormat::PNG | ImageFormat::GIF) + } +} + +/// String enum +#[wasm_bindgen] +#[derive(Copy, Clone)] +pub enum Status { + Success = "success", + Failure = "failure", +} + +#[wasm_bindgen] +impl Status { + /// I have documentation. + pub fn from_bool(success: bool) -> Status { + if success { + Status::Success + } else { + Status::Failure + } + } + + /// I will be ignored by the generated JS bindings. + pub fn to_bool(self) -> bool { + matches!(self, Status::Success) + } +}