Skip to content

Commit 61f228c

Browse files
committed
Implement #[wasm_bindgen(main)]
1 parent ab5da24 commit 61f228c

18 files changed

+264
-9
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ jobs:
222222
runs-on: ubuntu-latest
223223
steps:
224224
- uses: actions/checkout@v3
225-
- run: rustup update --no-self-update 1.59.0 && rustup default 1.59.0
225+
- run: rustup update --no-self-update 1.61.0 && rustup default 1.61.0
226226
- run: cargo test -p wasm-bindgen-macro
227227
- run: cargo test -p wasm-bindgen-test-macro
228228

crates/macro-support/src/parser.rs

+71-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree};
1010
use quote::ToTokens;
1111
use syn::parse::{Parse, ParseStream, Result as SynResult};
1212
use syn::spanned::Spanned;
13-
use syn::{Lit, MacroDelimiter};
13+
use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
1414

1515
use crate::ClassMarker;
1616

@@ -83,6 +83,7 @@ macro_rules! attrgen {
8383
(typescript_custom_section, TypescriptCustomSection(Span)),
8484
(skip_typescript, SkipTypescript(Span)),
8585
(skip_jsdoc, SkipJsDoc(Span)),
86+
(main, Main(Span)),
8687
(start, Start(Span)),
8788
(wasm_bindgen, WasmBindgen(Span, syn::Path)),
8889
(wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)),
@@ -949,6 +950,19 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
949950
) -> Result<(), Diagnostic> {
950951
match self {
951952
syn::Item::Fn(mut f) => {
953+
let opts = opts.unwrap_or_default();
954+
if let Some(path) = opts.wasm_bindgen() {
955+
program.wasm_bindgen = path.clone();
956+
}
957+
if let Some(path) = opts.wasm_bindgen_futures() {
958+
program.wasm_bindgen_futures = path.clone();
959+
}
960+
961+
if opts.main().is_some() {
962+
opts.check_used();
963+
return main(program, f, tokens);
964+
}
965+
952966
let no_mangle = f
953967
.attrs
954968
.iter()
@@ -966,13 +980,6 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
966980
// `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
967981
tokens.extend(quote::quote! { #[allow(dead_code)] });
968982
f.to_tokens(tokens);
969-
let opts = opts.unwrap_or_default();
970-
if let Some(path) = opts.wasm_bindgen() {
971-
program.wasm_bindgen = path.clone();
972-
}
973-
if let Some(path) = opts.wasm_bindgen_futures() {
974-
program.wasm_bindgen_futures = path.clone();
975-
}
976983
if opts.start().is_some() {
977984
if f.sig.generics.params.len() > 0 {
978985
bail_span!(&f.sig.generics, "the start function cannot have generics",);
@@ -1740,6 +1747,62 @@ pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
17401747
Ok(ast::LinkToModule(program))
17411748
}
17421749

1750+
fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
1751+
if f.sig.ident != "main" {
1752+
bail_span!(&f.sig.ident, "the main function has to be called main");
1753+
}
1754+
if let Some(constness) = f.sig.constness {
1755+
bail_span!(&constness, "the main function cannot be const");
1756+
}
1757+
if !f.sig.generics.params.is_empty() {
1758+
bail_span!(&f.sig.generics, "the main function cannot have generics");
1759+
}
1760+
if !f.sig.inputs.is_empty() {
1761+
bail_span!(&f.sig.inputs, "the main function cannot have arguments");
1762+
}
1763+
1764+
let r#return = f.sig.output;
1765+
f.sig.output = ReturnType::Default;
1766+
let body = f.block;
1767+
1768+
let wasm_bindgen = &program.wasm_bindgen;
1769+
let wasm_bindgen_futures = &program.wasm_bindgen_futures;
1770+
1771+
if f.sig.asyncness.take().is_some() {
1772+
f.block = Box::new(
1773+
syn::parse2(quote::quote! {
1774+
{
1775+
async fn __wasm_bindgen_generated_main() #r#return #body
1776+
#wasm_bindgen_futures::spawn_local(
1777+
async move {
1778+
use #wasm_bindgen::__rt::Main;
1779+
let __ret = __wasm_bindgen_generated_main();
1780+
(&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
1781+
},
1782+
)
1783+
}
1784+
})
1785+
.unwrap(),
1786+
);
1787+
} else {
1788+
f.block = Box::new(
1789+
syn::parse2(quote::quote! {
1790+
{
1791+
fn __wasm_bindgen_generated_main() #r#return #body
1792+
use #wasm_bindgen::__rt::Main;
1793+
let __ret = __wasm_bindgen_generated_main();
1794+
(&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
1795+
}
1796+
})
1797+
.unwrap(),
1798+
);
1799+
}
1800+
1801+
f.to_tokens(tokens);
1802+
1803+
Ok(())
1804+
}
1805+
17431806
#[cfg(test)]
17441807
mod tests {
17451808
#[test]

crates/macro/ui-tests/main-async.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen(main)]
4+
async fn main() {}
5+
6+
#[wasm_bindgen(main)]
7+
fn fail() {}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-async.rs:7:4
3+
|
4+
7 | fn fail() {}
5+
| ^^^^

crates/macro/ui-tests/main-debug.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::fmt;
2+
use wasm_bindgen::prelude::*;
3+
4+
#[wasm_bindgen(main)]
5+
fn main() -> Result<(), Test> {
6+
unimplemented!()
7+
}
8+
9+
struct Test;
10+
11+
impl fmt::Debug for Test {
12+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13+
unimplemented!()
14+
}
15+
}
16+
17+
#[wasm_bindgen(main)]
18+
fn fail() {}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-debug.rs:18:4
3+
|
4+
18 | fn fail() {}
5+
| ^^^^
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use std::convert::Infallible;
2+
use wasm_bindgen::prelude::*;
3+
4+
#[wasm_bindgen(main)]
5+
fn main() -> Infallible {
6+
unimplemented!()
7+
}
8+
9+
#[wasm_bindgen(main)]
10+
fn fail() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-infallible.rs:10:4
3+
|
4+
10 | fn fail() {}
5+
| ^^^^

crates/macro/ui-tests/main-jsvalue.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen(main)]
4+
fn main() -> Result<(), JsValue> {
5+
unimplemented!()
6+
}
7+
8+
#[wasm_bindgen(main)]
9+
fn fail() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-jsvalue.rs:9:4
3+
|
4+
9 | fn fail() {}
5+
| ^^^^
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::process;
2+
use wasm_bindgen::prelude::*;
3+
4+
#[wasm_bindgen(main)]
5+
fn main() -> Test {
6+
unimplemented!()
7+
}
8+
9+
struct Test;
10+
11+
impl process::Termination for Test {
12+
fn report(self) -> process::ExitCode {
13+
unimplemented!()
14+
}
15+
}
16+
17+
#[wasm_bindgen(main)]
18+
fn fail() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-termination.rs:18:4
3+
|
4+
18 | fn fail() {}
5+
| ^^^^

crates/macro/ui-tests/main-unit.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen(main)]
4+
fn main() -> () {}
5+
6+
#[wasm_bindgen(main)]
7+
fn fail() {}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main-unit.rs:7:4
3+
|
4+
7 | fn fail() {}
5+
| ^^^^

crates/macro/ui-tests/main.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen(main)]
4+
fn main() {}
5+
6+
#[wasm_bindgen(main)]
7+
fn fail() {}

crates/macro/ui-tests/main.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: the main function has to be called main
2+
--> ui-tests/main.rs:7:4
3+
|
4+
7 | fn fail() {}
5+
| ^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# `main`
2+
3+
When attached to the `fn main()` function this attribute will adjust it to
4+
properly throw errors if they can be. This is only intended to be used for
5+
binaries.
6+
7+
```rust
8+
#[wasm_bindgen(main)]
9+
fn main() -> Result<(), JsValue> {
10+
Err(JsValue::from("this error message will be thrown"))
11+
}
12+
```
13+
14+
The attribute also allows using `async fn main()` in Cargo binaries.
15+
16+
```rust
17+
#[wasm_bindgen(main)]
18+
async fn main() {
19+
// ...
20+
future.await;
21+
}
22+
```
23+
24+
Unlike `#[wasm_bindgen(start)]` this will not export a function to be executed
25+
on startup, it should only be used in Cargo binaries or examples for the `main`
26+
function. `#[wasm_bindgen(start)]` will prevent the `main` function to start and
27+
should not be used in conjunction.
28+
29+
Any return value that is supported by Rust is supported here, see
30+
[`Termination`]. In order, wasm-bindgen will first detect a
31+
`Result<(), impl Into<JsValue>>` and will throw proper `JsValue`s,
32+
`Result<(), impl Debug>` will convert an error to a string and throw that.
33+
Lastly anything implementing [`Termination`] will throw it's reported
34+
[`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) by
35+
using it's `Debug` representation.
36+
37+
[`termination`]: https://doc.rust-lang.org/std/process/trait.Termination.html

src/lib.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,7 @@ pub mod __rt {
13741374
use crate::JsValue;
13751375
use core::borrow::{Borrow, BorrowMut};
13761376
use core::cell::{Cell, UnsafeCell};
1377+
use core::convert::Infallible;
13771378
use core::ops::{Deref, DerefMut};
13781379

13791380
pub extern crate core;
@@ -1728,6 +1729,49 @@ pub mod __rt {
17281729
}
17291730
}
17301731
}
1732+
1733+
/// An internal helper struct for usage in `#[wasm_bindgen(main)]`
1734+
/// functions to throw the error (if it is `Err`).
1735+
pub struct MainWrapper<T>(pub Option<T>);
1736+
1737+
pub trait Main {
1738+
fn __wasm_bindgen_main(&mut self);
1739+
}
1740+
1741+
impl Main for &mut &mut MainWrapper<()> {
1742+
#[inline]
1743+
fn __wasm_bindgen_main(&mut self) {}
1744+
}
1745+
1746+
impl Main for &mut &mut MainWrapper<Infallible> {
1747+
#[inline]
1748+
fn __wasm_bindgen_main(&mut self) {}
1749+
}
1750+
1751+
impl<E: Into<JsValue>> Main for &mut &mut MainWrapper<Result<(), E>> {
1752+
#[inline]
1753+
fn __wasm_bindgen_main(&mut self) {
1754+
if let Err(e) = self.0.take().unwrap() {
1755+
crate::throw_val(e.into());
1756+
}
1757+
}
1758+
}
1759+
1760+
impl<E: std::fmt::Debug> Main for &mut MainWrapper<Result<(), E>> {
1761+
#[inline]
1762+
fn __wasm_bindgen_main(&mut self) {
1763+
if let Err(e) = self.0.take().unwrap() {
1764+
crate::throw_val(std::format!("{:?}", e).into());
1765+
}
1766+
}
1767+
}
1768+
1769+
impl<T: std::process::Termination> Main for MainWrapper<T> {
1770+
#[inline]
1771+
fn __wasm_bindgen_main(&mut self) {
1772+
crate::throw_val(std::format!("{:?}", self.0.take().unwrap().report()).into());
1773+
}
1774+
}
17311775
}
17321776

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

0 commit comments

Comments
 (0)