Skip to content

Commit

Permalink
Generate TypeScript return types for async functions (rustwasm#2665)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
trevyn authored Aug 26, 2021
1 parent 6ab9ac0 commit 965b88c
Show file tree
Hide file tree
Showing 31 changed files with 168 additions and 31 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ node_modules
package-lock.json
npm-shrinkwrap.json
yarn.lock
*.d.ts
/publish
/publish.exe
.vscode
Expand Down
9 changes: 7 additions & 2 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,10 @@ impl TryToTokens for ast::Export {
// For an `async` function we always run it through `future_to_promise`
// since we're returning a promise to JS, and this will implicitly
// require that the function returns a `Future<Output = Result<...>>`
let (ret_ty, ret_expr) = if self.function.r#async {
let (ret_ty, inner_ret_ty, ret_expr) = if self.function.r#async {
if self.start {
(
quote! { () },
quote! { () },
quote! {
wasm_bindgen_futures::spawn_local(async move {
Expand All @@ -472,6 +473,7 @@ impl TryToTokens for ast::Export {
} else {
(
quote! { wasm_bindgen::JsValue },
quote! { #syn_ret },
quote! {
wasm_bindgen_futures::future_to_promise(async move {
<#syn_ret as wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await)
Expand All @@ -481,17 +483,19 @@ impl TryToTokens for ast::Export {
}
} else if self.start {
(
quote! { () },
quote! { () },
quote! { <#syn_ret as wasm_bindgen::__rt::Start>::start(#ret) },
)
} else {
(quote! { #syn_ret }, quote! { #ret })
(quote! { #syn_ret }, quote! { #syn_ret }, quote! { #ret })
};

let projection = quote! { <#ret_ty as wasm_bindgen::convert::ReturnWasmAbi> };
let convert_ret = quote! { #projection::return_abi(#ret_expr) };
let describe_ret = quote! {
<#ret_ty as WasmDescribe>::describe();
<#inner_ret_ty as WasmDescribe>::describe();
};
let nargs = self.function.arguments.len() as u32;
let attrs = &self.function.rust_attrs;
Expand Down Expand Up @@ -1171,6 +1175,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
inform(#nargs);
#(<#argtys as WasmDescribe>::describe();)*
#inform_ret
#inform_ret
},
attrs: f.function.rust_attrs.clone(),
}
Expand Down
1 change: 1 addition & 0 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi
.collect::<Vec<_>>();
Function {
arg_names,
asyncness: func.r#async,
name: &func.name,
generate_typescript: func.generate_typescript,
}
Expand Down
2 changes: 2 additions & 0 deletions crates/cli-support/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub struct Function {
pub arguments: Vec<Descriptor>,
pub shim_idx: u32,
pub ret: Descriptor,
pub inner_ret: Option<Descriptor>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -237,6 +238,7 @@ impl Function {
arguments,
shim_idx,
ret: Descriptor::_decode(data, false),
inner_ret: Some(Descriptor::_decode(data, false)),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/cli-support/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ macro_rules! intrinsics {
shim_idx: 0,
arguments: vec![$($arg),*],
ret: $ret,
inner_ret: None
}
}
)*
Expand Down
8 changes: 7 additions & 1 deletion crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl<'a, 'b> Builder<'a, 'b> {
adapter: &Adapter,
instructions: &[InstructionData],
explicit_arg_names: &Option<Vec<String>>,
asyncness: bool,
) -> Result<JsFunction, Error> {
if self
.cx
Expand Down Expand Up @@ -223,8 +224,9 @@ impl<'a, 'b> Builder<'a, 'b> {
let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature(
&function_args,
&arg_tys,
&adapter.results,
&adapter.inner_results,
&mut might_be_optional_field,
asyncness,
);
let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
Ok(JsFunction {
Expand All @@ -250,6 +252,7 @@ impl<'a, 'b> Builder<'a, 'b> {
arg_tys: &[&AdapterType],
result_tys: &[AdapterType],
might_be_optional_field: &mut bool,
asyncness: bool,
) -> (String, Vec<String>, Option<String>) {
// Build up the typescript signature as well
let mut omittable = true;
Expand Down Expand Up @@ -298,6 +301,9 @@ impl<'a, 'b> Builder<'a, 'b> {
1 => adapter2ts(&result_tys[0], &mut ret),
_ => ret.push_str("[any]"),
}
if asyncness {
ret = format!("Promise<{}>", ret);
}
ts.push_str(&ret);
ts_ret = Some(ret);
}
Expand Down
4 changes: 3 additions & 1 deletion crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2325,9 +2325,11 @@ impl<'a> Context<'a> {
});
builder.catch(catch);
let mut arg_names = &None;
let mut asyncness = false;
match kind {
Kind::Export(export) => {
arg_names = &export.arg_names;
asyncness = export.asyncness;
match &export.kind {
AuxExportKind::Function(_) => {}
AuxExportKind::StaticFunction { .. } => {}
Expand All @@ -2352,7 +2354,7 @@ impl<'a> Context<'a> {
catch,
log_error,
} = builder
.process(&adapter, instrs, arg_names)
.process(&adapter, instrs, arg_names, asyncness)
.with_context(|| match kind {
Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
Kind::Import(i) => {
Expand Down
42 changes: 34 additions & 8 deletions crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl<'a> Context<'a> {
shim_idx: 0,
arguments: vec![Descriptor::I32; 3],
ret: Descriptor::Externref,
inner_ret: None,
};
let id = self.import_adapter(*id, signature, AdapterJsImportKind::Normal)?;
// Synthesize the two integer pointers we pass through which
Expand Down Expand Up @@ -455,6 +456,7 @@ impl<'a> Context<'a> {
debug_name: wasm_name,
comments: concatenate_comments(&export.comments),
arg_names: Some(export.function.arg_names),
asyncness: export.function.asyncness,
kind,
generate_typescript: export.function.generate_typescript,
},
Expand Down Expand Up @@ -720,6 +722,7 @@ impl<'a> Context<'a> {
arguments: Vec::new(),
shim_idx: 0,
ret: descriptor,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
Expand Down Expand Up @@ -748,6 +751,7 @@ impl<'a> Context<'a> {
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
shim_idx: 0,
ret: Descriptor::Boolean,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
Expand Down Expand Up @@ -797,13 +801,15 @@ impl<'a> Context<'a> {
arguments: vec![Descriptor::I32],
shim_idx: 0,
ret: descriptor.clone(),
inner_ret: None,
};
let getter_id = self.export_adapter(getter_id, getter_descriptor)?;
self.aux.export_map.insert(
getter_id,
AuxExport {
debug_name: format!("getter for `{}::{}`", struct_.name, field.name),
arg_names: None,
asyncness: false,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Getter {
class: struct_.name.to_string(),
Expand All @@ -824,13 +830,15 @@ impl<'a> Context<'a> {
arguments: vec![Descriptor::I32, descriptor],
shim_idx: 0,
ret: Descriptor::Unit,
inner_ret: None,
};
let setter_id = self.export_adapter(setter_id, setter_descriptor)?;
self.aux.export_map.insert(
setter_id,
AuxExport {
debug_name: format!("setter for `{}::{}`", struct_.name, field.name),
arg_names: None,
asyncness: false,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Setter {
class: struct_.name.to_string(),
Expand All @@ -855,6 +863,7 @@ impl<'a> Context<'a> {
shim_idx: 0,
arguments: vec![Descriptor::I32],
ret: Descriptor::Externref,
inner_ret: None,
};
let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
self.aux
Expand Down Expand Up @@ -981,6 +990,7 @@ impl<'a> Context<'a> {
let id = self.adapters.append(
params,
results,
vec![],
AdapterKind::Import {
module: import.module.clone(),
name: import.name.clone(),
Expand Down Expand Up @@ -1015,6 +1025,7 @@ impl<'a> Context<'a> {
self.adapters.append(
params,
results,
vec![],
AdapterKind::Local {
instructions: Vec::new(),
},
Expand Down Expand Up @@ -1066,6 +1077,7 @@ impl<'a> Context<'a> {
debug_name: format!("standard export {:?}", id),
comments: String::new(),
arg_names: None,
asyncness: false,
kind,
generate_typescript: true,
};
Expand Down Expand Up @@ -1204,6 +1216,7 @@ impl<'a> Context<'a> {
let f = args.cx.adapters.append(
args.output,
ret.input,
vec![],
AdapterKind::Import {
module: import_module,
name: import_name,
Expand Down Expand Up @@ -1237,10 +1250,12 @@ impl<'a> Context<'a> {
} else {
ret.output
};
let id = args
.cx
.adapters
.append(args.input, results, AdapterKind::Local { instructions });
let id = args.cx.adapters.append(
args.input,
results,
vec![],
AdapterKind::Local { instructions },
);
args.cx.adapters.implements.push((import_id, core_id, id));
Ok(f)
}
Expand Down Expand Up @@ -1279,6 +1294,15 @@ impl<'a> Context<'a> {
}

// ... then the returned value being translated back

let inner_ret_output = if signature.inner_ret.is_some() {
let mut inner_ret = args.cx.instruction_builder(true);
inner_ret.outgoing(&signature.inner_ret.unwrap())?;
inner_ret.output
} else {
vec![]
};

let mut ret = args.cx.instruction_builder(true);
ret.outgoing(&signature.ret)?;
let uses_retptr = ret.input.len() > 1;
Expand Down Expand Up @@ -1334,10 +1358,12 @@ impl<'a> Context<'a> {
}
instructions.extend(ret.instructions);

Ok(ret
.cx
.adapters
.append(args.input, ret.output, AdapterKind::Local { instructions }))
Ok(ret.cx.adapters.append(
args.input,
ret.output,
inner_ret_output,
AdapterKind::Local { instructions },
))
}

fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> {
Expand Down
2 changes: 2 additions & 0 deletions crates/cli-support/src/wit/nonstandard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ pub struct AuxExport {
/// Argument names in Rust forwarded here to configure the names that show
/// up in TypeScript bindings.
pub arg_names: Option<Vec<String>>,
/// Whether this is an async function, to configure the TypeScript return value.
pub asyncness: bool,
/// What kind of function this is and where it shows up
pub kind: AuxExportKind,
/// Whether typescript bindings should be generated for this export.
Expand Down
3 changes: 3 additions & 0 deletions crates/cli-support/src/wit/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Adapter {
pub id: AdapterId,
pub params: Vec<AdapterType>,
pub results: Vec<AdapterType>,
pub inner_results: Vec<AdapterType>,
pub kind: AdapterKind,
}

Expand Down Expand Up @@ -368,6 +369,7 @@ impl NonstandardWitSection {
&mut self,
params: Vec<AdapterType>,
results: Vec<AdapterType>,
inner_results: Vec<AdapterType>,
kind: AdapterKind,
) -> AdapterId {
let id = AdapterId(self.adapters.len());
Expand All @@ -377,6 +379,7 @@ impl NonstandardWitSection {
id,
params,
results,
inner_results,
kind,
},
);
Expand Down
20 changes: 11 additions & 9 deletions crates/cli/tests/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ fn runtest(test: &Path) -> Result<()> {
[dependencies]
wasm-bindgen = {{ path = '{}' }}
wasm-bindgen-futures = {{ path = '{}/crates/futures' }}
[lib]
crate-type = ['cdylib']
path = '{}'
",
repo_root().display(),
repo_root().display(),
test.display(),
);
let interface_types = contents.contains("// interface-types");
Expand All @@ -93,11 +95,7 @@ fn runtest(test: &Path) -> Result<()> {
.join("reference_test.wasm");

let mut bindgen = Command::cargo_bin("wasm-bindgen")?;
bindgen
.arg("--out-dir")
.arg(td.path())
.arg(&wasm)
.arg("--no-typescript");
bindgen.arg("--out-dir").arg(td.path()).arg(&wasm);
if contents.contains("// enable-externref") {
bindgen.env("WASM_BINDGEN_EXTERNREF", "1");
}
Expand All @@ -112,10 +110,14 @@ fn runtest(test: &Path) -> Result<()> {
let wat = sanitize_wasm(&wasm)?;
assert_same(&wat, &test.with_extension("wat"))?;
} else {
let js = fs::read_to_string(td.path().join("reference_test_bg.js"))?;
assert_same(&js, &test.with_extension("js"))?;
let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?;
assert_same(&wat, &test.with_extension("wat"))?;
if !contents.contains("async") {
let js = fs::read_to_string(td.path().join("reference_test_bg.js"))?;
assert_same(&js, &test.with_extension("js"))?;
let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?;
assert_same(&wat, &test.with_extension("wat"))?;
}
let d_ts = fs::read_to_string(td.path().join("reference_test.d.ts"))?;
assert_same(&d_ts, &test.with_extension("d.ts"))?;
}

Ok(())
Expand Down
14 changes: 14 additions & 0 deletions crates/cli/tests/reference/add.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function add_u32(a: number, b: number): number;
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function add_i32(a: number, b: number): number;
2 changes: 2 additions & 0 deletions crates/cli/tests/reference/anyref-empty.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* tslint:disable */
/* eslint-disable */
Loading

0 comments on commit 965b88c

Please sign in to comment.