Skip to content
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

Introduce #[wasm_bindgen(main)] #3299

Merged
merged 4 commits into from
Apr 12, 2023
Merged
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
79 changes: 71 additions & 8 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::{Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::parse::{Parse, ParseStream, Result as SynResult};
use syn::spanned::Spanned;
use syn::{Lit, MacroDelimiter};
use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};

use crate::ClassMarker;

Expand Down Expand Up @@ -83,6 +83,7 @@ macro_rules! attrgen {
(typescript_custom_section, TypescriptCustomSection(Span)),
(skip_typescript, SkipTypescript(Span)),
(skip_jsdoc, SkipJsDoc(Span)),
(main, Main(Span)),
(start, Start(Span)),
(wasm_bindgen, WasmBindgen(Span, syn::Path)),
(wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)),
Expand Down Expand Up @@ -949,6 +950,19 @@ 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 let Some(path) = opts.wasm_bindgen() {
program.wasm_bindgen = path.clone();
}
if let Some(path) = opts.wasm_bindgen_futures() {
program.wasm_bindgen_futures = path.clone();
}

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

let no_mangle = f
.attrs
.iter()
Expand All @@ -966,13 +980,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 let Some(path) = opts.wasm_bindgen() {
program.wasm_bindgen = path.clone();
}
if let Some(path) = opts.wasm_bindgen_futures() {
program.wasm_bindgen_futures = path.clone();
}
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 @@ -1740,6 +1747,62 @@ pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> {
Ok(ast::LinkToModule(program))
}

fn main(program: &ast::Program, 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;

let wasm_bindgen = &program.wasm_bindgen;
let wasm_bindgen_futures = &program.wasm_bindgen_futures;

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() {}
| ^^^^
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() {}
| ^^^^
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
- [`skip`](./reference/attributes/on-rust-exports/skip.md)
- [`skip_jsdoc`](./reference/attributes/on-rust-exports/skip_jsdoc.md)
- [`start`](./reference/attributes/on-rust-exports/start.md)
- [`main`](./reference/attributes/on-rust-exports/main.md)
- [`typescript_custom_section`](./reference/attributes/on-rust-exports/typescript_custom_section.md)
- [`getter` and `setter`](./reference/attributes/on-rust-exports/getter-and-setter.md)
- [`inspectable`](./reference/attributes/on-rust-exports/inspectable.md)
Expand Down
33 changes: 33 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,33 @@
# `main`

When attached to the `main` function this attribute will adjust it to properly
throw errors if they can be.

```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;
}
```

This attribute is only intended to be used on the `main` function of binaries or
examples. Unlike `#[wasm_bindgen(start)]`, it will not cause an arbitrary
function to be executed on start in a library.

The return type support is modeled after [`Termination`]. `()` and `Infallible`
are supported, but [`Termination`] itself is not. 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.

[`Termination`]: https://doc.rust-lang.org/std/process/trait.Termination.html
37 changes: 37 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,42 @@ 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_str(&std::format!("{:?}", e));
}
}
}
}

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