Skip to content

Commit 965b88c

Browse files
authored
Generate TypeScript return types for async functions (#2665)
* Generate TypeScript return types for `async` functions * Fix tests to respect returning only `Result<T, JsValue>` * Fix smoke test * add `simple_async_fn` to `typescript-tests` * cleanup and set JsDoc comment correctly for `Promise<void>` * clean up now that `ts_ret_ty` is complete w/ Promise * add `.d.ts` reference tests * add async reference tests * don't test `.js` and `.wat` in async reference tests
1 parent 6ab9ac0 commit 965b88c

31 files changed

+168
-31
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ node_modules
66
package-lock.json
77
npm-shrinkwrap.json
88
yarn.lock
9-
*.d.ts
109
/publish
1110
/publish.exe
1211
.vscode

crates/backend/src/codegen.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,10 @@ impl TryToTokens for ast::Export {
459459
// For an `async` function we always run it through `future_to_promise`
460460
// since we're returning a promise to JS, and this will implicitly
461461
// require that the function returns a `Future<Output = Result<...>>`
462-
let (ret_ty, ret_expr) = if self.function.r#async {
462+
let (ret_ty, inner_ret_ty, ret_expr) = if self.function.r#async {
463463
if self.start {
464464
(
465+
quote! { () },
465466
quote! { () },
466467
quote! {
467468
wasm_bindgen_futures::spawn_local(async move {
@@ -472,6 +473,7 @@ impl TryToTokens for ast::Export {
472473
} else {
473474
(
474475
quote! { wasm_bindgen::JsValue },
476+
quote! { #syn_ret },
475477
quote! {
476478
wasm_bindgen_futures::future_to_promise(async move {
477479
<#syn_ret as wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await)
@@ -481,17 +483,19 @@ impl TryToTokens for ast::Export {
481483
}
482484
} else if self.start {
483485
(
486+
quote! { () },
484487
quote! { () },
485488
quote! { <#syn_ret as wasm_bindgen::__rt::Start>::start(#ret) },
486489
)
487490
} else {
488-
(quote! { #syn_ret }, quote! { #ret })
491+
(quote! { #syn_ret }, quote! { #syn_ret }, quote! { #ret })
489492
};
490493

491494
let projection = quote! { <#ret_ty as wasm_bindgen::convert::ReturnWasmAbi> };
492495
let convert_ret = quote! { #projection::return_abi(#ret_expr) };
493496
let describe_ret = quote! {
494497
<#ret_ty as WasmDescribe>::describe();
498+
<#inner_ret_ty as WasmDescribe>::describe();
495499
};
496500
let nargs = self.function.arguments.len() as u32;
497501
let attrs = &self.function.rust_attrs;
@@ -1171,6 +1175,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
11711175
inform(#nargs);
11721176
#(<#argtys as WasmDescribe>::describe();)*
11731177
#inform_ret
1178+
#inform_ret
11741179
},
11751180
attrs: f.function.rust_attrs.clone(),
11761181
}

crates/backend/src/encode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi
205205
.collect::<Vec<_>>();
206206
Function {
207207
arg_names,
208+
asyncness: func.r#async,
208209
name: &func.name,
209210
generate_typescript: func.generate_typescript,
210211
}

crates/cli-support/src/descriptor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub struct Function {
7676
pub arguments: Vec<Descriptor>,
7777
pub shim_idx: u32,
7878
pub ret: Descriptor,
79+
pub inner_ret: Option<Descriptor>,
7980
}
8081

8182
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -237,6 +238,7 @@ impl Function {
237238
arguments,
238239
shim_idx,
239240
ret: Descriptor::_decode(data, false),
241+
inner_ret: Some(Descriptor::_decode(data, false)),
240242
}
241243
}
242244
}

crates/cli-support/src/intrinsic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ macro_rules! intrinsics {
4545
shim_idx: 0,
4646
arguments: vec![$($arg),*],
4747
ret: $ret,
48+
inner_ret: None
4849
}
4950
}
5051
)*

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl<'a, 'b> Builder<'a, 'b> {
101101
adapter: &Adapter,
102102
instructions: &[InstructionData],
103103
explicit_arg_names: &Option<Vec<String>>,
104+
asyncness: bool,
104105
) -> Result<JsFunction, Error> {
105106
if self
106107
.cx
@@ -223,8 +224,9 @@ impl<'a, 'b> Builder<'a, 'b> {
223224
let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature(
224225
&function_args,
225226
&arg_tys,
226-
&adapter.results,
227+
&adapter.inner_results,
227228
&mut might_be_optional_field,
229+
asyncness,
228230
);
229231
let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
230232
Ok(JsFunction {
@@ -250,6 +252,7 @@ impl<'a, 'b> Builder<'a, 'b> {
250252
arg_tys: &[&AdapterType],
251253
result_tys: &[AdapterType],
252254
might_be_optional_field: &mut bool,
255+
asyncness: bool,
253256
) -> (String, Vec<String>, Option<String>) {
254257
// Build up the typescript signature as well
255258
let mut omittable = true;
@@ -298,6 +301,9 @@ impl<'a, 'b> Builder<'a, 'b> {
298301
1 => adapter2ts(&result_tys[0], &mut ret),
299302
_ => ret.push_str("[any]"),
300303
}
304+
if asyncness {
305+
ret = format!("Promise<{}>", ret);
306+
}
301307
ts.push_str(&ret);
302308
ts_ret = Some(ret);
303309
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2325,9 +2325,11 @@ impl<'a> Context<'a> {
23252325
});
23262326
builder.catch(catch);
23272327
let mut arg_names = &None;
2328+
let mut asyncness = false;
23282329
match kind {
23292330
Kind::Export(export) => {
23302331
arg_names = &export.arg_names;
2332+
asyncness = export.asyncness;
23312333
match &export.kind {
23322334
AuxExportKind::Function(_) => {}
23332335
AuxExportKind::StaticFunction { .. } => {}
@@ -2352,7 +2354,7 @@ impl<'a> Context<'a> {
23522354
catch,
23532355
log_error,
23542356
} = builder
2355-
.process(&adapter, instrs, arg_names)
2357+
.process(&adapter, instrs, arg_names, asyncness)
23562358
.with_context(|| match kind {
23572359
Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
23582360
Kind::Import(i) => {

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ impl<'a> Context<'a> {
188188
shim_idx: 0,
189189
arguments: vec![Descriptor::I32; 3],
190190
ret: Descriptor::Externref,
191+
inner_ret: None,
191192
};
192193
let id = self.import_adapter(*id, signature, AdapterJsImportKind::Normal)?;
193194
// Synthesize the two integer pointers we pass through which
@@ -455,6 +456,7 @@ impl<'a> Context<'a> {
455456
debug_name: wasm_name,
456457
comments: concatenate_comments(&export.comments),
457458
arg_names: Some(export.function.arg_names),
459+
asyncness: export.function.asyncness,
458460
kind,
459461
generate_typescript: export.function.generate_typescript,
460462
},
@@ -720,6 +722,7 @@ impl<'a> Context<'a> {
720722
arguments: Vec::new(),
721723
shim_idx: 0,
722724
ret: descriptor,
725+
inner_ret: None,
723726
},
724727
AdapterJsImportKind::Normal,
725728
)?;
@@ -748,6 +751,7 @@ impl<'a> Context<'a> {
748751
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
749752
shim_idx: 0,
750753
ret: Descriptor::Boolean,
754+
inner_ret: None,
751755
},
752756
AdapterJsImportKind::Normal,
753757
)?;
@@ -797,13 +801,15 @@ impl<'a> Context<'a> {
797801
arguments: vec![Descriptor::I32],
798802
shim_idx: 0,
799803
ret: descriptor.clone(),
804+
inner_ret: None,
800805
};
801806
let getter_id = self.export_adapter(getter_id, getter_descriptor)?;
802807
self.aux.export_map.insert(
803808
getter_id,
804809
AuxExport {
805810
debug_name: format!("getter for `{}::{}`", struct_.name, field.name),
806811
arg_names: None,
812+
asyncness: false,
807813
comments: concatenate_comments(&field.comments),
808814
kind: AuxExportKind::Getter {
809815
class: struct_.name.to_string(),
@@ -824,13 +830,15 @@ impl<'a> Context<'a> {
824830
arguments: vec![Descriptor::I32, descriptor],
825831
shim_idx: 0,
826832
ret: Descriptor::Unit,
833+
inner_ret: None,
827834
};
828835
let setter_id = self.export_adapter(setter_id, setter_descriptor)?;
829836
self.aux.export_map.insert(
830837
setter_id,
831838
AuxExport {
832839
debug_name: format!("setter for `{}::{}`", struct_.name, field.name),
833840
arg_names: None,
841+
asyncness: false,
834842
comments: concatenate_comments(&field.comments),
835843
kind: AuxExportKind::Setter {
836844
class: struct_.name.to_string(),
@@ -855,6 +863,7 @@ impl<'a> Context<'a> {
855863
shim_idx: 0,
856864
arguments: vec![Descriptor::I32],
857865
ret: Descriptor::Externref,
866+
inner_ret: None,
858867
};
859868
let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
860869
self.aux
@@ -981,6 +990,7 @@ impl<'a> Context<'a> {
981990
let id = self.adapters.append(
982991
params,
983992
results,
993+
vec![],
984994
AdapterKind::Import {
985995
module: import.module.clone(),
986996
name: import.name.clone(),
@@ -1015,6 +1025,7 @@ impl<'a> Context<'a> {
10151025
self.adapters.append(
10161026
params,
10171027
results,
1028+
vec![],
10181029
AdapterKind::Local {
10191030
instructions: Vec::new(),
10201031
},
@@ -1066,6 +1077,7 @@ impl<'a> Context<'a> {
10661077
debug_name: format!("standard export {:?}", id),
10671078
comments: String::new(),
10681079
arg_names: None,
1080+
asyncness: false,
10691081
kind,
10701082
generate_typescript: true,
10711083
};
@@ -1204,6 +1216,7 @@ impl<'a> Context<'a> {
12041216
let f = args.cx.adapters.append(
12051217
args.output,
12061218
ret.input,
1219+
vec![],
12071220
AdapterKind::Import {
12081221
module: import_module,
12091222
name: import_name,
@@ -1237,10 +1250,12 @@ impl<'a> Context<'a> {
12371250
} else {
12381251
ret.output
12391252
};
1240-
let id = args
1241-
.cx
1242-
.adapters
1243-
.append(args.input, results, AdapterKind::Local { instructions });
1253+
let id = args.cx.adapters.append(
1254+
args.input,
1255+
results,
1256+
vec![],
1257+
AdapterKind::Local { instructions },
1258+
);
12441259
args.cx.adapters.implements.push((import_id, core_id, id));
12451260
Ok(f)
12461261
}
@@ -1279,6 +1294,15 @@ impl<'a> Context<'a> {
12791294
}
12801295

12811296
// ... then the returned value being translated back
1297+
1298+
let inner_ret_output = if signature.inner_ret.is_some() {
1299+
let mut inner_ret = args.cx.instruction_builder(true);
1300+
inner_ret.outgoing(&signature.inner_ret.unwrap())?;
1301+
inner_ret.output
1302+
} else {
1303+
vec![]
1304+
};
1305+
12821306
let mut ret = args.cx.instruction_builder(true);
12831307
ret.outgoing(&signature.ret)?;
12841308
let uses_retptr = ret.input.len() > 1;
@@ -1334,10 +1358,12 @@ impl<'a> Context<'a> {
13341358
}
13351359
instructions.extend(ret.instructions);
13361360

1337-
Ok(ret
1338-
.cx
1339-
.adapters
1340-
.append(args.input, ret.output, AdapterKind::Local { instructions }))
1361+
Ok(ret.cx.adapters.append(
1362+
args.input,
1363+
ret.output,
1364+
inner_ret_output,
1365+
AdapterKind::Local { instructions },
1366+
))
13411367
}
13421368

13431369
fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> {

crates/cli-support/src/wit/nonstandard.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ pub struct AuxExport {
7171
/// Argument names in Rust forwarded here to configure the names that show
7272
/// up in TypeScript bindings.
7373
pub arg_names: Option<Vec<String>>,
74+
/// Whether this is an async function, to configure the TypeScript return value.
75+
pub asyncness: bool,
7476
/// What kind of function this is and where it shows up
7577
pub kind: AuxExportKind,
7678
/// Whether typescript bindings should be generated for this export.

crates/cli-support/src/wit/standard.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct Adapter {
2626
pub id: AdapterId,
2727
pub params: Vec<AdapterType>,
2828
pub results: Vec<AdapterType>,
29+
pub inner_results: Vec<AdapterType>,
2930
pub kind: AdapterKind,
3031
}
3132

@@ -368,6 +369,7 @@ impl NonstandardWitSection {
368369
&mut self,
369370
params: Vec<AdapterType>,
370371
results: Vec<AdapterType>,
372+
inner_results: Vec<AdapterType>,
371373
kind: AdapterKind,
372374
) -> AdapterId {
373375
let id = AdapterId(self.adapters.len());
@@ -377,6 +379,7 @@ impl NonstandardWitSection {
377379
id,
378380
params,
379381
results,
382+
inner_results,
380383
kind,
381384
},
382385
);

crates/cli/tests/reference.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ fn runtest(test: &Path) -> Result<()> {
6666
6767
[dependencies]
6868
wasm-bindgen = {{ path = '{}' }}
69+
wasm-bindgen-futures = {{ path = '{}/crates/futures' }}
6970
7071
[lib]
7172
crate-type = ['cdylib']
7273
path = '{}'
7374
",
7475
repo_root().display(),
76+
repo_root().display(),
7577
test.display(),
7678
);
7779
let interface_types = contents.contains("// interface-types");
@@ -93,11 +95,7 @@ fn runtest(test: &Path) -> Result<()> {
9395
.join("reference_test.wasm");
9496

9597
let mut bindgen = Command::cargo_bin("wasm-bindgen")?;
96-
bindgen
97-
.arg("--out-dir")
98-
.arg(td.path())
99-
.arg(&wasm)
100-
.arg("--no-typescript");
98+
bindgen.arg("--out-dir").arg(td.path()).arg(&wasm);
10199
if contents.contains("// enable-externref") {
102100
bindgen.env("WASM_BINDGEN_EXTERNREF", "1");
103101
}
@@ -112,10 +110,14 @@ fn runtest(test: &Path) -> Result<()> {
112110
let wat = sanitize_wasm(&wasm)?;
113111
assert_same(&wat, &test.with_extension("wat"))?;
114112
} else {
115-
let js = fs::read_to_string(td.path().join("reference_test_bg.js"))?;
116-
assert_same(&js, &test.with_extension("js"))?;
117-
let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?;
118-
assert_same(&wat, &test.with_extension("wat"))?;
113+
if !contents.contains("async") {
114+
let js = fs::read_to_string(td.path().join("reference_test_bg.js"))?;
115+
assert_same(&js, &test.with_extension("js"))?;
116+
let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?;
117+
assert_same(&wat, &test.with_extension("wat"))?;
118+
}
119+
let d_ts = fs::read_to_string(td.path().join("reference_test.d.ts"))?;
120+
assert_same(&d_ts, &test.with_extension("d.ts"))?;
119121
}
120122

121123
Ok(())

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
/**
4+
* @param {number} a
5+
* @param {number} b
6+
* @returns {number}
7+
*/
8+
export function add_u32(a: number, b: number): number;
9+
/**
10+
* @param {number} a
11+
* @param {number} b
12+
* @returns {number}
13+
*/
14+
export function add_i32(a: number, b: number): number;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/* tslint:disable */
2+
/* eslint-disable */

0 commit comments

Comments
 (0)