Repository: rustwasm/wasm-bindgen
Pull request #4332 (2024-12-07 11:23:50)

Added missing type annotation in docs
Annotation was missing. This PR fixes that.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32405a47..3402f7ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
 # `wasm-bindgen` Change Log
 --------------------------------------------------------------------------------
 
+## Unreleased
+
+### Changed
+
+* Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.
+  [#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188)
+
+--------------------------------------------------------------------------------
+
 ## [0.2.99](https://github.com/rustwasm/wasm-bindgen/compare/0.2.98...0.2.99)
 
 Released 2024-12-07
diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs
index 54be1481..637c3a5e 100644
--- a/crates/cli-support/src/js/binding.rs
+++ b/crates/cli-support/src/js/binding.rs
@@ -321,13 +321,15 @@ impl<'a, 'b> Builder<'a, 'b> {
             let mut ts = String::new();
             match ty {
                 AdapterType::Option(ty) if omittable => {
+                    // e.g. `foo?: string | null`
                     arg.push_str("?: ");
-                    adapter2ts(ty, &mut ts, Some(&mut ts_refs));
+                    adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
+                    ts.push_str(" | null");
                 }
                 ty => {
                     omittable = false;
                     arg.push_str(": ");
-                    adapter2ts(ty, &mut ts, Some(&mut ts_refs));
+                    adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
                 }
             }
             arg.push_str(&ts);
@@ -363,7 +365,12 @@ impl<'a, 'b> Builder<'a, 'b> {
             let mut ret = String::new();
             match result_tys.len() {
                 0 => ret.push_str("void"),
-                1 => adapter2ts(&result_tys[0], &mut ret, Some(&mut ts_refs)),
+                1 => adapter2ts(
+                    &result_tys[0],
+                    TypePosition::Return,
+                    &mut ret,
+                    Some(&mut ts_refs),
+                ),
                 _ => ret.push_str("[any]"),
             }
             if asyncness {
@@ -395,16 +402,18 @@ impl<'a, 'b> Builder<'a, 'b> {
         for (name, ty) in fn_arg_names.iter().zip(arg_tys).rev() {
             let mut arg = "@param {".to_string();
 
-            adapter2ts(ty, &mut arg, None);
-            arg.push_str("} ");
             match ty {
-                AdapterType::Option(..) if omittable => {
+                AdapterType::Option(ty) if omittable => {
+                    adapter2ts(ty, TypePosition::Argument, &mut arg, None);
+                    arg.push_str(" | null} ");
                     arg.push('[');
                     arg.push_str(name);
                     arg.push(']');
                 }
                 _ => {
                     omittable = false;
+                    adapter2ts(ty, TypePosition::Argument, &mut arg, None);
+                    arg.push_str("} ");
                     arg.push_str(name);
                 }
             }
@@ -416,7 +425,7 @@ impl<'a, 'b> Builder<'a, 'b> {
 
         if let (Some(name), Some(ty)) = (variadic_arg, arg_tys.last()) {
             ret.push_str("@param {...");
-            adapter2ts(ty, &mut ret, None);
+            adapter2ts(ty, TypePosition::Argument, &mut ret, None);
             ret.push_str("} ");
             ret.push_str(name);
             ret.push('\n');
@@ -1542,7 +1551,18 @@ impl Invocation {
     }
 }
 
-fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsReference>>) {
+#[derive(Debug, Clone, Copy)]
+enum TypePosition {
+    Argument,
+    Return,
+}
+
+fn adapter2ts(
+    ty: &AdapterType,
+    position: TypePosition,
+    dst: &mut String,
+    refs: Option<&mut HashSet<TsReference>>,
+) {
     match ty {
         AdapterType::I32
         | AdapterType::S8
@@ -1564,8 +1584,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsRe
         AdapterType::Bool => dst.push_str("boolean"),
         AdapterType::Vector(kind) => dst.push_str(&kind.js_ty()),
         AdapterType::Option(ty) => {
-            adapter2ts(ty, dst, refs);
-            dst.push_str(" | undefined");
+            adapter2ts(ty, position, dst, refs);
+            dst.push_str(match position {
+                TypePosition::Argument => " | null | undefined",
+                TypePosition::Return => " | undefined",
+            });
         }
         AdapterType::NamedExternref(name) => dst.push_str(name),
         AdapterType::Struct(name) => dst.push_str(name),
diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index e26f7a6d..9cdd2d7d 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -120,6 +120,18 @@ struct FieldAccessor {
     is_optional: bool,
 }
 
+/// 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),
+}
+
 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;
@@ -163,38 +175,46 @@ 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 {
+        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);
         }
+
         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)
                 }
-            }
+            },
+            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)
+                }
+            },
             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 +223,12 @@ impl<'a> Context<'a> {
                             definition_name, body, definition_name, export_name,
                         )
                     }
