Skip to content

Commit a0b9b43

Browse files
Merge branch 'main' into deprecate-spans
2 parents 6e0ef2f + 54f97c9 commit a0b9b43

File tree

23 files changed

+311
-153
lines changed

23 files changed

+311
-153
lines changed

.github/workflows/main.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- run: rustup update --no-self-update stable && rustup default stable
3030
- run: rustup component add rustfmt
3131
- run: cargo fmt --all -- --check
32-
32+
3333
# Check TOML style by using Taplo.
3434
taplo:
3535
name: Taplo
@@ -240,6 +240,22 @@ jobs:
240240
# WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
241241
# - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document"
242242

243+
# This checks that the output of the CLI is actually valid JavaScript and TypeScript
244+
test_cli_reference_typescript:
245+
name: Run CLI reference TypeScript check
246+
runs-on: ubuntu-latest
247+
steps:
248+
- uses: actions/checkout@v4
249+
- uses: actions/setup-node@v4
250+
with:
251+
node-version: 'lts/*'
252+
- run: npm i -g typescript
253+
- run: npm i --save @types/node @types/deno
254+
- name: Check TypeScript output
255+
run: tsc --noEmit --skipLibCheck --lib esnext,dom $(echo crates/cli/tests/reference/*.d.ts)
256+
- name: Check JavaScript output
257+
run: tsc --noEmit --skipLibCheck --lib esnext,dom --module esnext --allowJs $(echo crates/cli/tests/reference/*.js)
258+
243259
test_native:
244260
name: Run native tests
245261
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# `wasm-bindgen` Change Log
22
--------------------------------------------------------------------------------
33

4+
## Unreleased
5+
6+
### Changed
7+
8+
* Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.
9+
[#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188)
10+
11+
--------------------------------------------------------------------------------
12+
413
## [0.2.99](https://github.com/rustwasm/wasm-bindgen/compare/0.2.98...0.2.99)
514

615
Released 2024-12-07

crates/cli-support/src/js/binding.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,15 @@ impl<'a, 'b> Builder<'a, 'b> {
321321
let mut ts = String::new();
322322
match ty {
323323
AdapterType::Option(ty) if omittable => {
324+
// e.g. `foo?: string | null`
324325
arg.push_str("?: ");
325-
adapter2ts(ty, &mut ts, Some(&mut ts_refs));
326+
adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
327+
ts.push_str(" | null");
326328
}
327329
ty => {
328330
omittable = false;
329331
arg.push_str(": ");
330-
adapter2ts(ty, &mut ts, Some(&mut ts_refs));
332+
adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
331333
}
332334
}
333335
arg.push_str(&ts);
@@ -363,7 +365,12 @@ impl<'a, 'b> Builder<'a, 'b> {
363365
let mut ret = String::new();
364366
match result_tys.len() {
365367
0 => ret.push_str("void"),
366-
1 => adapter2ts(&result_tys[0], &mut ret, Some(&mut ts_refs)),
368+
1 => adapter2ts(
369+
&result_tys[0],
370+
TypePosition::Return,
371+
&mut ret,
372+
Some(&mut ts_refs),
373+
),
367374
_ => ret.push_str("[any]"),
368375
}
369376
if asyncness {
@@ -395,16 +402,18 @@ impl<'a, 'b> Builder<'a, 'b> {
395402
for (name, ty) in fn_arg_names.iter().zip(arg_tys).rev() {
396403
let mut arg = "@param {".to_string();
397404

398-
adapter2ts(ty, &mut arg, None);
399-
arg.push_str("} ");
400405
match ty {
401-
AdapterType::Option(..) if omittable => {
406+
AdapterType::Option(ty) if omittable => {
407+
adapter2ts(ty, TypePosition::Argument, &mut arg, None);
408+
arg.push_str(" | null} ");
402409
arg.push('[');
403410
arg.push_str(name);
404411
arg.push(']');
405412
}
406413
_ => {
407414
omittable = false;
415+
adapter2ts(ty, TypePosition::Argument, &mut arg, None);
416+
arg.push_str("} ");
408417
arg.push_str(name);
409418
}
410419
}
@@ -416,7 +425,7 @@ impl<'a, 'b> Builder<'a, 'b> {
416425

417426
if let (Some(name), Some(ty)) = (variadic_arg, arg_tys.last()) {
418427
ret.push_str("@param {...");
419-
adapter2ts(ty, &mut ret, None);
428+
adapter2ts(ty, TypePosition::Argument, &mut ret, None);
420429
ret.push_str("} ");
421430
ret.push_str(name);
422431
ret.push('\n');
@@ -1542,7 +1551,18 @@ impl Invocation {
15421551
}
15431552
}
15441553

1545-
fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsReference>>) {
1554+
#[derive(Debug, Clone, Copy)]
1555+
enum TypePosition {
1556+
Argument,
1557+
Return,
1558+
}
1559+
1560+
fn adapter2ts(
1561+
ty: &AdapterType,
1562+
position: TypePosition,
1563+
dst: &mut String,
1564+
refs: Option<&mut HashSet<TsReference>>,
1565+
) {
15461566
match ty {
15471567
AdapterType::I32
15481568
| AdapterType::S8
@@ -1564,8 +1584,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsRe
15641584
AdapterType::Bool => dst.push_str("boolean"),
15651585
AdapterType::Vector(kind) => dst.push_str(&kind.js_ty()),
15661586
AdapterType::Option(ty) => {
1567-
adapter2ts(ty, dst, refs);
1568-
dst.push_str(" | undefined");
1587+
adapter2ts(ty, position, dst, refs);
1588+
dst.push_str(match position {
1589+
TypePosition::Argument => " | null | undefined",
1590+
TypePosition::Return => " | undefined",
1591+
});
15691592
}
15701593
AdapterType::NamedExternref(name) => dst.push_str(name),
15711594
AdapterType::Struct(name) => dst.push_str(name),

crates/cli-support/src/js/mod.rs

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ struct FieldAccessor {
120120
is_optional: bool,
121121
}
122122

123+
/// Different JS constructs that can be exported.
124+
enum ExportJs<'a> {
125+
/// A class of the form `class Name {...}`.
126+
Class(&'a str),
127+
/// An anonymous function expression of the form `function(...) {...}`.
128+
///
129+
/// Note that the function name is not included in the string.
130+
Function(&'a str),
131+
/// An arbitrary JS expression.
132+
Expression(&'a str),
133+
}
134+
123135
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
124136
// Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
125137
const INITIAL_HEAP_OFFSET: usize = 128;
@@ -163,38 +175,46 @@ impl<'a> Context<'a> {
163175
fn export(
164176
&mut self,
165177
export_name: &str,
166-
contents: &str,
178+
export: ExportJs,
167179
comments: Option<&str>,
168180
) -> Result<(), Error> {
169181
let definition_name = self.generate_identifier(export_name);
170-
if contents.starts_with("class") && definition_name != export_name {
182+
if matches!(export, ExportJs::Class(_)) && definition_name != export_name {
171183
bail!("cannot shadow already defined class `{}`", export_name);
172184
}
173185

174-
let contents = contents.trim();
186+
// write out comments
175187
if let Some(c) = comments {
176188
self.globals.push_str(c);
177189
}
190+
178191
let global = match self.config.mode {
179-
OutputMode::Node { module: false } => {
180-
if contents.starts_with("class") {
181-
format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
182-
} else {
183-
format!("module.exports.{} = {};\n", export_name, contents)
192+
OutputMode::Node { module: false } => match export {
193+
ExportJs::Class(class) => {
194+
format!("{}\nmodule.exports.{1} = {1};\n", class, export_name)
184195
}
185-
}
186-
OutputMode::NoModules { .. } => {
187-
if contents.starts_with("class") {
188-
format!("{}\n__exports.{1} = {1};\n", contents, export_name)
189-
} else {
190-
format!("__exports.{} = {};\n", export_name, contents)
196+
ExportJs::Function(expr) | ExportJs::Expression(expr) => {
197+
format!("module.exports.{} = {};\n", export_name, expr)
191198
}
192-
}
199+
},
200+
OutputMode::NoModules { .. } => match export {
201+
ExportJs::Class(class) => {
202+
format!("{}\n__exports.{1} = {1};\n", class, export_name)
203+
}
204+
ExportJs::Function(expr) | ExportJs::Expression(expr) => {
205+
format!("__exports.{} = {};\n", export_name, expr)
206+
}
207+
},
193208
OutputMode::Bundler { .. }
194209
| OutputMode::Node { module: true }
195210
| OutputMode::Web
196-
| OutputMode::Deno => {
197-
if let Some(body) = contents.strip_prefix("function") {
211+
| OutputMode::Deno => match export {
212+
ExportJs::Class(class) => {
213+
assert_eq!(export_name, definition_name);
214+
format!("export {}\n", class)
215+
}
216+
ExportJs::Function(function) => {
217+
let body = function.strip_prefix("function").unwrap();
198218
if export_name == definition_name {
199219
format!("export function {}{}\n", export_name, body)
200220
} else {
@@ -203,14 +223,12 @@ impl<'a> Context<'a> {
203223
definition_name, body, definition_name, export_name,
204224
)
205225
}
206-
} else if contents.starts_with("class") {
207-
assert_eq!(export_name, definition_name);
208-
format!("export {}\n", contents)
209-
} else {
226+
}
227+
ExportJs::Expression(expr) => {
210228
assert_eq!(export_name, definition_name);
211-
format!("export const {} = {};\n", export_name, contents)
229+
format!("export const {} = {};\n", export_name, expr)
212230
}
213-
}
231+
},
214232
};
215233
self.global(&global);
216234
Ok(())
@@ -1169,10 +1187,10 @@ __wbg_set_wasm(wasm);"
11691187

11701188
self.write_class_field_types(class, &mut ts_dst);
11711189

1172-
dst.push_str("}\n");
1190+
dst.push('}');
11731191
ts_dst.push_str("}\n");
11741192

1175-
self.export(name, &dst, Some(&class.comments))?;
1193+
self.export(name, ExportJs::Class(&dst), Some(&class.comments))?;
11761194

11771195
if class.generate_typescript {
11781196
self.typescript.push_str(&class.comments);
@@ -2872,7 +2890,11 @@ __wbg_set_wasm(wasm);"
28722890
self.typescript.push_str(";\n");
28732891
}
28742892

2875-
self.export(name, &format!("function{}", code), Some(&js_docs))?;
2893+
self.export(
2894+
name,
2895+
ExportJs::Function(&format!("function{}", code)),
2896+
Some(&js_docs),
2897+
)?;
28762898
self.globals.push('\n');
28772899
}
28782900
AuxExportKind::Constructor(class) => {
@@ -3995,7 +4017,7 @@ __wbg_set_wasm(wasm);"
39954017

39964018
self.export(
39974019
&enum_.name,
3998-
&format!("Object.freeze({{\n{}}})", variants),
4020+
ExportJs::Expression(&format!("Object.freeze({{\n{}}})", variants)),
39994021
Some(&docs),
40004022
)?;
40014023

crates/cli/tests/reference/echo.d.ts

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -36,42 +36,42 @@ export function echo_vec_uninit_i64(a: BigInt64Array): BigInt64Array;
3636
export function echo_vec_string(a: (string)[]): (string)[];
3737
export function echo_struct(a: Foo): Foo;
3838
export function echo_vec_struct(a: (Foo)[]): (Foo)[];
39-
export function echo_option_u8(a?: number): number | undefined;
40-
export function echo_option_i8(a?: number): number | undefined;
41-
export function echo_option_u16(a?: number): number | undefined;
42-
export function echo_option_i16(a?: number): number | undefined;
43-
export function echo_option_u32(a?: number): number | undefined;
44-
export function echo_option_i32(a?: number): number | undefined;
45-
export function echo_option_u64(a?: bigint): bigint | undefined;
46-
export function echo_option_i64(a?: bigint): bigint | undefined;
47-
export function echo_option_u128(a?: bigint): bigint | undefined;
48-
export function echo_option_i128(a?: bigint): bigint | undefined;
49-
export function echo_option_usize(a?: number): number | undefined;
50-
export function echo_option_isize(a?: number): number | undefined;
51-
export function echo_option_f32(a?: number): number | undefined;
52-
export function echo_option_f64(a?: number): number | undefined;
53-
export function echo_option_bool(a?: boolean): boolean | undefined;
54-
export function echo_option_char(a?: string): string | undefined;
55-
export function echo_option_string(a?: string): string | undefined;
56-
export function echo_option_vec_u8(a?: Uint8Array): Uint8Array | undefined;
57-
export function echo_option_vec_i8(a?: Int8Array): Int8Array | undefined;
58-
export function echo_option_vec_u16(a?: Uint16Array): Uint16Array | undefined;
59-
export function echo_option_vec_i16(a?: Int16Array): Int16Array | undefined;
60-
export function echo_option_vec_u32(a?: Uint32Array): Uint32Array | undefined;
61-
export function echo_option_vec_i32(a?: Int32Array): Int32Array | undefined;
62-
export function echo_option_vec_u64(a?: BigUint64Array): BigUint64Array | undefined;
63-
export function echo_option_vec_i64(a?: BigInt64Array): BigInt64Array | undefined;
64-
export function echo_option_vec_uninit_u8(a?: Uint8Array): Uint8Array | undefined;
65-
export function echo_option_vec_uninit_i8(a?: Int8Array): Int8Array | undefined;
66-
export function echo_option_vec_uninit_u16(a?: Uint16Array): Uint16Array | undefined;
67-
export function echo_option_vec_uninit_i16(a?: Int16Array): Int16Array | undefined;
68-
export function echo_option_vec_uninit_u32(a?: Uint32Array): Uint32Array | undefined;
69-
export function echo_option_vec_uninit_i32(a?: Int32Array): Int32Array | undefined;
70-
export function echo_option_vec_uninit_u64(a?: BigUint64Array): BigUint64Array | undefined;
71-
export function echo_option_vec_uninit_i64(a?: BigInt64Array): BigInt64Array | undefined;
72-
export function echo_option_vec_string(a?: (string)[]): (string)[] | undefined;
73-
export function echo_option_struct(a?: Foo): Foo | undefined;
74-
export function echo_option_vec_struct(a?: (Foo)[]): (Foo)[] | undefined;
39+
export function echo_option_u8(a?: number | null): number | undefined;
40+
export function echo_option_i8(a?: number | null): number | undefined;
41+
export function echo_option_u16(a?: number | null): number | undefined;
42+
export function echo_option_i16(a?: number | null): number | undefined;
43+
export function echo_option_u32(a?: number | null): number | undefined;
44+
export function echo_option_i32(a?: number | null): number | undefined;
45+
export function echo_option_u64(a?: bigint | null): bigint | undefined;
46+
export function echo_option_i64(a?: bigint | null): bigint | undefined;
47+
export function echo_option_u128(a?: bigint | null): bigint | undefined;
48+
export function echo_option_i128(a?: bigint | null): bigint | undefined;
49+
export function echo_option_usize(a?: number | null): number | undefined;
50+
export function echo_option_isize(a?: number | null): number | undefined;
51+
export function echo_option_f32(a?: number | null): number | undefined;
52+
export function echo_option_f64(a?: number | null): number | undefined;
53+
export function echo_option_bool(a?: boolean | null): boolean | undefined;
54+
export function echo_option_char(a?: string | null): string | undefined;
55+
export function echo_option_string(a?: string | null): string | undefined;
56+
export function echo_option_vec_u8(a?: Uint8Array | null): Uint8Array | undefined;
57+
export function echo_option_vec_i8(a?: Int8Array | null): Int8Array | undefined;
58+
export function echo_option_vec_u16(a?: Uint16Array | null): Uint16Array | undefined;
59+
export function echo_option_vec_i16(a?: Int16Array | null): Int16Array | undefined;
60+
export function echo_option_vec_u32(a?: Uint32Array | null): Uint32Array | undefined;
61+
export function echo_option_vec_i32(a?: Int32Array | null): Int32Array | undefined;
62+
export function echo_option_vec_u64(a?: BigUint64Array | null): BigUint64Array | undefined;
63+
export function echo_option_vec_i64(a?: BigInt64Array | null): BigInt64Array | undefined;
64+
export function echo_option_vec_uninit_u8(a?: Uint8Array | null): Uint8Array | undefined;
65+
export function echo_option_vec_uninit_i8(a?: Int8Array | null): Int8Array | undefined;
66+
export function echo_option_vec_uninit_u16(a?: Uint16Array | null): Uint16Array | undefined;
67+
export function echo_option_vec_uninit_i16(a?: Int16Array | null): Int16Array | undefined;
68+
export function echo_option_vec_uninit_u32(a?: Uint32Array | null): Uint32Array | undefined;
69+
export function echo_option_vec_uninit_i32(a?: Int32Array | null): Int32Array | undefined;
70+
export function echo_option_vec_uninit_u64(a?: BigUint64Array | null): BigUint64Array | undefined;
71+
export function echo_option_vec_uninit_i64(a?: BigInt64Array | null): BigInt64Array | undefined;
72+
export function echo_option_vec_string(a?: (string)[] | null): (string)[] | undefined;
73+
export function echo_option_struct(a?: Foo | null): Foo | undefined;
74+
export function echo_option_vec_struct(a?: (Foo)[] | null): (Foo)[] | undefined;
7575
export class Foo {
7676
private constructor();
7777
free(): void;

0 commit comments

Comments
 (0)