Skip to content

Commit

Permalink
Implement #[wasm_bindgen(main)]
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda committed Feb 13, 2023
1 parent d9e113b commit 4640269
Show file tree
Hide file tree
Showing 18 changed files with 255 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: rustup update --no-self-update 1.59.0 && rustup default 1.59.0
- run: rustup update --no-self-update 1.61.0 && rustup default 1.61.0
- run: cargo test -p wasm-bindgen-macro
- run: cargo test -p wasm-bindgen-test-macro

Expand Down
64 changes: 62 additions & 2 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::{ItemFn, Lit, ReturnType};

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

Expand Down Expand Up @@ -80,6 +80,7 @@ macro_rules! attrgen {
(variadic, Variadic(Span)),
(typescript_custom_section, TypescriptCustomSection(Span)),
(skip_typescript, SkipTypescript(Span)),
(main, Main(Span)),
(start, Start(Span)),
(skip, Skip(Span)),
(typescript_type, TypeScriptType(Span, String, Span)),
Expand Down Expand Up @@ -933,6 +934,13 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
) -> Result<(), Diagnostic> {
match self {
syn::Item::Fn(mut f) => {
let opts = opts.unwrap_or_default();

if opts.main().is_some() {
opts.check_used();
return main(f, tokens);
}

let no_mangle = f
.attrs
.iter()
Expand All @@ -951,7 +959,6 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
// `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 f.sig.generics.params.len() > 0 {
bail_span!(&f.sig.generics, "the start function cannot have generics",);
Expand Down Expand Up @@ -1689,6 +1696,59 @@ pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
Ok(ast::LinkToModule(program))
}

fn main(mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
if f.sig.ident != "main" {
bail_span!(&f.sig.ident, "the main function has to be called main");
}
if let Some(constness) = f.sig.constness {
bail_span!(&constness, "the main function cannot be const");
}
if !f.sig.generics.params.is_empty() {
bail_span!(&f.sig.generics, "the main function cannot have generics");
}
if !f.sig.inputs.is_empty() {
bail_span!(&f.sig.inputs, "the main function cannot have arguments");
}

let r#return = f.sig.output;
f.sig.output = ReturnType::Default;
let body = f.block;

if f.sig.asyncness.take().is_some() {
f.block = Box::new(
syn::parse2(quote::quote! {
{
async fn __wasm_bindgen_generated_main() #r#return #body
wasm_bindgen_futures::spawn_local(
async move {
use wasm_bindgen::__rt::Main;
let __ret = __wasm_bindgen_generated_main();
(&mut &mut &mut wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
},
)
}
})
.unwrap(),
);
} else {
f.block = Box::new(
syn::parse2(quote::quote! {
{
fn __wasm_bindgen_generated_main() #r#return #body
use wasm_bindgen::__rt::Main;
let __ret = __wasm_bindgen_generated_main();
(&mut &mut &mut wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
}
})
.unwrap(),
);
}

f.to_tokens(tokens);

Ok(())
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
7 changes: 7 additions & 0 deletions crates/macro/ui-tests/main-async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
async fn main() {}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-async.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-async.rs:7:4
|
7 | fn fail() {}
| ^^^^
18 changes: 18 additions & 0 deletions crates/macro/ui-tests/main-debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::fmt;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() -> Result<(), Test> {
unimplemented!()
}

struct Test;

impl fmt::Debug for Test {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unimplemented!()
}
}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-debug.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-debug.rs:18:4
|
18 | fn fail() {}
| ^^^^
10 changes: 10 additions & 0 deletions crates/macro/ui-tests/main-infallible.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::convert::Infallible;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() -> Infallible {
unimplemented!()
}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-infallible.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-infallible.rs:10:4
|
10 | fn fail() {}
| ^^^^
9 changes: 9 additions & 0 deletions crates/macro/ui-tests/main-jsvalue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() -> Result<(), JsValue> {
unimplemented!()
}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-jsvalue.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-jsvalue.rs:9:4
|
9 | fn fail() {}
| ^^^^
18 changes: 18 additions & 0 deletions crates/macro/ui-tests/main-termination.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::process;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() -> Test {
unimplemented!()
}

struct Test;

impl process::Termination for Test {
fn report(self) -> process::ExitCode {
unimplemented!()
}
}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-termination.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-termination.rs:18:4
|
18 | fn fail() {}
| ^^^^
7 changes: 7 additions & 0 deletions crates/macro/ui-tests/main-unit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() -> () {}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main-unit.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main-unit.rs:7:4
|
7 | fn fail() {}
| ^^^^
7 changes: 7 additions & 0 deletions crates/macro/ui-tests/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(main)]
fn main() {}

#[wasm_bindgen(main)]
fn fail() {}
5 changes: 5 additions & 0 deletions crates/macro/ui-tests/main.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the main function has to be called main
--> ui-tests/main.rs:7:4
|
7 | fn fail() {}
| ^^^^
37 changes: 37 additions & 0 deletions guide/src/reference/attributes/on-rust-exports/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# `main`

When attached to the `fn main()` function this attribute will adjust it to
properly throw errors if they can be. This is only intended to be used for
binaries.

```rust
#[wasm_bindgen(main)]
fn main() -> Result<(), JsValue> {
Err(JsValue::from("this error message will be thrown"))
}
```

The attribute also allows using `async fn main()` in Cargo binaries.

```rust
#[wasm_bindgen(main)]
async fn main() {
// ...
future.await;
}
```

Unlike `#[wasm_bindgen(start)]` this will not export a function to be executed
on startup, it should only be used in Cargo binaries or examples for the `main`
function. `#[wasm_bindgen(start)]` will prevent the `main` function to start and
should not be used in conjunction.

Any return value that is supported by Rust is supported here, see
[`Termination`]. In order, wasm-bindgen will first detect a
`Result<(), impl Into<JsValue>>` and will throw proper `JsValue`s,
`Result<(), impl Debug>` will convert an error to a string and throw that.
Lastly anything implementing [`Termination`] will throw it's reported
[`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) by
using it's `Debug` representation.

[`termination`]: https://doc.rust-lang.org/std/process/trait.Termination.html
44 changes: 44 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,7 @@ pub mod __rt {
use crate::JsValue;
use core::borrow::{Borrow, BorrowMut};
use core::cell::{Cell, UnsafeCell};
use core::convert::Infallible;
use core::ops::{Deref, DerefMut};

pub extern crate core;
Expand Down Expand Up @@ -1728,6 +1729,49 @@ pub mod __rt {
}
}
}

/// An internal helper struct for usage in `#[wasm_bindgen(main)]`
/// functions to throw the error (if it is `Err`).
pub struct MainWrapper<T>(pub Option<T>);

pub trait Main {
fn __wasm_bindgen_main(&mut self);
}

impl Main for &mut &mut MainWrapper<()> {
#[inline]
fn __wasm_bindgen_main(&mut self) {}
}

impl Main for &mut &mut MainWrapper<Infallible> {
#[inline]
fn __wasm_bindgen_main(&mut self) {}
}

impl<E: Into<JsValue>> Main for &mut &mut MainWrapper<Result<(), E>> {
#[inline]
fn __wasm_bindgen_main(&mut self) {
if let Err(e) = self.0.take().unwrap() {
crate::throw_val(e.into());
}
}
}

impl<E: std::fmt::Debug> Main for &mut MainWrapper<Result<(), E>> {
#[inline]
fn __wasm_bindgen_main(&mut self) {
if let Err(e) = self.0.take().unwrap() {
crate::throw_val(std::format!("{:?}", e).into());
}
}
}

impl<T: std::process::Termination> Main for MainWrapper<T> {
#[inline]
fn __wasm_bindgen_main(&mut self) {
crate::throw_val(std::format!("{:?}", self.0.take().unwrap().report()).into());
}
}
}

/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`
Expand Down

0 comments on commit 4640269

Please sign in to comment.