Skip to content

Commit 50c7504

Browse files
Improved TS type generation from WASM (#4229)
1 parent 539ea43 commit 50c7504

File tree

5 files changed

+380
-76
lines changed

5 files changed

+380
-76
lines changed

crates/cli-support/src/wasm2es6js.rs

+73-76
Original file line numberDiff line numberDiff line change
@@ -62,97 +62,94 @@ fn args_are_optional(name: &str) -> bool {
6262

6363
pub fn interface(module: &Module) -> Result<String, Error> {
6464
let mut exports = String::new();
65+
module_export_types(module, |name, ty| {
66+
writeln!(exports, " readonly {}: {};", name, ty).unwrap();
67+
});
68+
Ok(exports)
69+
}
70+
71+
pub fn typescript(module: &Module) -> Result<String, Error> {
72+
let mut exports = "/* tslint:disable */\n/* eslint-disable */\n".to_string();
73+
module_export_types(module, |name, ty| {
74+
writeln!(exports, "export const {}: {};", name, ty).unwrap();
75+
});
76+
Ok(exports)
77+
}
6578

79+
/// Iterates over all the exports in a module and generates TypeScript types. All
80+
/// name-type pairs are passed to the `export` function.
81+
fn module_export_types(module: &Module, mut export: impl FnMut(&str, &str)) {
6682
for entry in module.exports.iter() {
67-
let id = match entry.item {
68-
walrus::ExportItem::Function(i) => i,
69-
walrus::ExportItem::Memory(_) => {
70-
exports.push_str(&format!(" readonly {}: WebAssembly.Memory;\n", entry.name,));
71-
continue;
72-
}
73-
walrus::ExportItem::Table(_) => {
74-
exports.push_str(&format!(" readonly {}: WebAssembly.Table;\n", entry.name,));
75-
continue;
83+
match entry.item {
84+
walrus::ExportItem::Function(id) => {
85+
let func = module.funcs.get(id);
86+
let ty = module.types.get(func.ty());
87+
let ts_type = function_type_to_ts(ty, args_are_optional(&entry.name));
88+
export(&entry.name, &ts_type);
7689
}
90+
walrus::ExportItem::Memory(_) => export(&entry.name, "WebAssembly.Memory"),
91+
walrus::ExportItem::Table(_) => export(&entry.name, "WebAssembly.Table"),
7792
walrus::ExportItem::Global(_) => continue,
7893
};
94+
}
95+
}
96+
fn val_type_to_ts(ty: walrus::ValType) -> &'static str {
97+
// see https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue
98+
// and https://webassembly.github.io/spec/js-api/index.html#tojsvalue
99+
match ty {
100+
walrus::ValType::I32 | walrus::ValType::F32 | walrus::ValType::F64 => "number",
101+
walrus::ValType::I64 => "bigint",
102+
// there could be anything behind a reference
103+
walrus::ValType::Ref(_) => "any",
104+
// V128 currently isn't supported in JS and therefore doesn't have a
105+
// specific type in the spec. When it does get support, this type will
106+
// still be technically correct, but should be updated to something more
107+
// specific.
108+
walrus::ValType::V128 => "any",
109+
}
110+
}
111+
fn function_type_to_ts(function: &walrus::Type, all_args_optional: bool) -> String {
112+
let mut out = String::new();
79113

80-
let func = module.funcs.get(id);
81-
let ty = module.types.get(func.ty());
82-
let mut args = String::new();
83-
for (i, _) in ty.params().iter().enumerate() {
84-
if i > 0 {
85-
args.push_str(", ");
86-
}
87-
88-
push_index_identifier(i, &mut args);
89-
if args_are_optional(&entry.name) {
90-
args.push('?');
91-
}
92-
args.push_str(": number");
114+
// parameters
115+
out.push('(');
116+
for (i, arg_type) in function.params().iter().enumerate() {
117+
if i > 0 {
118+
out.push_str(", ");
93119
}
94120

95-
exports.push_str(&format!(
96-
" readonly {name}: ({args}) => {ret};\n",
97-
name = entry.name,
98-
args = args,
99-
ret = match ty.results().len() {
100-
0 => "void",
101-
1 => "number",
102-
_ => "number[]",
103-
},
104-
));
121+
push_index_identifier(i, &mut out);
122+
if all_args_optional {
123+
out.push('?');
124+
}
125+
out.push_str(": ");
126+
out.push_str(val_type_to_ts(*arg_type));
105127
}
128+
out.push(')');
106129

107-
Ok(exports)
108-
}
109-
110-
pub fn typescript(module: &Module) -> Result<String, Error> {
111-
let mut exports = "/* tslint:disable */\n/* eslint-disable */\n".to_string();
112-
for entry in module.exports.iter() {
113-
let id = match entry.item {
114-
walrus::ExportItem::Function(i) => i,
115-
walrus::ExportItem::Memory(_) => {
116-
exports.push_str(&format!(
117-
"export const {}: WebAssembly.Memory;\n",
118-
entry.name,
119-
));
120-
continue;
121-
}
122-
walrus::ExportItem::Table(_) => {
123-
exports.push_str(&format!(
124-
"export const {}: WebAssembly.Table;\n",
125-
entry.name,
126-
));
127-
continue;
128-
}
129-
walrus::ExportItem::Global(_) => continue,
130-
};
130+
// arrow
131+
out.push_str(" => ");
131132

132-
let func = module.funcs.get(id);
133-
let ty = module.types.get(func.ty());
134-
let mut args = String::new();
135-
for (i, _) in ty.params().iter().enumerate() {
136-
if i > 0 {
137-
args.push_str(", ");
133+
// results
134+
let results = function.results();
135+
// this match follows the spec:
136+
// https://webassembly.github.io/spec/js-api/index.html#exported-function-exotic-objects
137+
match results.len() {
138+
0 => out.push_str("void"),
139+
1 => out.push_str(val_type_to_ts(results[0])),
140+
_ => {
141+
out.push('[');
142+
for (i, result) in results.iter().enumerate() {
143+
if i > 0 {
144+
out.push_str(", ");
145+
}
146+
out.push_str(val_type_to_ts(*result));
138147
}
139-
push_index_identifier(i, &mut args);
140-
args.push_str(": number");
148+
out.push(']');
141149
}
142-
143-
exports.push_str(&format!(
144-
"export function {name}({args}): {ret};\n",
145-
name = entry.name,
146-
args = args,
147-
ret = match ty.results().len() {
148-
0 => "void",
149-
1 => "number",
150-
_ => "number[]",
151-
},
152-
));
153150
}
154151

155-
Ok(exports)
152+
out
156153
}
157154

158155
impl Output {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export function example(a: number, b: bigint, c: any, d: string): string;
4+
export function example_128(a: bigint): bigint | undefined;
5+
6+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
7+
8+
export interface InitOutput {
9+
readonly memory: WebAssembly.Memory;
10+
readonly example: (a: number, b: bigint, c: any, d: number, e: number) => [number, number];
11+
readonly example_128: (a: bigint, b: bigint) => [number, bigint, bigint];
12+
readonly __wbindgen_export_0: WebAssembly.Table;
13+
readonly __wbindgen_malloc: (a: number, b: number) => number;
14+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
15+
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
16+
readonly __wbindgen_start: () => void;
17+
}
18+
19+
export type SyncInitInput = BufferSource | WebAssembly.Module;
20+
/**
21+
* Instantiates the given `module`, which can either be bytes or
22+
* a precompiled `WebAssembly.Module`.
23+
*
24+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
25+
*
26+
* @returns {InitOutput}
27+
*/
28+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
29+
30+
/**
31+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
32+
* for everything else, calls `WebAssembly.instantiate` directly.
33+
*
34+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
35+
*
36+
* @returns {Promise<InitOutput>}
37+
*/
38+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

0 commit comments

Comments
 (0)