-                } else if contents.starts_with("class") {
-                    assert_eq!(export_name, definition_name);
-                    format!("export {}\n", contents)
-                } else {
+                }
+                ExportJs::Expression(expr) => {
                     assert_eq!(export_name, definition_name);
-                    format!("export const {} = {};\n", export_name, contents)
+                    format!("export const {} = {};\n", export_name, expr)
                 }
-            }
+            },
         };
         self.global(&global);
         Ok(())
@@ -1169,10 +1187,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);
@@ -2872,7 +2890,11 @@ __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) => {
@@ -3995,7 +4017,7 @@ __wbg_set_wasm(wasm);"
 
         self.export(
             &enum_.name,
-            &format!("Object.freeze({{\n{}}})", variants),
+            ExportJs::Expression(&format!("Object.freeze({{\n{}}})", variants)),
             Some(&docs),
         )?;
 
diff --git a/crates/web-sys/README.md b/crates/web-sys/README.md
index 7693680c..53753673 100644
--- a/crates/web-sys/README.md
+++ b/crates/web-sys/README.md
@@ -32,9 +32,6 @@ If you don't see a particular web API in `web-sys`, here is how to add it.
 2. Annotate the functions that can throw with `[Throws]`
 3. `cd crates/web-sys`
 4. Run `cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml`
-
-   If formatting fails, you can run `cargo fmt` in the `crates/web-sys` directory. On Windows, you might also want to run `cargo fmt -- --config newline_style=Unix` depending on your git configuration.
-
 5. Run `git add .` to add all the generated files into git.
 6. Add an entry in CHANGELOG.md like the following
 
diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs
index a6caa7d5..778b1f40 100644
--- a/crates/webidl/src/lib.rs
+++ b/crates/webidl/src/lib.rs
@@ -997,14 +997,21 @@ pub fn generate(from: &Path, to: &Path, options: Options) -> Result<String> {
 
     fn rustfmt(paths: impl IntoIterator<Item = PathBuf>) -> Result<()> {
         // run rustfmt on the generated file - really handy for debugging
-        let result = Command::new("rustfmt")
-            .arg("--edition")
-            .arg("2021")
-            .args(paths)
-            .status()
-            .context("rustfmt failed")?;
-
-        assert!(result.success(), "rustfmt failed");
+
+        // On Windows, the command line length is limited to 32k characters, so
+        // we need to split the command into multiple invocations. I've
+        // arbitrarily chosen to format 400 files at a time, because it works.
+        let paths: Vec<_> = paths.into_iter().collect();
+        for chunk in paths.chunks(400) {
+            let result = Command::new("rustfmt")
+                .arg("--edition")
+                .arg("2021")
+                .args(chunk)
+                .status()
+                .context("rustfmt failed")?;
+
+            assert!(result.success(), "rustfmt failed");
+        }
 
         Ok(())
     }
diff --git a/guide/src/reference/static-js-objects.md b/guide/src/reference/static-js-objects.md
index 92b699d5..c78dc3c2 100644
--- a/guide/src/reference/static-js-objects.md
+++ b/guide/src/reference/static-js-objects.md
@@ -26,7 +26,7 @@ let COLORS = {
 #[wasm_bindgen]
 extern "C" {
     #[wasm_bindgen(thread_local_v2)]
-    static COLORS;
+    static COLORS: JsValue;
 }
 
 fn get_colors() -> JsValue {


Test patch:
diff --git a/crates/cli/tests/reference/echo.d.ts b/crates/cli/tests/reference/echo.d.ts
index c318f636..61fd4a79 100644
--- a/crates/cli/tests/reference/echo.d.ts
+++ b/crates/cli/tests/reference/echo.d.ts
@@ -36,42 +36,42 @@ export function echo_vec_uninit_i64(a: BigInt64Array): BigInt64Array;
 export function echo_vec_string(a: (string)[]): (string)[];
 export function echo_struct(a: Foo): Foo;
 export function echo_vec_struct(a: (Foo)[]): (Foo)[];
-export function echo_option_u8(a?: number): number | undefined;
-export function echo_option_i8(a?: number): number | undefined;
-export function echo_option_u16(a?: number): number | undefined;
-export function echo_option_i16(a?: number): number | undefined;
-export function echo_option_u32(a?: number): number | undefined;
-export function echo_option_i32(a?: number): number | undefined;
-export function echo_option_u64(a?: bigint): bigint | undefined;
-export function echo_option_i64(a?: bigint): bigint | undefined;
-export function echo_option_u128(a?: bigint): bigint | undefined;
-export function echo_option_i128(a?: bigint): bigint | undefined;
-export function echo_option_usize(a?: number): number | undefined;
-export function echo_option_isize(a?: number): number | undefined;
-export function echo_option_f32(a?: number): number | undefined;
-export function echo_option_f64(a?: number): number | undefined;
-export function echo_option_bool(a?: boolean): boolean | undefined;
-export function echo_option_char(a?: string): string | undefined;
-export function echo_option_string(a?: string): string | undefined;
-export function echo_option_vec_u8(a?: Uint8Array): Uint8Array | undefined;
-export function echo_option_vec_i8(a?: Int8Array): Int8Array | undefined;
-export function echo_option_vec_u16(a?: Uint16Array): Uint16Array | undefined;
-export function echo_option_vec_i16(a?: Int16Array): Int16Array | undefined;
-export function echo_option_vec_u32(a?: Uint32Array): Uint32Array | undefined;
-export function echo_option_vec_i32(a?: Int32Array): Int32Array | undefined;
-export function echo_option_vec_u64(a?: BigUint64Array): BigUint64Array | undefined;
-export function echo_option_vec_i64(a?: BigInt64Array): BigInt64Array | undefined;
-export function echo_option_vec_uninit_u8(a?: Uint8Array): Uint8Array | undefined;
-export function echo_option_vec_uninit_i8(a?: Int8Array): Int8Array | undefined;
-export function echo_option_vec_uninit_u16(a?: Uint16Array): Uint16Array | undefined;
-export function echo_option_vec_uninit_i16(a?: Int16Array): Int16Array | undefined;
-export function echo_option_vec_uninit_u32(a?: Uint32Array): Uint32Array | undefined;
-export function echo_option_vec_uninit_i32(a?: Int32Array): Int32Array | undefined;
-export function echo_option_vec_uninit_u64(a?: BigUint64Array): BigUint64Array | undefined;
-export function echo_option_vec_uninit_i64(a?: BigInt64Array): BigInt64Array | undefined;
-export function echo_option_vec_string(a?: (string)[]): (string)[] | undefined;
-export function echo_option_struct(a?: Foo): Foo | undefined;
-export function echo_option_vec_struct(a?: (Foo)[]): (Foo)[] | undefined;
+export function echo_option_u8(a?: number | null): number | undefined;
+export function echo_option_i8(a?: number | null): number | undefined;
+export function echo_option_u16(a?: number | null): number | undefined;
+export function echo_option_i16(a?: number | null): number | undefined;
+export function echo_option_u32(a?: number | null): number | undefined;
+export function echo_option_i32(a?: number | null): number | undefined;
+export function echo_option_u64(a?: bigint | null): bigint | undefined;
+export function echo_option_i64(a?: bigint | null): bigint | undefined;
+export function echo_option_u128(a?: bigint | null): bigint | undefined;
+export function echo_option_i128(a?: bigint | null): bigint | undefined;
+export function echo_option_usize(a?: number | null): number | undefined;
+export function echo_option_isize(a?: number | null): number | undefined;
+export function echo_option_f32(a?: number | null): number | undefined;
+export function echo_option_f64(a?: number | null): number | undefined;
+export function echo_option_bool(a?: boolean | null): boolean | undefined;
+export function echo_option_char(a?: string | null): string | undefined;
+export function echo_option_string(a?: string | null): string | undefined;
+export function echo_option_vec_u8(a?: Uint8Array | null): Uint8Array | undefined;
+export function echo_option_vec_i8(a?: Int8Array | null): Int8Array | undefined;
+export function echo_option_vec_u16(a?: Uint16Array | null): Uint16Array | undefined;
+export function echo_option_vec_i16(a?: Int16Array | null): Int16Array | undefined;
+export function echo_option_vec_u32(a?: Uint32Array | null): Uint32Array | undefined;
+export function echo_option_vec_i32(a?: Int32Array | null): Int32Array | undefined;
+export function echo_option_vec_u64(a?: BigUint64Array | null): BigUint64Array | undefined;
+export function echo_option_vec_i64(a?: BigInt64Array | null): BigInt64Array | undefined;
+export function echo_option_vec_uninit_u8(a?: Uint8Array | null): Uint8Array | undefined;
+export function echo_option_vec_uninit_i8(a?: Int8Array | null): Int8Array | undefined;
+export function echo_option_vec_uninit_u16(a?: Uint16Array | null): Uint16Array | undefined;
+export function echo_option_vec_uninit_i16(a?: Int16Array | null): Int16Array | undefined;
+export function echo_option_vec_uninit_u32(a?: Uint32Array | null): Uint32Array | undefined;
+export function echo_option_vec_uninit_i32(a?: Int32Array | null): Int32Array | undefined;
+export function echo_option_vec_uninit_u64(a?: BigUint64Array | null): BigUint64Array | undefined;
+export function echo_option_vec_uninit_i64(a?: BigInt64Array | null): BigInt64Array | undefined;
+export function echo_option_vec_string(a?: (string)[] | null): (string)[] | undefined;
+export function echo_option_struct(a?: Foo | null): Foo | undefined;
+export function echo_option_vec_struct(a?: (Foo)[] | null): (Foo)[] | undefined;
 export class Foo {
   private constructor();
   free(): void;
diff --git a/crates/cli/tests/reference/echo.js b/crates/cli/tests/reference/echo.js
index 1809b0e8..c3e9c47f 100644
--- a/crates/cli/tests/reference/echo.js
+++ b/crates/cli/tests/reference/echo.js
@@ -727,7 +727,7 @@ export function echo_vec_struct(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_u8(a) {
@@ -736,7 +736,7 @@ export function echo_option_u8(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_i8(a) {
@@ -745,7 +745,7 @@ export function echo_option_i8(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_u16(a) {
@@ -754,7 +754,7 @@ export function echo_option_u16(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_i16(a) {
@@ -763,7 +763,7 @@ export function echo_option_i16(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_u32(a) {
@@ -772,7 +772,7 @@ export function echo_option_u32(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_i32(a) {
@@ -781,7 +781,7 @@ export function echo_option_i32(a) {
 }
 
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_u64(a) {
@@ -790,7 +790,7 @@ export function echo_option_u64(a) {
 }
 
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_i64(a) {
@@ -799,7 +799,7 @@ export function echo_option_i64(a) {
 }
 
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_u128(a) {
@@ -808,7 +808,7 @@ export function echo_option_u128(a) {
 }
 
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_i128(a) {
@@ -817,7 +817,7 @@ export function echo_option_i128(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_usize(a) {
@@ -826,7 +826,7 @@ export function echo_option_usize(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_isize(a) {
@@ -835,7 +835,7 @@ export function echo_option_isize(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_f32(a) {
@@ -844,7 +844,7 @@ export function echo_option_f32(a) {
 }
 
 /**
- * @param {number | undefined} [a]
+ * @param {number | null} [a]
  * @returns {number | undefined}
  */
 export function echo_option_f64(a) {
@@ -853,7 +853,7 @@ export function echo_option_f64(a) {
 }
 
 /**
- * @param {boolean | undefined} [a]
+ * @param {boolean | null} [a]
  * @returns {boolean | undefined}
  */
 export function echo_option_bool(a) {
@@ -862,7 +862,7 @@ export function echo_option_bool(a) {
 }
 
 /**
- * @param {string | undefined} [a]
+ * @param {string | null} [a]
  * @returns {string | undefined}
  */
 export function echo_option_char(a) {
@@ -873,7 +873,7 @@ export function echo_option_char(a) {
 }
 
 /**
- * @param {string | undefined} [a]
+ * @param {string | null} [a]
  * @returns {string | undefined}
  */
 export function echo_option_string(a) {
@@ -889,7 +889,7 @@ export function echo_option_string(a) {
 }
 
 /**
- * @param {Uint8Array | undefined} [a]
+ * @param {Uint8Array | null} [a]
  * @returns {Uint8Array | undefined}
  */
 export function echo_option_vec_u8(a) {
@@ -905,7 +905,7 @@ export function echo_option_vec_u8(a) {
 }
 
 /**
- * @param {Int8Array | undefined} [a]
+ * @param {Int8Array | null} [a]
  * @returns {Int8Array | undefined}
  */
 export function echo_option_vec_i8(a) {
@@ -921,7 +921,7 @@ export function echo_option_vec_i8(a) {
 }
 
 /**
- * @param {Uint16Array | undefined} [a]
+ * @param {Uint16Array | null} [a]
  * @returns {Uint16Array | undefined}
  */
 export function echo_option_vec_u16(a) {
@@ -937,7 +937,7 @@ export function echo_option_vec_u16(a) {
 }
 
 /**
- * @param {Int16Array | undefined} [a]
+ * @param {Int16Array | null} [a]
  * @returns {Int16Array | undefined}
  */
 export function echo_option_vec_i16(a) {
@@ -953,7 +953,7 @@ export function echo_option_vec_i16(a) {
 }
 
 /**
- * @param {Uint32Array | undefined} [a]
+ * @param {Uint32Array | null} [a]
  * @returns {Uint32Array | undefined}
  */
 export function echo_option_vec_u32(a) {
@@ -969,7 +969,7 @@ export function echo_option_vec_u32(a) {
 }
 
 /**
- * @param {Int32Array | undefined} [a]
+ * @param {Int32Array | null} [a]
  * @returns {Int32Array | undefined}
  */
 export function echo_option_vec_i32(a) {
@@ -985,7 +985,7 @@ export function echo_option_vec_i32(a) {
 }
 
 /**
- * @param {BigUint64Array | undefined} [a]
+ * @param {BigUint64Array | null} [a]
  * @returns {BigUint64Array | undefined}
  */
 export function echo_option_vec_u64(a) {
@@ -1001,7 +1001,7 @@ export function echo_option_vec_u64(a) {
 }
 
 /**
- * @param {BigInt64Array | undefined} [a]
+ * @param {BigInt64Array | null} [a]
  * @returns {BigInt64Array | undefined}
  */
 export function echo_option_vec_i64(a) {
@@ -1017,7 +1017,7 @@ export function echo_option_vec_i64(a) {
 }
 
 /**
- * @param {Uint8Array | undefined} [a]
+ * @param {Uint8Array | null} [a]
  * @returns {Uint8Array | undefined}
  */
 export function echo_option_vec_uninit_u8(a) {
@@ -1033,7 +1033,7 @@ export function echo_option_vec_uninit_u8(a) {
 }
 
 /**
- * @param {Int8Array | undefined} [a]
+ * @param {Int8Array | null} [a]
  * @returns {Int8Array | undefined}
  */
 export function echo_option_vec_uninit_i8(a) {
@@ -1049,7 +1049,7 @@ export function echo_option_vec_uninit_i8(a) {
 }
 
 /**
- * @param {Uint16Array | undefined} [a]
+ * @param {Uint16Array | null} [a]
  * @returns {Uint16Array | undefined}
  */
 export function echo_option_vec_uninit_u16(a) {
@@ -1065,7 +1065,7 @@ export function echo_option_vec_uninit_u16(a) {
 }
 
 /**
- * @param {Int16Array | undefined} [a]
+ * @param {Int16Array | null} [a]
  * @returns {Int16Array | undefined}
  */
 export function echo_option_vec_uninit_i16(a) {
@@ -1081,7 +1081,7 @@ export function echo_option_vec_uninit_i16(a) {
 }
 
 /**
- * @param {Uint32Array | undefined} [a]
+ * @param {Uint32Array | null} [a]
  * @returns {Uint32Array | undefined}
  */
 export function echo_option_vec_uninit_u32(a) {
@@ -1097,7 +1097,7 @@ export function echo_option_vec_uninit_u32(a) {
 }
 
 /**
- * @param {Int32Array | undefined} [a]
+ * @param {Int32Array | null} [a]
  * @returns {Int32Array | undefined}
  */
 export function echo_option_vec_uninit_i32(a) {
@@ -1113,7 +1113,7 @@ export function echo_option_vec_uninit_i32(a) {
 }
 
 /**
- * @param {BigUint64Array | undefined} [a]
+ * @param {BigUint64Array | null} [a]
  * @returns {BigUint64Array | undefined}
  */
 export function echo_option_vec_uninit_u64(a) {
@@ -1129,7 +1129,7 @@ export function echo_option_vec_uninit_u64(a) {
 }
 
 /**
- * @param {BigInt64Array | undefined} [a]
+ * @param {BigInt64Array | null} [a]
  * @returns {BigInt64Array | undefined}
  */
 export function echo_option_vec_uninit_i64(a) {
@@ -1145,7 +1145,7 @@ export function echo_option_vec_uninit_i64(a) {
 }
 
 /**
- * @param {(string)[] | undefined} [a]
+ * @param {(string)[] | null} [a]
  * @returns {(string)[] | undefined}
  */
 export function echo_option_vec_string(a) {
@@ -1161,7 +1161,7 @@ export function echo_option_vec_string(a) {
 }
 
 /**
- * @param {Foo | undefined} [a]
+ * @param {Foo | null} [a]
  * @returns {Foo | undefined}
  */
 export function echo_option_struct(a) {
@@ -1175,7 +1175,7 @@ export function echo_option_struct(a) {
 }
 
 /**
- * @param {(Foo)[] | undefined} [a]
+ * @param {(Foo)[] | null} [a]
  * @returns {(Foo)[] | undefined}
  */
 export function echo_option_vec_struct(a) {
diff --git a/crates/cli/tests/reference/enums.d.ts b/crates/cli/tests/reference/enums.d.ts
index da84bbb7..8d1b955c 100644
--- a/crates/cli/tests/reference/enums.d.ts
+++ b/crates/cli/tests/reference/enums.d.ts
@@ -1,10 +1,10 @@
 /* tslint:disable */
 /* eslint-disable */
 export function enum_echo(color: Color): Color;
-export function option_enum_echo(color?: Color): Color | undefined;
+export function option_enum_echo(color?: Color | null): Color | undefined;
 export function get_name(color: Color): ColorName;
-export function option_string_enum_echo(color?: ColorName): ColorName | undefined;
-export function option_order(order?: Ordering): Ordering | undefined;
+export function option_string_enum_echo(color?: ColorName | null): ColorName | undefined;
+export function option_order(order?: Ordering | null): Ordering | undefined;
 /**
  * A color.
  */
diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js
index 13459b9c..10d698dc 100644
--- a/crates/cli/tests/reference/enums.js
+++ b/crates/cli/tests/reference/enums.js
@@ -36,7 +36,7 @@ function isLikeNone(x) {
     return x === undefined || x === null;
 }
 /**
- * @param {Color | undefined} [color]
+ * @param {Color | null} [color]
  * @returns {Color | undefined}
  */
 export function option_enum_echo(color) {
@@ -54,7 +54,7 @@ export function get_name(color) {
 }
 
 /**
- * @param {ColorName | undefined} [color]
+ * @param {ColorName | null} [color]
  * @returns {ColorName | undefined}
  */
 export function option_string_enum_echo(color) {
@@ -63,7 +63,7 @@ export function option_string_enum_echo(color) {
 }
 
 /**
- * @param {Ordering | undefined} [order]
+ * @param {Ordering | null} [order]
  * @returns {Ordering | undefined}
  */
 export function option_order(order) {
diff --git a/crates/cli/tests/reference/getter-setter.d.ts b/crates/cli/tests/reference/getter-setter.d.ts
index 8222ca2a..789fb0f2 100644
--- a/crates/cli/tests/reference/getter-setter.d.ts
+++ b/crates/cli/tests/reference/getter-setter.d.ts
@@ -4,10 +4,12 @@ export class Foo {
   private constructor();
   free(): void;
   x: number;
-  y?: number;
-  z?: number;
+  get y(): number | undefined;
+  set y(value: number | null | undefined);
+  get z(): number | undefined;
+  set z(value: number | null | undefined);
   readonly lone_getter: number | undefined;
-  set lone_setter(value: number | undefined);
+  set lone_setter(value: number | null | undefined);
   /**
    * You will only read numbers.
    */
@@ -17,10 +19,11 @@ export class Foo {
    *
    * Yes, this is totally fine in JS.
    */
-  set weird(value: string | undefined);
+  set weird(value: string | null | undefined);
   /**
    * There can be static getters and setters too, and they can even have the
    * same name as instance getters and setters.
    */
-  static x?: boolean;
+  static get x(): boolean | undefined;
+  static set x(value: boolean | null | undefined);
 }
diff --git a/crates/cli/tests/reference/getter-setter.js b/crates/cli/tests/reference/getter-setter.js
index fc0ce482..a9df5536 100644
--- a/crates/cli/tests/reference/getter-setter.js
+++ b/crates/cli/tests/reference/getter-setter.js
@@ -124,7 +124,7 @@ export class Foo {
         return ret === 0x100000001 ? undefined : ret;
     }
     /**
-     * @param {number | undefined} [arg0]
+     * @param {number | null} [arg0]
      */
     set y(arg0) {
         wasm.__wbg_set_foo_y(this.__wbg_ptr, isLikeNone(arg0) ? 0x100000001 : (arg0) >>> 0);
@@ -137,7 +137,7 @@ export class Foo {
         return ret === 0x100000001 ? undefined : ret;
     }
     /**
-     * @param {number | undefined} [z]
+     * @param {number | null} [z]
      */
     set z(z) {
         wasm.foo_set_z(this.__wbg_ptr, isLikeNone(z) ? 0x100000001 : (z) >>> 0);
@@ -150,7 +150,7 @@ export class Foo {
         return ret === 0x100000001 ? undefined : ret;
     }
     /**
-     * @param {number | undefined} [value]
+     * @param {number | null} [value]
      */
     set lone_setter(value) {
         wasm.foo_set_lone_setter(this.__wbg_ptr, isLikeNone(value) ? 0x100000001 : (value) >>> 0);
@@ -167,7 +167,7 @@ export class Foo {
      * But you must write strings.
      *
      * Yes, this is totally fine in JS.
-     * @param {string | undefined} [value]
+     * @param {string | null} [value]
      */
     set weird(value) {
         var ptr0 = isLikeNone(value) ? 0 : passStringToWasm0(value, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -184,7 +184,7 @@ export class Foo {
         return ret === 0xFFFFFF ? undefined : ret !== 0;
     }
     /**
-     * @param {boolean | undefined} [value]
+     * @param {boolean | null} [value]
      */
     static set x(value) {
         wasm.foo_set_x_static(isLikeNone(value) ? 0xFFFFFF : value ? 1 : 0);
diff --git a/crates/cli/tests/reference/int128.d.ts b/crates/cli/tests/reference/int128.d.ts
index 183cef47..ff5bfb22 100644
--- a/crates/cli/tests/reference/int128.d.ts
+++ b/crates/cli/tests/reference/int128.d.ts
@@ -2,6 +2,6 @@
 /* eslint-disable */
 export function echo_i128(a: bigint): bigint;
 export function echo_u128(a: bigint): bigint;
-export function echo_option_i128(a?: bigint): bigint | undefined;
-export function echo_option_u128(a?: bigint): bigint | undefined;
+export function echo_option_i128(a?: bigint | null): bigint | undefined;
+export function echo_option_u128(a?: bigint | null): bigint | undefined;
 export function throw_i128(): bigint;
diff --git a/crates/cli/tests/reference/int128.js b/crates/cli/tests/reference/int128.js
index 1df6bb28..32de7786 100644
--- a/crates/cli/tests/reference/int128.js
+++ b/crates/cli/tests/reference/int128.js
@@ -45,7 +45,7 @@ function isLikeNone(x) {
     return x === undefined || x === null;
 }
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_i128(a) {
@@ -54,7 +54,7 @@ export function echo_option_i128(a) {
 }
 
 /**
- * @param {bigint | undefined} [a]
+ * @param {bigint | null} [a]
  * @returns {bigint | undefined}
  */
 export function echo_option_u128(a) {
diff --git a/crates/cli/tests/reference/optional-args.d.ts b/crates/cli/tests/reference/optional-args.d.ts
new file mode 100644
index 00000000..4f156949
--- /dev/null
+++ b/crates/cli/tests/reference/optional-args.d.ts
@@ -0,0 +1,4 @@
+/* tslint:disable */
+/* eslint-disable */
+export function all_optional(a?: number | null, b?: number | null, c?: number | null): void;
+export function some_optional(a: number | null | undefined, b: number, c?: number | null): void;
diff --git a/crates/cli/tests/reference/optional-args.js b/crates/cli/tests/reference/optional-args.js
new file mode 100644
index 00000000..43ead5d8
--- /dev/null
+++ b/crates/cli/tests/reference/optional-args.js
@@ -0,0 +1,38 @@
+let wasm;
+export function __wbg_set_wasm(val) {
+    wasm = val;
+}
+
+
+function isLikeNone(x) {
+    return x === undefined || x === null;
+}
+/**
+ * @param {number | null} [a]
+ * @param {number | null} [b]
+ * @param {number | null} [c]
+ */
+export function all_optional(a, b, c) {
+    wasm.all_optional(isLikeNone(a) ? 0x100000001 : (a) >>> 0, isLikeNone(b) ? 0x100000001 : (b) >>> 0, isLikeNone(c) ? 0x100000001 : (c) >>> 0);
+}
+
+/**
+ * @param {number | null | undefined} a
+ * @param {number} b
+ * @param {number | null} [c]
+ */
+export function some_optional(a, b, c) {
+    wasm.some_optional(isLikeNone(a) ? 0x100000001 : (a) >>> 0, b, isLikeNone(c) ? 0x100000001 : (c) >>> 0);
+}
+
+export function __wbindgen_init_externref_table() {
+    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);
+    ;
+};
+
diff --git a/crates/cli/tests/reference/optional-args.rs b/crates/cli/tests/reference/optional-args.rs
new file mode 100644
index 00000000..bbc0fa81
--- /dev/null
+++ b/crates/cli/tests/reference/optional-args.rs
@@ -0,0 +1,7 @@
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub fn all_optional(a: Option<u32>, b: Option<u32>, c: Option<u32>) {}
+
+#[wasm_bindgen]
+pub fn some_optional(a: Option<u32>, b: u32, c: Option<u32>) {}
diff --git a/crates/cli/tests/reference/optional-args.wat b/crates/cli/tests/reference/optional-args.wat
new file mode 100644
index 00000000..a0f6061e
--- /dev/null
+++ b/crates/cli/tests/reference/optional-args.wat
@@ -0,0 +1,17 @@
+(module $reference_test.wasm
+  (type (;0;) (func))
+  (type (;1;) (func (param f64 i32 f64)))
+  (type (;2;) (func (param f64 f64 f64)))
+  (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
+  (func $all_optional (;1;) (type 2) (param f64 f64 f64))
+  (func $some_optional (;2;) (type 1) (param f64 i32 f64))
+  (table (;0;) 128 externref)
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "all_optional" (func $all_optional))
+  (export "some_optional" (func $some_optional))
+  (export "__wbindgen_export_0" (table 0))
+  (export "__wbindgen_start" (func 0))
+  (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
+)
+
diff --git a/crates/typescript-tests/src/optional_fields.ts b/crates/typescript-tests/src/optional_fields.ts
index 61e1fc05..8d1cf90c 100644
--- a/crates/typescript-tests/src/optional_fields.ts
+++ b/crates/typescript-tests/src/optional_fields.ts
@@ -1,3 +1,10 @@
 import * as wbg from '../pkg/typescript_tests';
 
-const fields: wbg.Fields = { spaceboy: true, free: () => { } };
+const fields: wbg.Fields = null as unknown as wbg.Fields;
+
+// optional fields read T | undefined
+const _hallo: boolean | undefined = fields.hallo;
+
+// and allow writing T | null | undefined
+fields.hallo = undefined;
+fields.hallo = null;