From 631c5a70d6715043b2f7e9fbf2d4008a1237ba8a Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 9 Nov 2024 18:42:43 +0100 Subject: [PATCH 1/7] Add support for static enum methods via TS namespaces --- crates/cli-support/src/js/mod.rs | 309 ++++++++++++++++++---- crates/cli/tests/reference/enums.js | 8 +- crates/cli/tests/reference/namespace.d.ts | 23 ++ crates/cli/tests/reference/namespace.js | 127 +++++++++ crates/cli/tests/reference/namespace.rs | 42 +++ crates/cli/tests/reference/namespace.wat | 17 ++ 6 files changed, 475 insertions(+), 51 deletions(-) create mode 100644 crates/cli/tests/reference/namespace.d.ts create mode 100644 crates/cli/tests/reference/namespace.js create mode 100644 crates/cli/tests/reference/namespace.rs create mode 100644 crates/cli/tests/reference/namespace.wat diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index aff91c6cfcc..3e160e0a9df 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 function expression for initiating the namespace. The + /// function expression is of the form `(function(Name) {...})`. + 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,72 @@ 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!( + "{}({});\n", + 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!( + "{}({});\n", + 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 +269,25 @@ 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. + let mut definition = String::new(); + if !self.defined_identifiers.contains_key(export_name) { + definition = format!("export const {} = {{}};\n", export_name) + } + format!("{}{}({});\n", definition, namespace, export_name) } - } + }, }; self.global(&global); Ok(()) @@ -225,6 +302,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 +1085,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 +1248,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 +1379,57 @@ __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(()); + } + + let dst = format!( + "(function({name}) {{\n{contents}}})", + contents = namespace.contents + ); + self.export(name, ExportJs::Namespace(&dst), 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 +2608,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) { @@ -2831,11 +2978,15 @@ __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(class); if exported.has_constructor { bail!("found duplicate constructor for class `{}`", class); @@ -2850,7 +3001,7 @@ __wbg_set_wasm(wasm);" receiver, kind, } => { - let exported = require_class(&mut self.exported_classes, class); + let mut exported = self.require_class_or_namespace(class); let mut prefix = String::new(); if receiver.is_static() { @@ -2859,6 +3010,17 @@ __wbg_set_wasm(wasm);" let ts = match kind { AuxExportedMethodKind::Method => ts_sig, AuxExportedMethodKind::Getter => { + let class = match exported { + ClassOrNamespace::Class(ref mut class) => class, + ClassOrNamespace::Namespace(_) => { + bail!( + "the getter `{}` is not supported on `{}`. Enums only support static methods on them.", + name, + class + ); + } + }; + prefix += "get "; // For getters and setters, we generate a separate TypeScript definition. if export.generate_typescript { @@ -2873,14 +3035,25 @@ __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(_) => { + bail!( + "the setter `{}` is not supported on `{}`. Enums only support static methods on them.", + name, + class + ); + } + }; + prefix += "set "; if export.generate_typescript { let location = FieldLocation { @@ -2893,13 +3066,27 @@ __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 !receiver.is_static() { + bail!( + "non-static method `{}` on namespace `{}`", + name, + ns.name + ); + } + ns.push(name, &js_docs, &code, &ts_docs, ts); + } + } } } } @@ -3957,7 +4144,7 @@ __wbg_set_wasm(wasm);" self.export( &enum_.name, - &format!("Object.freeze({{\n{}}})", variants), + ExportJs::Expression(&format!("{{\n{}}}", variants)), Some(&docs), )?; @@ -4008,7 +4195,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 +4651,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 +4764,45 @@ 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("function "); + self.contents.push_str(function_name); + self.contents.push_str(js); + self.contents.push('\n'); + self.contents.push_str(&self.name); + self.contents.push('.'); + self.contents.push_str(function_name); + self.contents.push_str(" = "); + self.contents.push_str(function_name); + 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 a425ef2874f..216796dc4be 100644 --- a/crates/cli/tests/reference/enums.js +++ b/crates/cli/tests/reference/enums.js @@ -66,7 +66,7 @@ export function option_string_enum_echo(color) { * A color. * @enum {0 | 1 | 2} */ -export const Color = Object.freeze({ +export const Color = { /** * Green as a leaf. */ @@ -79,16 +79,16 @@ 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", -}); +}; const __wbindgen_enum_ColorName = ["green", "yellow", "red"]; diff --git a/crates/cli/tests/reference/namespace.d.ts b/crates/cli/tests/reference/namespace.d.ts new file mode 100644 index 00000000000..05e0fb23e34 --- /dev/null +++ b/crates/cli/tests/reference/namespace.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.js b/crates/cli/tests/reference/namespace.js new file mode 100644 index 00000000000..04a63628d18 --- /dev/null +++ b/crates/cli/tests/reference/namespace.js @@ -0,0 +1,127 @@ +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"]; + +(function(ImageFormat) { + /** + * @param {string} s + * @returns {ImageFormat} + */ + 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; + } + ImageFormat.from_str = from_str; +})(ImageFormat); + +export const Status = {}; +(function(Status) { + /** + * I have documentation. + * @param {boolean} success + * @returns {Status} + */ + function from_bool(success) { + const ret = wasm.status_from_bool(success); + return __wbindgen_enum_Status[ret]; + } + Status.from_bool = from_bool; +})(Status); + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/namespace.rs b/crates/cli/tests/reference/namespace.rs new file mode 100644 index 00000000000..6970afdb1dd --- /dev/null +++ b/crates/cli/tests/reference/namespace.rs @@ -0,0 +1,42 @@ +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), + } + } +} + +/// 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 + } + } +} diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace.wat new file mode 100644 index 00000000000..3e5ad71a48c --- /dev/null +++ b/crates/cli/tests/reference/namespace.wat @@ -0,0 +1,17 @@ +(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)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "imageformat_from_str" (func $imageformat_from_str)) + (export "status_from_bool" (func $status_from_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") +) + From afd00bbd982204a34bc52499c79278f91e03331f Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sat, 9 Nov 2024 18:57:25 +0100 Subject: [PATCH 2/7] Terser code gen --- crates/cli-support/src/js/mod.rs | 10 +++++----- crates/cli/tests/reference/namespace.js | 10 ++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 3e160e0a9df..a71cc9cb1a4 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -281,6 +281,9 @@ impl<'a> Context<'a> { // 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) @@ -4774,15 +4777,12 @@ impl ExportedNamespace { ts: Option<&str>, ) { self.contents.push_str(js_docs); - self.contents.push_str("function "); - self.contents.push_str(function_name); - self.contents.push_str(js); - self.contents.push('\n'); self.contents.push_str(&self.name); self.contents.push('.'); self.contents.push_str(function_name); - self.contents.push_str(" = "); + 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 { diff --git a/crates/cli/tests/reference/namespace.js b/crates/cli/tests/reference/namespace.js index 04a63628d18..9ee8cb20f29 100644 --- a/crates/cli/tests/reference/namespace.js +++ b/crates/cli/tests/reference/namespace.js @@ -98,13 +98,12 @@ const __wbindgen_enum_Status = ["success", "failure"]; * @param {string} s * @returns {ImageFormat} */ - function from_str(s) { + 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; - } - ImageFormat.from_str = from_str; + }; })(ImageFormat); export const Status = {}; @@ -114,11 +113,10 @@ export const Status = {}; * @param {boolean} success * @returns {Status} */ - function from_bool(success) { + Status.from_bool = function from_bool(success) { const ret = wasm.status_from_bool(success); return __wbindgen_enum_Status[ret]; - } - Status.from_bool = from_bool; + }; })(Status); export function __wbindgen_throw(arg0, arg1) { From 04cef11b70fada9e727a0e322cf477e58324c1d5 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Wed, 13 Nov 2024 17:13:50 +0100 Subject: [PATCH 3/7] Updated enum.js --- crates/cli/tests/reference/enums.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js index 713fa4f7f9d..39b01f0df91 100644 --- a/crates/cli/tests/reference/enums.js +++ b/crates/cli/tests/reference/enums.js @@ -102,11 +102,11 @@ export const ImplicitDiscriminant = { * 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"]; From b6ff56fbf0f883ff284a2242dfc1338863cfd68a Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Wed, 13 Nov 2024 17:35:33 +0100 Subject: [PATCH 4/7] Disallow enum constructors and ignore enum methods --- crates/cli-support/src/js/mod.rs | 35 +++++++++++++++++------- crates/cli/tests/reference/namespace.rs | 10 +++++++ crates/cli/tests/reference/namespace.wat | 4 +++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index a71cc9cb1a4..5077ac13ec0 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2989,7 +2989,16 @@ __wbg_set_wasm(wasm);" self.globals.push('\n'); } AuxExportKind::Constructor(class) => { - let exported = self.require_class(class); + let exported = self.require_class_or_namespace(class); + let exported = match exported { + ClassOrNamespace::Class(class) => class, + ClassOrNamespace::Namespace(_) => { + bail!( + "constructor is not supported on `{}`. Enums do not support exported constructors.", + class + ); + } + }; if exported.has_constructor { bail!("found duplicate constructor for class `{}`", class); @@ -3004,10 +3013,11 @@ __wbg_set_wasm(wasm);" receiver, kind, } => { + 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 { @@ -3029,7 +3039,7 @@ __wbg_set_wasm(wasm);" 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. @@ -3061,7 +3071,7 @@ __wbg_set_wasm(wasm);" 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(), @@ -3080,14 +3090,19 @@ __wbg_set_wasm(wasm);" class.push(name, &prefix, &js_docs, &code, &ts_docs, ts); } ClassOrNamespace::Namespace(ns) => { - if !receiver.is_static() { - bail!( - "non-static method `{}` on namespace `{}`", - name, - ns.name + if !is_static { + let msg = 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 ); + println!("WARNING: {}", msg); + } else { + ns.push(name, &js_docs, &code, &ts_docs, ts); } - ns.push(name, &js_docs, &code, &ts_docs, ts); } } } diff --git a/crates/cli/tests/reference/namespace.rs b/crates/cli/tests/reference/namespace.rs index 6970afdb1dd..a8ff9efd1ce 100644 --- a/crates/cli/tests/reference/namespace.rs +++ b/crates/cli/tests/reference/namespace.rs @@ -19,6 +19,11 @@ impl ImageFormat { _ => 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 @@ -39,4 +44,9 @@ impl Status { Status::Failure } } + + /// I will be ignored by the generated JS bindings. + pub fn to_bool(self) -> bool { + matches!(self, Status::Success) + } } diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace.wat index 3e5ad71a48c..e68b54b461e 100644 --- a/crates/cli/tests/reference/namespace.wat +++ b/crates/cli/tests/reference/namespace.wat @@ -6,10 +6,14 @@ (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") From 6b4a16fc86f145fd7e49c5a1e11c5b14dab7a52a Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Thu, 14 Nov 2024 16:06:11 +0100 Subject: [PATCH 5/7] Test all export modes --- .../{namespace.d.ts => namespace-0.d.ts} | 0 .../{namespace.js => namespace-0.js} | 0 .../{namespace.wat => namespace-0.wat} | 0 crates/cli/tests/reference/namespace-1.d.ts | 23 ++ crates/cli/tests/reference/namespace-1.js | 141 ++++++++++ crates/cli/tests/reference/namespace-1.wat | 26 ++ crates/cli/tests/reference/namespace-2.d.ts | 50 ++++ crates/cli/tests/reference/namespace-2.js | 240 ++++++++++++++++++ crates/cli/tests/reference/namespace-2.wat | 26 ++ crates/cli/tests/reference/namespace.rs | 5 + 10 files changed, 511 insertions(+) rename crates/cli/tests/reference/{namespace.d.ts => namespace-0.d.ts} (100%) rename crates/cli/tests/reference/{namespace.js => namespace-0.js} (100%) rename crates/cli/tests/reference/{namespace.wat => namespace-0.wat} (100%) create mode 100644 crates/cli/tests/reference/namespace-1.d.ts create mode 100644 crates/cli/tests/reference/namespace-1.js create mode 100644 crates/cli/tests/reference/namespace-1.wat create mode 100644 crates/cli/tests/reference/namespace-2.d.ts create mode 100644 crates/cli/tests/reference/namespace-2.js create mode 100644 crates/cli/tests/reference/namespace-2.wat diff --git a/crates/cli/tests/reference/namespace.d.ts b/crates/cli/tests/reference/namespace-0.d.ts similarity index 100% rename from crates/cli/tests/reference/namespace.d.ts rename to crates/cli/tests/reference/namespace-0.d.ts diff --git a/crates/cli/tests/reference/namespace.js b/crates/cli/tests/reference/namespace-0.js similarity index 100% rename from crates/cli/tests/reference/namespace.js rename to crates/cli/tests/reference/namespace-0.js diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace-0.wat similarity index 100% rename from crates/cli/tests/reference/namespace.wat rename to crates/cli/tests/reference/namespace-0.wat 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 index a8ff9efd1ce..06682dd172d 100644 --- a/crates/cli/tests/reference/namespace.rs +++ b/crates/cli/tests/reference/namespace.rs @@ -1,3 +1,8 @@ +// test the next export code +// FLAGS: --target=bundler +// FLAGS: --target=nodejs +// FLAGS: --target=no-modules + use wasm_bindgen::prelude::*; /// C-style enum From b0c06a58e4afd5d95ead2ff5e2365323b07c0bf0 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Thu, 14 Nov 2024 16:14:22 +0100 Subject: [PATCH 6/7] Remove IIFE for ESM output --- crates/cli-support/src/js/mod.rs | 19 +++++----- crates/cli/tests/reference/namespace-0.js | 42 ++++++++++------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 5077ac13ec0..1ae5c420027 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -146,8 +146,8 @@ enum ExportJs<'a> { Function(&'a str), /// An arbitrary JS expression. Expression(&'a str), - /// A namespace as a function expression for initiating the namespace. The - /// function expression is of the form `(function(Name) {...})`. + /// A namespace as a statement with multiple assignments of the form + /// `.prop = value;` Namespace(&'a str), } @@ -230,7 +230,8 @@ impl<'a> Context<'a> { } ExportJs::Namespace(namespace) => { format!( - "{}({});\n", + "(function({}) {{\n{}}})({});\n", + export_name, namespace, namespace_init_arg(&format!("module.exports.{}", export_name)) ) @@ -245,7 +246,8 @@ impl<'a> Context<'a> { } ExportJs::Namespace(namespace) => { format!( - "{}({});\n", + "(function({}) {{\n{}}})({});\n", + export_name, namespace, namespace_init_arg(&format!("__exports.{}", export_name)) ) @@ -288,7 +290,8 @@ impl<'a> Context<'a> { if !self.defined_identifiers.contains_key(export_name) { definition = format!("export const {} = {{}};\n", export_name) } - format!("{}{}({});\n", definition, namespace, export_name) + definition.push_str(namespace); + definition } }, }; @@ -1416,11 +1419,7 @@ __wbg_set_wasm(wasm);" return Ok(()); } - let dst = format!( - "(function({name}) {{\n{contents}}})", - contents = namespace.contents - ); - self.export(name, ExportJs::Namespace(&dst), None)?; + self.export(name, ExportJs::Namespace(&namespace.contents), None)?; if namespace.generate_typescript { let ts_dst = format!( diff --git a/crates/cli/tests/reference/namespace-0.js b/crates/cli/tests/reference/namespace-0.js index 9ee8cb20f29..fac130709fb 100644 --- a/crates/cli/tests/reference/namespace-0.js +++ b/crates/cli/tests/reference/namespace-0.js @@ -93,31 +93,27 @@ export const ImageFormat = { 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; - }; -})(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; +}; export const Status = {}; -(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]; - }; -})(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)); From 7771e6475168f018681adb12a0327b28da65f955 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Thu, 14 Nov 2024 19:54:18 +0100 Subject: [PATCH 7/7] Replace errors with warnings --- crates/cli-support/src/js/mod.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 1ae5c420027..18ba1208606 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2958,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 { @@ -2992,10 +2996,11 @@ __wbg_set_wasm(wasm);" let exported = match exported { ClassOrNamespace::Class(class) => class, ClassOrNamespace::Namespace(_) => { - bail!( + warn(&format!( "constructor is not supported on `{}`. Enums do not support exported constructors.", class - ); + )); + return Ok(()); } }; @@ -3025,11 +3030,12 @@ __wbg_set_wasm(wasm);" let class = match exported { ClassOrNamespace::Class(ref mut class) => class, ClassOrNamespace::Namespace(_) => { - bail!( + warn(&format!( "the getter `{}` is not supported on `{}`. Enums only support static methods on them.", name, class - ); + )); + return Ok(()); } }; @@ -3058,11 +3064,12 @@ __wbg_set_wasm(wasm);" let class = match exported { ClassOrNamespace::Class(ref mut class) => class, ClassOrNamespace::Namespace(_) => { - bail!( - "the setter `{}` is not supported on `{}`. Enums only support static methods on them.", + warn(&format!( + "the setter `{}` is not supported on `{}`. Enums only support static methods on them.", name, class - ); + )); + return Ok(()); } }; @@ -3090,15 +3097,15 @@ __wbg_set_wasm(wasm);" } ClassOrNamespace::Namespace(ns) => { if !is_static { - let msg = format!( + 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 - ); - println!("WARNING: {}", msg); + )); + return Ok(()); } else { ns.push(name, &js_docs, &code, &ts_docs, ts); }