Skip to content

Commit 153a6aa

Browse files
authored
Introduce #[wasm_bindgen(main)] (#3299)
1 parent ab5da24 commit 153a6aa

File tree

16 files changed

+230
-8
lines changed

16 files changed

+230
-8
lines changed

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+
| ^^^^

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+
| ^^^^

guide/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
- [`skip`](./reference/attributes/on-rust-exports/skip.md)
8686
- [`skip_jsdoc`](./reference/attributes/on-rust-exports/skip_jsdoc.md)
8787
- [`start`](./reference/attributes/on-rust-exports/start.md)
88+
- [`main`](./reference/attributes/on-rust-exports/main.md)
8889
- [`typescript_custom_section`](./reference/attributes/on-rust-exports/typescript_custom_section.md)
8990
- [`getter` and `setter`](./reference/attributes/on-rust-exports/getter-and-setter.md)
9091
- [`inspectable`](./reference/attributes/on-rust-exports/inspectable.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# `main`
2+
3+
When attached to the `main` function this attribute will adjust it to properly
4+
throw errors if they can be.
5+
6+
```rust
7+
#[wasm_bindgen(main)]
8+
fn main() -> Result<(), JsValue> {
9+
Err(JsValue::from("this error message will be thrown"))
10+
}
11+
```
12+
13+
The attribute also allows using `async fn main()` in Cargo binaries.
14+
15+
```rust
16+
#[wasm_bindgen(main)]
17+
async fn main() {
18+
// ...
19+
future.await;
20+
}
21+
```
22+
23+
This attribute is only intended to be used on the `main` function of binaries or
24+
examples. Unlike `#[wasm_bindgen(start)]`, it will not cause an arbitrary
25+
function to be executed on start in a library.
26+
27+
The return type support is modeled after [`Termination`]. `()` and `Infallible`
28+
are supported, but [`Termination`] itself is not. In order, wasm-bindgen will
29+
first detect a `Result<(), impl Into<JsValue>>` and will throw proper
30+
`JsValue`s, `Result<(), impl Debug>` will convert an error to a string and throw
31+
that.
32+
33+
[`Termination`]: https://doc.rust-lang.org/std/process/trait.Termination.html

src/lib.rs

+37
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,42 @@ 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_str(&std::format!("{:?}", e));
1765+
}
1766+
}
1767+
}
17311768
}
17321769

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

0 commit comments

Comments
 (0)