diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5263ee206cb..6909d10dd08 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - run: rustup update --no-self-update stable && rustup default stable - run: rustup component add rustfmt - run: cargo fmt --all -- --check - + # Check TOML style by using Taplo. taplo: name: Taplo @@ -240,6 +240,22 @@ jobs: # WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1 # - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document" + # This checks that the output of the CLI is actually valid JavaScript and TypeScript + test_cli_reference_typescript: + name: Run CLI reference TypeScript check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - run: npm i -g typescript + - run: npm i --save @types/node @types/deno + - name: Check TypeScript output + run: tsc --noEmit --skipLibCheck --lib esnext,dom $(echo crates/cli/tests/reference/*.d.ts) + - name: Check JavaScript output + run: tsc --noEmit --skipLibCheck --lib esnext,dom --module esnext --allowJs $(echo crates/cli/tests/reference/*.js) + test_native: name: Run native tests runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be38908270..11e3535f4b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,14 @@ ## 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) + ### Fixed -- Fixed JS keyword-like identifiers not being handled correctly for imports and exports. +- Fixed JS keyword-like identifiers not being handled correctly. Keywords on imports will no longer be escaped. Using keyword identifiers in places that caused invalid JS code gen will now error at compile time. [#4329](https://github.com/rustwasm/wasm-bindgen/pull/4329) -------------------------------------------------------------------------------- diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 54be1481163..637c3a5edf7 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>) { +#[derive(Debug, Clone, Copy)] +enum TypePosition { + Argument, + Return, +} + +fn adapter2ts( + ty: &AdapterType, + position: TypePosition, + dst: &mut String, + refs: Option<&mut HashSet>, +) { match ty { AdapterType::I32 | AdapterType::S8 @@ -1564,8 +1584,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet 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 e26f7a6d2ca..9cdd2d7ddb0 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/cli/tests/reference/echo.d.ts b/crates/cli/tests/reference/echo.d.ts index c318f636c11..61fd4a7925e 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 1809b0e8618..c3e9c47f835 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 da84bbb7740..8d1b955c1f9 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 13459b9c77e..10d698dc7e5 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 8222ca2a462..789fb0f279f 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 fc0ce4820dd..a9df5536a5b 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 183cef4758a..ff5bfb22910 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 1df6bb28c26..32de778621c 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 00000000000..4f156949356 --- /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 00000000000..43ead5d86c1 --- /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 00000000000..bbc0fa81e39 --- /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, b: Option, c: Option) {} + +#[wasm_bindgen] +pub fn some_optional(a: Option, b: u32, c: Option) {} diff --git a/crates/cli/tests/reference/optional-args.wat b/crates/cli/tests/reference/optional-args.wat new file mode 100644 index 00000000000..a0f6061efbe --- /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 61e1fc05b1b..8d1cf90c64c 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; diff --git a/crates/web-sys/README.md b/crates/web-sys/README.md index 7693680c6fe..53753673564 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 a6caa7d5145..778b1f40d71 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 { fn rustfmt(paths: impl IntoIterator) -> 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 92b699d57c6..c78dc3c261e 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 { diff --git a/src/closure.rs b/src/closure.rs index 95cf2250a89..d5107c34ec9 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -561,6 +561,7 @@ macro_rules! doit { ($( ($($var:ident $arg1:ident $arg2:ident $arg3:ident $arg4:ident)*) )*) => ($( + #[allow(coherence_leak_check)] unsafe impl<$($var,)* R> WasmClosure for dyn Fn($($var),*) -> R + 'static where $($var: FromWasmAbi + 'static,)* R: ReturnWasmAbi + 'static, @@ -620,6 +621,7 @@ macro_rules! doit { } } + #[allow(coherence_leak_check)] unsafe impl<$($var,)* R> WasmClosure for dyn FnMut($($var),*) -> R + 'static where $($var: FromWasmAbi + 'static,)* R: ReturnWasmAbi + 'static, diff --git a/src/convert/closures.rs b/src/convert/closures.rs index 1f45f90f58b..536ca686d0a 100644 --- a/src/convert/closures.rs +++ b/src/convert/closures.rs @@ -10,7 +10,8 @@ use crate::throw_str; macro_rules! stack_closures { ($( ($cnt:tt $invoke:ident $invoke_mut:ident $($var:ident $arg1:ident $arg2:ident $arg3:ident $arg4:ident)*) )*) => ($( - impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (dyn Fn($($var),*) -> R + 'b) + #[allow(coherence_leak_check)] + impl<$($var,)* R> IntoWasmAbi for &'_ (dyn Fn($($var),*) -> R + '_) where $($var: FromWasmAbi,)* R: ReturnWasmAbi { @@ -50,7 +51,8 @@ macro_rules! stack_closures { ret.return_abi().into() } - impl<'a, $($var,)* R> WasmDescribe for dyn Fn($($var),*) -> R + 'a + #[allow(coherence_leak_check)] + impl<$($var,)* R> WasmDescribe for dyn Fn($($var),*) -> R + '_ where $($var: FromWasmAbi,)* R: ReturnWasmAbi { @@ -65,7 +67,8 @@ macro_rules! stack_closures { } } - impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a mut (dyn FnMut($($var),*) -> R + 'b) + #[allow(coherence_leak_check)] + impl<$($var,)* R> IntoWasmAbi for &'_ mut (dyn FnMut($($var),*) -> R + '_) where $($var: FromWasmAbi,)* R: ReturnWasmAbi { @@ -105,7 +108,8 @@ macro_rules! stack_closures { ret.return_abi().into() } - impl<'a, $($var,)* R> WasmDescribe for dyn FnMut($($var),*) -> R + 'a + #[allow(coherence_leak_check)] + impl<$($var,)* R> WasmDescribe for dyn FnMut($($var),*) -> R + '_ where $($var: FromWasmAbi,)* R: ReturnWasmAbi { diff --git a/src/lib.rs b/src/lib.rs index 73a6147773f..3aac1273ae7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,17 +6,19 @@ //! interface. #![no_std] +#![cfg_attr(wasm_bindgen_unstable_test_coverage, feature(coverage_attribute))] #![cfg_attr( - wasm_bindgen_unstable_test_coverage, - feature(coverage_attribute, allow_internal_unstable), - allow(internal_features) + all(not(feature = "std"), target_feature = "atomics"), + feature(thread_local) )] #![cfg_attr( - all(not(feature = "std"), target_feature = "atomics"), - feature(thread_local, allow_internal_unstable), + any( + all(not(feature = "std"), target_feature = "atomics"), + wasm_bindgen_unstable_test_coverage + ), + feature(allow_internal_unstable), allow(internal_features) )] -#![allow(coherence_leak_check)] #![doc(html_root_url = "https://docs.rs/wasm-bindgen/0.2")] extern crate alloc;