Skip to content

Implement #[wasm_bindgen(start_async)] #3252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::parse::{Parse, ParseStream, Result as SynResult};
use syn::spanned::Spanned;
use syn::Lit;
use syn::{Lit, ReturnType};

thread_local!(static ATTRS: AttributeParseState = Default::default());

Expand Down Expand Up @@ -81,6 +81,7 @@ macro_rules! attrgen {
(typescript_custom_section, TypescriptCustomSection(Span)),
(skip_typescript, SkipTypescript(Span)),
(start, Start(Span)),
(start_async, StartAsync(Span)),
(skip, Skip(Span)),
(typescript_type, TypeScriptType(Span, String, Span)),
(getter_with_clone, GetterWithClone(Span)),
Expand Down Expand Up @@ -745,7 +746,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
match self.vis {
syn::Visibility::Public(_) => {}
_ if attrs.start().is_some() => {}
_ if attrs.start().is_some() || attrs.start_async().is_some() => {}
_ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
}
if self.sig.constness.is_some() {
Expand Down Expand Up @@ -933,9 +934,40 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
// it'll be unused when not building for the wasm target and produce a
// `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
tokens.extend(quote::quote! { #[allow(dead_code)] });
f.to_tokens(tokens);

let opts = opts.unwrap_or_default();
if opts.start().is_some() {

if opts.start_async().is_some() {
if f.sig.asyncness.take().is_none() {
bail_span!(f.sig, "the start_async function has to be async",);
}

let r#return = f.sig.output;
let return_ty = match &r#return {
ReturnType::Default => quote::quote! { () },
ReturnType::Type(_, ty) => ty.to_token_stream(),
};
f.sig.output = ReturnType::Default;
let body = f.block;

f.block = Box::new(
syn::parse2(quote::quote! {
{
async fn __wasm_bindgen_generated_start() #r#return #body
wasm_bindgen_futures::spawn_local(
async move {
let _ret = __wasm_bindgen_generated_start();
<#return_ty as wasm_bindgen::__rt::Start>::start(_ret.await)
},
)
}
})
.unwrap(),
);
}
f.to_tokens(tokens);

if opts.start().is_some() || opts.start_async().is_some() {
if f.sig.generics.params.len() > 0 {
bail_span!(&f.sig.generics, "the start function cannot have generics",);
}
Expand All @@ -948,7 +980,7 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
kind: operation_kind(&opts),
});
let rust_name = f.sig.ident.clone();
let start = opts.start().is_some();
let start = opts.start().is_some() || opts.start_async().is_some();
program.exports.push(ast::Export {
comments,
function: f.convert(opts)?,
Expand Down
3 changes: 3 additions & 0 deletions crates/macro/ui-tests/start-function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ async fn foo_async3() -> Result<JsValue, ()> { Err(()) }
#[wasm_bindgen(start)]
async fn foo_async4() -> Result<JsValue, JsValue> { Ok(JsValue::from(1u32)) }

#[wasm_bindgen(start_async)]
fn foo_missing_async() {}

fn main() {}
6 changes: 6 additions & 0 deletions crates/macro/ui-tests/start-function.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ error: the start function cannot have generics
10 | fn foo3<T>() {}
| ^^^

error: the start_async function has to be async
--> ui-tests/start-function.rs:34:1
|
34 | fn foo_missing_async() {}
| ^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound `Result<wasm_bindgen::JsValue, ()>: wasm_bindgen::__rt::Start` is not satisfied
--> ui-tests/start-function.rs:15:1
|
Expand Down
14 changes: 14 additions & 0 deletions guide/src/reference/attributes/on-rust-exports/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,17 @@ There's a few caveats to be aware of when using the `start` attribute:
executed *once per thread*, not once globally!
* Note that the `start` function is relatively new, so if you find any bugs with
it, please feel free to report an issue!

To support `async fn main()` in a binary, you can use the `start_async` attribute,
à la `#[tokio::main]`, like this:

# `start_async`
```rust
#[wasm_bindgen(start_async)]
async fn main() {
// executed automatically ...
}
```

This will otherwise behave the same as `start`, except that it rewrites the function
to a sync version.