Skip to content

Commit 1c742d7

Browse files
committed
Implement #[wasm_bindgen(main)]
1 parent 9958e2e commit 1c742d7

18 files changed

+255
-3
lines changed

.github/workflows/main.yml

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

crates/macro-support/src/parser.rs

+62-2
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
thread_local!(static ATTRS: AttributeParseState = Default::default());
1616

@@ -81,6 +81,7 @@ macro_rules! attrgen {
8181
(typescript_custom_section, TypescriptCustomSection(Span)),
8282
(skip_typescript, SkipTypescript(Span)),
8383
(skip_jsdoc, SkipJsDoc(Span)),
84+
(main, Main(Span)),
8485
(start, Start(Span)),
8586
(skip, Skip(Span)),
8687
(typescript_type, TypeScriptType(Span, String, Span)),
@@ -929,6 +930,13 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
929930
) -> Result<(), Diagnostic> {
930931
match self {
931932
syn::Item::Fn(mut f) => {
933+
let opts = opts.unwrap_or_default();
934+
935+
if opts.main().is_some() {
936+
opts.check_used();
937+
return main(f, tokens);
938+
}
939+
932940
let no_mangle = f
933941
.attrs
934942
.iter()
@@ -946,7 +954,6 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
946954
// `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that.
947955
tokens.extend(quote::quote! { #[allow(dead_code)] });
948956
f.to_tokens(tokens);
949-
let opts = opts.unwrap_or_default();
950957
if opts.start().is_some() {
951958
if f.sig.generics.params.len() > 0 {
952959
bail_span!(&f.sig.generics, "the start function cannot have generics",);
@@ -1693,6 +1700,59 @@ pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
16931700
Ok(ast::LinkToModule(program))
16941701
}
16951702

1703+
fn main(mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
1704+
if f.sig.ident != "main" {
1705+
bail_span!(&f.sig.ident, "the main function has to be called main");
1706+
}
1707+
if let Some(constness) = f.sig.constness {
1708+
bail_span!(&constness, "the main function cannot be const");
1709+
}
1710+
if !f.sig.generics.params.is_empty() {
1711+
bail_span!(&f.sig.generics, "the main function cannot have generics");
1712+
}
1713+
if !f.sig.inputs.is_empty() {
1714+
bail_span!(&f.sig.inputs, "the main function cannot have arguments");
1715+
}
1716+
1717+
let r#return = f.sig.output;
1718+
f.sig.output = ReturnType::Default;
1719+
let body = f.block;
1720+
1721+
if f.sig.asyncness.take().is_some() {
1722+
f.block = Box::new(
1723+
syn::parse2(quote::quote! {
1724+
{
1725+
async fn __wasm_bindgen_generated_main() #r#return #body
1726+
wasm_bindgen_futures::spawn_local(
1727+
async move {
1728+
use wasm_bindgen::__rt::Main;
1729+
let __ret = __wasm_bindgen_generated_main();
1730+
(&mut &mut &mut wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main()
1731+
},
1732+
)
1733+
}
1734+
})
1735+
.unwrap(),
1736+
);
1737+
} else {
1738+
f.block = Box::new(
1739+
syn::parse2(quote::quote! {
1740+
{
1741+
fn __wasm_bindgen_generated_main() #r#return #body
1742+
use wasm_bindgen::__rt::Main;
1743+
let __ret = __wasm_bindgen_generated_main();
1744+
(&mut &mut &mut wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main()
1745+
}
1746+
})
1747+
.unwrap(),
1748+
);
1749+
}
1750+
1751+
f.to_tokens(tokens);
1752+
1753+
Ok(())
1754+
}
1755+
16961756
#[cfg(test)]
16971757
mod tests {
16981758
#[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)